Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65d2371239 | |||
| ead2f3a62a | |||
| dfdc74bcf9 | |||
| 44672a61aa | |||
| 2adba16d29 | |||
| 290279df1c |
370
README.md
370
README.md
@@ -1,17 +1,17 @@
|
|||||||
# WP Business Forum - Anwender README
|
# WP Business Forum — Anwender-Dokumentation
|
||||||
|
|
||||||
WP Business Forum bringt ein modernes, eigenständiges Community-Forum direkt in deine WordPress-Website.
|
WP Business Forum bringt ein modernes, eigenständiges Community-Forum direkt in deine WordPress-Website.
|
||||||
Statt auf externe Plattformen auszuweichen, bleiben Diskussionen, Support-Anfragen und Mitgliederaktivität
|
Statt auf externe Plattformen auszuweichen, bleiben Diskussionen, Support-Anfragen und Mitgliederaktivität
|
||||||
zentral auf deiner eigenen Seite - inklusive voller Kontrolle über Inhalte, Rollen und Moderation.
|
zentral auf deiner eigenen Seite — inklusive voller Kontrolle über Inhalte, Rollen und Moderation.
|
||||||
|
|
||||||
Diese Dokumentation richtet sich an Betreiber, Moderatoren und Community-Manager, die das Forum
|
Diese Dokumentation richtet sich an Betreiber, Moderatoren und Community-Manager, die das Forum
|
||||||
schnell einrichten, sicher betreiben und im Alltag effizient verwalten möchten. Von der ersten
|
schnell einrichten, sicher betreiben und im Alltag effizient verwalten möchten. Von der ersten
|
||||||
Installation bis zum Live-Betrieb findest du hier alle wichtigen Schritte und Funktionen kompakt erklärt.
|
Installation bis zum Live-Betrieb findest du hier alle wichtigen Schritte und Funktionen kompakt erklärt.
|
||||||
|
|
||||||
Wenn du eine professionelle Community mit klaren Rechten, direkter Nutzerkommunikation und
|
---
|
||||||
strukturierter Moderation aufbauen willst, ist WP Business Forum dafür ausgelegt.
|
|
||||||
|
|
||||||
## Inhalt
|
## Inhalt
|
||||||
|
|
||||||
1. Über das Plugin
|
1. Über das Plugin
|
||||||
2. Funktionsübersicht
|
2. Funktionsübersicht
|
||||||
3. Voraussetzungen
|
3. Voraussetzungen
|
||||||
@@ -24,19 +24,29 @@ strukturierter Moderation aufbauen willst, ist WP Business Forum dafür ausgeleg
|
|||||||
10. Export, Import und Deinstallation
|
10. Export, Import und Deinstallation
|
||||||
11. FAQ / Troubleshooting
|
11. FAQ / Troubleshooting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 1) Über das Plugin
|
## 1) Über das Plugin
|
||||||
|
|
||||||
WP Business Forum ist ein eigenständiges Foren-System für WordPress mit:
|
WP Business Forum ist ein eigenständiges Foren-System für WordPress mit:
|
||||||
|
|
||||||
- eigenem Forum-Login (unabhängig vom WP-Login)
|
- eigenem Forum-Login (unabhängig vom WP-Login)
|
||||||
- Rollen- und Rechteverwaltung
|
- Rollen- und Rechteverwaltung
|
||||||
- Kategorien mit Hierarchie
|
- Kategorien mit Hierarchie
|
||||||
- Moderationswerkzeugen
|
- Moderationswerkzeugen
|
||||||
- Direktnachrichten, Benachrichtigungen, Meldesystem
|
- Direktnachrichten, Benachrichtigungen, Meldesystem
|
||||||
- Umfragen, Tags, Reaktionen, Lesezeichen
|
- Umfragen, Tags, Reaktionen, Lesezeichen
|
||||||
|
- Level-System (beitragsbasierte Rangstufen)
|
||||||
|
- Vollständigem Export / Import mit automatischer ID-Zuordnung
|
||||||
|
|
||||||
Das Forum wird per Shortcode in eine WordPress-Seite eingebunden.
|
Das Forum wird per Shortcode in eine WordPress-Seite eingebunden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2) Funktionsübersicht
|
## 2) Funktionsübersicht
|
||||||
|
|
||||||
### Für Mitglieder
|
### Für Mitglieder
|
||||||
|
|
||||||
- Registrieren / Einloggen / Logout
|
- Registrieren / Einloggen / Logout
|
||||||
- Passwort vergessen und Reset per E-Mail
|
- Passwort vergessen und Reset per E-Mail
|
||||||
- Threads erstellen, antworten, bearbeiten
|
- Threads erstellen, antworten, bearbeiten
|
||||||
@@ -48,243 +58,339 @@ Das Forum wird per Shortcode in eine WordPress-Seite eingebunden.
|
|||||||
- Private Nachrichten (DM)
|
- Private Nachrichten (DM)
|
||||||
- Profil mit Avatar, Bio, Signatur und eigenen Profilfeldern
|
- Profil mit Avatar, Bio, Signatur und eigenen Profilfeldern
|
||||||
- Mitgliederliste und Suchfunktion
|
- Mitgliederliste und Suchfunktion
|
||||||
|
- Andere Nutzer ignorieren / blockieren
|
||||||
|
|
||||||
### Für Moderation / Admin
|
### Für Moderation / Admin
|
||||||
|
|
||||||
- Threads pinnen, schließen, archivieren, verschieben, löschen
|
- Threads pinnen, schließen, archivieren, verschieben, löschen
|
||||||
- Beiträge löschen
|
- Beiträge löschen und wiederherstellen (Papierkorb)
|
||||||
- Meldungen (Reports) bearbeiten
|
- Meldungen (Reports) bearbeiten
|
||||||
- Kategorien und Rollen verwalten
|
- Kategorien und Rollen verwalten
|
||||||
|
- Mitglieder verwalten: Rolle ändern, Profil bearbeiten, Sperren, Löschen
|
||||||
- Einladungssystem für Registrierung
|
- Einladungssystem für Registrierung
|
||||||
- Wartungsmodus
|
- Wartungsmodus
|
||||||
- Wortfilter
|
- Wortfilter / Zensurliste
|
||||||
- Statistiken
|
- Statistiken und Aktivitäts-Dashboard
|
||||||
- Papierkorb / Wiederherstellung
|
- Export / Import (vollständiges Backup mit Wortfilter, Ignore-Liste, Präfixen u. v. m.)
|
||||||
- Export / Import
|
|
||||||
|
---
|
||||||
|
|
||||||
## 3) Voraussetzungen
|
## 3) Voraussetzungen
|
||||||
- Laufende WordPress-Installation
|
|
||||||
- Schreibrechte für WordPress-Uploads (für Avatar-/Bild-Uploads)
|
- Laufende WordPress-Installation (empfohlen: aktuelle Version)
|
||||||
|
- PHP 7.4 oder höher (empfohlen: PHP 8.0+)
|
||||||
|
- MySQL 5.7 / MariaDB 10.3 oder höher
|
||||||
|
- Schreibrechte für WordPress-Uploads (für Avatar- und Bild-Uploads)
|
||||||
- Funktionierende E-Mail-Zustellung in WordPress (für Passwort-Reset und Benachrichtigungen)
|
- Funktionierende E-Mail-Zustellung in WordPress (für Passwort-Reset und Benachrichtigungen)
|
||||||
|
|
||||||
Hinweis: Das Plugin nutzt eigene Datenbanktabellen (Präfix `wp_forum_*` bzw. mit deinem Tabellenpräfix).
|
> Das Plugin nutzt eigene Datenbanktabellen mit dem Präfix `wp_forum_*` (bzw. deinem konfigurierten Tabellenpräfix).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 4) Installation
|
## 4) Installation
|
||||||
|
|
||||||
1. Plugin-Ordner `wp-business-forum` in `wp-content/plugins/` kopieren.
|
1. Plugin-Ordner `wp-business-forum` in `wp-content/plugins/` kopieren.
|
||||||
2. Im WordPress-Backend unter Plugins aktivieren.
|
2. Im WordPress-Backend unter **Plugins** aktivieren.
|
||||||
3. Nach der Aktivierung startet einmalig der Setup-Wizard.
|
3. Nach der Aktivierung startet einmalig der Setup-Wizard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 5) Ersteinrichtung (Setup-Wizard)
|
## 5) Ersteinrichtung (Setup-Wizard)
|
||||||
Nach Aktivierung führt der Wizard durch 3 Schritte:
|
|
||||||
|
|
||||||
1. Superadmin-Konto erstellen oder bestehendes Forum-Konto hochstufen
|
Nach der Aktivierung führt der Wizard durch drei Schritte:
|
||||||
|
|
||||||
|
1. Superadmin-Konto erstellen oder bestehendes Forum-Konto verknüpfen
|
||||||
2. Optional automatisch eine Forum-Seite erzeugen
|
2. Optional automatisch eine Forum-Seite erzeugen
|
||||||
3. Abschluss
|
3. Abschluss und Weiterleitung ins Dashboard
|
||||||
|
|
||||||
Wichtig:
|
**Wichtig:**
|
||||||
- Der Superadmin ist fest mit dem WordPress-Admin verknüpft.
|
- Der Superadmin ist fest mit dem WordPress-Administrator verknüpft und kann nicht über den Import überschrieben werden.
|
||||||
- Wenn noch kein Superadmin existiert, erscheint im Backend ein Hinweisbanner.
|
- Wenn noch kein Superadmin existiert, erscheint im Backend ein Hinweisbanner.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 6) Forum-Seite einbinden
|
## 6) Forum-Seite einbinden
|
||||||
|
|
||||||
Das Forum wird mit folgendem Shortcode auf einer WordPress-Seite angezeigt:
|
Das Forum wird mit folgendem Shortcode auf einer WordPress-Seite angezeigt:
|
||||||
|
|
||||||
```text
|
```
|
||||||
[business_forum]
|
[business_forum]
|
||||||
```
|
```
|
||||||
|
|
||||||
Empfohlen:
|
**Empfehlung:**
|
||||||
- Eine eigene Seite (z. B. "Forum") anlegen
|
- Eine eigene Seite (z. B. „Forum") anlegen
|
||||||
- Nur diesen Shortcode als Seiteninhalt verwenden
|
- Nur diesen Shortcode als Seiteninhalt verwenden
|
||||||
|
- Die Seite in der WordPress-Navigation verlinken
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 7) Bedienung im Frontend (Mitglieder)
|
## 7) Bedienung im Frontend (Mitglieder)
|
||||||
|
|
||||||
### 7.1 Registrierung und Login
|
### 7.1 Registrierung und Login
|
||||||
- Registrierung kann offen, nur per Einladung oder deaktiviert sein.
|
|
||||||
|
- Die Registrierung kann offen, nur per Einladung oder deaktiviert sein.
|
||||||
- Optional müssen Nutzer die Forum-Regeln akzeptieren.
|
- Optional müssen Nutzer die Forum-Regeln akzeptieren.
|
||||||
- Spam-Schutz bei Registrierung:
|
- Spam-Schutz bei der Registrierung:
|
||||||
- Honeypot-Feld
|
- Honeypot-Feld
|
||||||
- Mindestzeit bis Formular-Absenden
|
- Mindestzeit bis zum Formular-Absenden
|
||||||
- Login unterstützt "Angemeldet bleiben" (Remember-Me Cookie).
|
- Login unterstützt „Angemeldet bleiben" (Remember-Me Cookie, 30 Tage).
|
||||||
|
|
||||||
### 7.2 Kategorien und Threads
|
### 7.2 Kategorien und Threads
|
||||||
|
|
||||||
- Kategorien können verschachtelt sein (Hauptkategorie + Unterkategorien).
|
- Kategorien können verschachtelt sein (Hauptkategorie + Unterkategorien).
|
||||||
- Sichtbarkeit kann rollenbasiert sein.
|
- Die Sichtbarkeit kann rollenbasiert eingeschränkt werden.
|
||||||
- Threads können folgende Zustände haben:
|
- Threads können folgende Zustände haben: offen · geschlossen · archiviert · gepinnt
|
||||||
- offen
|
|
||||||
- geschlossen
|
|
||||||
- archiviert
|
|
||||||
- gepinnt
|
|
||||||
|
|
||||||
### 7.3 Thread erstellen
|
### 7.3 Thread erstellen
|
||||||
|
|
||||||
- Mindestlänge Titel: 5 Zeichen
|
- Mindestlänge Titel: 5 Zeichen
|
||||||
- Mindestlänge Inhalt: 10 Zeichen (bei normalem Thread)
|
- Mindestlänge Inhalt: 10 Zeichen
|
||||||
- Tags können vergeben werden
|
- Tags können vergeben werden
|
||||||
- Optional kann ein Thread-Präfix gesetzt werden
|
- Optional kann ein Thread-Präfix gesetzt werden
|
||||||
- Optional kann direkt eine Umfrage erstellt werden
|
- Optional kann direkt eine Umfrage erstellt werden
|
||||||
|
|
||||||
### 7.4 Antworten und Bearbeiten
|
### 7.4 Antworten und Bearbeiten
|
||||||
- Antworten mit BBCode-Unterstützung
|
|
||||||
|
- Antworten mit BBCode-Unterstützung (`[b]`, `[i]`, `[quote]`, `[code]`, `[spoiler]`, `[url]`, `[img]` u. v. m.)
|
||||||
- Flood-Control: konfigurierbare Wartezeit zwischen Posts
|
- Flood-Control: konfigurierbare Wartezeit zwischen Posts
|
||||||
- Eigene Posts nur innerhalb des eingestellten Bearbeitungsfensters (z. B. 30 Minuten)
|
- Eigene Posts können nur innerhalb des konfigurierten Bearbeitungsfensters geändert werden
|
||||||
- Moderation kann unabhängig davon eingreifen
|
- Moderation kann unabhängig davon jederzeit eingreifen
|
||||||
|
|
||||||
### 7.5 Umfragen
|
### 7.5 Umfragen
|
||||||
- Umfrage direkt beim Thread-Erstellen oder nachträglich im Thread
|
|
||||||
|
- Umfrage direkt beim Thread-Erstellen oder nachträglich anfügen
|
||||||
- 2 bis 10 Antwortoptionen
|
- 2 bis 10 Antwortoptionen
|
||||||
- Optional Mehrfachauswahl
|
- Optional Mehrfachauswahl
|
||||||
- Optional Enddatum
|
- Optional Enddatum
|
||||||
- Nach Abstimmung werden Ergebnisse direkt angezeigt
|
- Nach der Abstimmung werden Ergebnisse direkt angezeigt
|
||||||
|
|
||||||
### 7.6 Reaktionen, Likes, Lesezeichen
|
### 7.6 Reaktionen, Likes, Lesezeichen
|
||||||
- Likes auf Thread/Beitrag
|
|
||||||
|
- Likes auf Threads und Beiträge
|
||||||
- Emoji-Reaktionen (adminseitig konfigurierbar)
|
- Emoji-Reaktionen (adminseitig konfigurierbar)
|
||||||
- Lesezeichen für Threads (im Profil einsehbar)
|
- Lesezeichen für Threads, im Profil jederzeit einsehbar
|
||||||
|
|
||||||
### 7.7 Private Nachrichten (DM)
|
### 7.7 Private Nachrichten (DM)
|
||||||
|
|
||||||
- 1:1 Nachrichten zwischen Mitgliedern
|
- 1:1 Nachrichten zwischen Mitgliedern
|
||||||
- Inbox-Ansicht und Konversation
|
- Inbox-Ansicht und Konversationsansicht
|
||||||
- Ungelesene Nachrichten werden gezählt
|
- Ungelesene Nachrichten werden im Header gezählt
|
||||||
- Optional E-Mail-Hinweis bei neuer Nachricht
|
- Optional E-Mail-Hinweis bei neuer Nachricht
|
||||||
|
|
||||||
### 7.8 Benachrichtigungen
|
### 7.8 Benachrichtigungen
|
||||||
Benachrichtigungen bei:
|
|
||||||
- Antworten auf abonnierte / relevante Threads
|
Benachrichtigungen werden ausgelöst bei:
|
||||||
- @Erwähnungen
|
|
||||||
- neuen privaten Nachrichten
|
- Antworten auf abonnierte Threads
|
||||||
|
- @Erwähnungen in Beiträgen
|
||||||
|
- Neuen privaten Nachrichten
|
||||||
|
|
||||||
### 7.9 Profil
|
### 7.9 Profil
|
||||||
|
|
||||||
Mitglieder können:
|
Mitglieder können:
|
||||||
|
|
||||||
- Anzeigenamen, Bio und Signatur pflegen
|
- Anzeigenamen, Bio und Signatur pflegen
|
||||||
- Avatar hochladen
|
- Avatar hochladen (max. 2 MB, JPG/PNG/GIF/WebP)
|
||||||
- Passwort ändern
|
- Passwort ändern
|
||||||
- eigene Profil-Sichtbarkeit umschalten
|
- Profil-Sichtbarkeit umschalten
|
||||||
- benutzerdefinierte Profilfelder ausfüllen (falls aktiviert)
|
- Benutzerdefinierte Profilfelder ausfüllen (falls vom Admin aktiviert)
|
||||||
|
- Andere Nutzer zur Ignore-Liste hinzufügen
|
||||||
|
|
||||||
Upload-Limits:
|
Upload-Limits:
|
||||||
- Avatar: max. 2 MB (JPG/PNG/GIF/WebP)
|
|
||||||
- Bild im Beitrag: max. 5 MB (JPG/PNG/GIF/WebP)
|
- Avatar: max. 2 MB (JPG / PNG / GIF / WebP)
|
||||||
|
- Bild im Beitrag: max. 5 MB (JPG / PNG / GIF / WebP)
|
||||||
|
|
||||||
### 7.10 Passwort vergessen
|
### 7.10 Passwort vergessen
|
||||||
- Über "Passwort vergessen" kann ein Reset-Link per E-Mail angefordert werden.
|
|
||||||
- Das Zurücksetzen erfolgt über einen zeitlich gültigen Token.
|
Über „Passwort vergessen" kann ein Reset-Link per E-Mail angefordert werden. Das Zurücksetzen erfolgt über einen zeitlich begrenzten Token.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 8) Moderation und Verwaltung
|
## 8) Moderation und Verwaltung
|
||||||
Im WordPress-Backend gibt es den Menüpunkt "Business Forum" mit Unterseiten:
|
|
||||||
|
|
||||||
- Übersicht: Dashboard mit Kennzahlen und Aktivitäten
|
Im WordPress-Backend gibt es den Menüpunkt **Business Forum** mit folgenden Unterseiten:
|
||||||
- Kategorien: Struktur und Sichtbarkeit verwalten
|
|
||||||
- Rollen: Rollen/Permissions anpassen
|
| Unterseite | Funktion |
|
||||||
- Level: Beitragsbasierte Rangstufen
|
|---|---|
|
||||||
- Mitglieder: Nutzer verwalten
|
| Übersicht | Dashboard mit Kennzahlen, Trends und Aktivitätsprotokoll |
|
||||||
- Meldungen: gemeldete Inhalte bearbeiten
|
| Kategorien | Struktur, Hierarchie und Sichtbarkeit verwalten |
|
||||||
- Profilfelder: eigene Felder definieren
|
| Rollen | Rollen, Permissions und Design anpassen |
|
||||||
- Einstellungen: Texte, Sicherheit, Registrierung, Regeln, Wartung
|
| Level | Beitragsbasierte Rangstufen konfigurieren |
|
||||||
- Reaktionen: erlaubte Emoji-Reaktionen
|
| Mitglieder | Nutzer verwalten, sperren, löschen |
|
||||||
- Einladungen: Invite-Codes erstellen und verwalten
|
| Meldungen | Gemeldete Inhalte bearbeiten |
|
||||||
- Statistiken: Forum-Auswertung
|
| Profilfelder | Eigene Felder definieren |
|
||||||
- Papierkorb: gelöschte Inhalte wiederherstellen
|
| Einstellungen | Texte, Sicherheit, Registrierung, Regeln, Wartung |
|
||||||
- Thread-Präfixe: Label für Threads verwalten
|
| Reaktionen | Erlaubte Emoji-Reaktionen konfigurieren |
|
||||||
- Wortfilter: unerwünschte Begriffe ersetzen/filtern
|
| Einladungen | Invite-Codes erstellen und verwalten |
|
||||||
- Export / Import: Backup und Wiederherstellung
|
| Statistiken | Forum-Auswertung und Trends |
|
||||||
- Deinstallieren: komplette Löschung des Plugins inkl. Daten
|
| Papierkorb | Gelöschte Inhalte einsehen und wiederherstellen |
|
||||||
|
| Thread-Präfixe | Farbige Label für Threads verwalten |
|
||||||
|
| Wortfilter | Unerwünschte Begriffe automatisch ersetzen |
|
||||||
|
| Export / Import | Vollständiges Backup und Wiederherstellung |
|
||||||
|
| ⚠️ Deinstallieren | Komplette Löschung inkl. aller Daten |
|
||||||
|
| 🔔 Updates | Update-Status und Changelog |
|
||||||
|
|
||||||
|
### 8.1 Mitglieder verwalten
|
||||||
|
|
||||||
|
In der Mitglieder-Übersicht stehen pro Nutzer drei Aktionen zur Verfügung:
|
||||||
|
|
||||||
|
**Rolle ändern**
|
||||||
|
Rolle direkt aus dem Dropdown wählen und speichern. Bei „Gesperrt" kann zusätzlich ein Sperrgrund und ein automatisches Ablaufdatum (temporäre Sperre) gesetzt werden.
|
||||||
|
|
||||||
|
**Profil bearbeiten**
|
||||||
|
Anzeigename, E-Mail, Passwort, Bio, Signatur und benutzerdefinierte Profilfelder direkt im Admin ändern.
|
||||||
|
|
||||||
|
**Nutzer löschen**
|
||||||
|
Beim Klick auf „Löschen" öffnet sich ein Bestätigungs-Panel mit zwei Optionen:
|
||||||
|
|
||||||
|
- **DSGVO Anonymisieren** *(empfohlen)*: Der Account wird nach Art. 17 DSGVO anonymisiert — Benutzername, E-Mail und Passwort werden gelöscht, Threads und Beiträge bleiben unter „Gelöschter Nutzer" erhalten.
|
||||||
|
- **Dauerhaft löschen**: Der Datensatz wird vollständig aus der Datenbank entfernt. Alle nutzerbezogenen Daten (Nachrichten, Likes, Reaktionen, Abonnements, Lesezeichen u. a.) werden gelöscht. Threads und Beiträge bleiben anonym erhalten. **Dieser Vorgang ist nicht rückgängig zu machen.**
|
||||||
|
|
||||||
|
> Der Superadmin-Account ist in beiden Pfaden geschützt und kann nicht gelöscht werden.
|
||||||
|
|
||||||
|
### 8.2 Sperren von Nutzern
|
||||||
|
|
||||||
|
Statt eines vollständigen Löschens kann ein Nutzer auch gesperrt werden (Rolle „Gesperrt"):
|
||||||
|
|
||||||
|
- **Permanente Sperre**: Kein Forum-Zugang, Sperrgrund wird beim Login angezeigt.
|
||||||
|
- **Temporäre Sperre**: Automatische Entsperrung zum angegebenen Datum/Uhrzeit. Bei Ablauf wird die vorherige Rolle automatisch wiederhergestellt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 9) Einstellungen im Detail
|
## 9) Einstellungen im Detail
|
||||||
Unter Business Forum > Einstellungen:
|
|
||||||
|
Unter **Business Forum › Einstellungen**:
|
||||||
|
|
||||||
### 9.1 Texte und UI
|
### 9.1 Texte und UI
|
||||||
- Hero-Titel/Untertitel
|
|
||||||
|
- Hero-Titel und Untertitel
|
||||||
- Topbar-Brand
|
- Topbar-Brand
|
||||||
- Label für Statistik
|
- Labels für Statistiken
|
||||||
- Abschnittstitel
|
- Abschnittstitel und Buttontexte
|
||||||
- Buttontexte
|
|
||||||
- Sidebar-Titel
|
- Sidebar-Titel
|
||||||
|
|
||||||
### 9.2 Sicherheit
|
### 9.2 Sicherheit
|
||||||
- Auto-Logout nach Inaktivität (0 = deaktiviert)
|
|
||||||
- Post-Bearbeitungslimit
|
- Auto-Logout nach Inaktivität (0 = deaktiviert, in Minuten)
|
||||||
- Spam-Mindestzeit bei Registrierung
|
- Post-Bearbeitungslimit (in Minuten, 0 = unbegrenzt)
|
||||||
- Flood-Control Intervall
|
- Spam-Mindestzeit bei Registrierung (in Sekunden)
|
||||||
- Profil-Sichtbarkeit (Standard)
|
- Flood-Control Intervall zwischen Posts (in Sekunden, 0 = deaktiviert)
|
||||||
|
- Standard-Profil-Sichtbarkeit für neue Mitglieder
|
||||||
|
|
||||||
### 9.3 Registrierung
|
### 9.3 Registrierung
|
||||||
- Modus:
|
|
||||||
- offen
|
- Modus: **offen** · **nur Einladung** · **deaktiviert**
|
||||||
- nur Einladung
|
- Freitext-Hinweis bei Einladungs-Modus
|
||||||
- deaktiviert
|
- Forum-Regeln bei Registrierung verpflichtend akzeptieren
|
||||||
- Freitext-Hinweis für Einladungsmode
|
|
||||||
|
|
||||||
### 9.4 Wartungsmodus
|
### 9.4 Wartungsmodus
|
||||||
|
|
||||||
- Forum für normale Nutzer sperren
|
- Forum für normale Nutzer sperren
|
||||||
- Moderation/Admin behalten Zugriff
|
- Moderation und Admins behalten vollen Zugriff
|
||||||
- Eigener Wartungs-Titel und Hinweistext
|
- Eigener Wartungs-Titel und Hinweistext konfigurierbar
|
||||||
|
|
||||||
### 9.5 Forum-Regeln / Nutzungsbedingungen
|
### 9.5 Forum-Regeln / Nutzungsbedingungen
|
||||||
- Regelseite aktivieren/deaktivieren
|
|
||||||
|
- Regelseite aktivieren / deaktivieren
|
||||||
- Akzeptierung bei Registrierung optional verpflichtend
|
- Akzeptierung bei Registrierung optional verpflichtend
|
||||||
- Titel und Inhalt frei editierbar
|
- Titel und Inhalt frei editierbar (unterstützt einfaches Markdown)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 10) Export, Import und Deinstallation
|
## 10) Export, Import und Deinstallation
|
||||||
### 10.1 Export / Import
|
|
||||||
Exportierbare Bereiche (je nach Auswahl):
|
|
||||||
- Einstellungen
|
|
||||||
- Rollen und Level
|
|
||||||
- Kategorien
|
|
||||||
- Nutzer und User-Meta
|
|
||||||
- Threads und Posts
|
|
||||||
- Interaktionen (Likes/Reaktionen/Benachrichtigungen)
|
|
||||||
- Nachrichten
|
|
||||||
- Meldungen
|
|
||||||
- Einladungen
|
|
||||||
|
|
||||||
Empfehlung:
|
### 10.1 Export
|
||||||
- Vor großen Änderungen immer einen Voll-Export speichern.
|
|
||||||
|
|
||||||
### 10.2 Deinstallation (wichtig)
|
Unter **Business Forum › Export / Import** kannst du einzelne oder alle Bereiche als `.json`-Datei exportieren:
|
||||||
Beim Löschen des Plugins werden komplett entfernt:
|
|
||||||
- alle Forum-Datenbanktabellen
|
| Bereich | Enthält |
|
||||||
- relevante Plugin-Optionen
|
|---|---|
|
||||||
|
| Einstellungen & Wortfilter | Forum-Texte, Regeln, Labels, Auto-Logout, Wortfilter, Profilfeld-Definitionen, Reaktionen-Konfiguration |
|
||||||
|
| Rollen | Alle Rollen mit Berechtigungen und Design (Superadmin wird nie überschrieben) |
|
||||||
|
| Level-System | Level-Namen, Schwellenwerte, Icons, Farben, An/Aus-Status |
|
||||||
|
| Kategorien | Kategoriestruktur inkl. Eltern-Kind-Hierarchie, Icons, Min-Rolle |
|
||||||
|
| Benutzer & Profilfelder | Accounts inkl. Passwort-Hashes, Ban-Status, Profilfeld-Werte |
|
||||||
|
| Threads, Posts & Abonnements | Alle Inhalte inkl. Tag-Zuordnungen und Thread-Abonnements |
|
||||||
|
| Umfragen | Alle Umfragen inkl. Abstimmungen |
|
||||||
|
| Lesezeichen | Alle gespeicherten Thread-Lesezeichen |
|
||||||
|
| Thread-Präfixe | Alle Präfix-Labels, Farben und Reihenfolgen |
|
||||||
|
| Likes & Reaktionen | Likes, Emoji-Reaktionen, Benachrichtigungen |
|
||||||
|
| Privatnachrichten | Alle DM-Konversationen |
|
||||||
|
| Ignore-Liste | Alle gegenseitigen Nutzer-Blockierungen |
|
||||||
|
| Meldungen | Gemeldete Beiträge inkl. Status |
|
||||||
|
| Einladungen | Alle Einladungscodes inkl. Nutzungsanzahl und Ablaufdatum |
|
||||||
|
|
||||||
|
**Tipp:** Mit „Alle wählen" / „Keine" lässt sich die Auswahl schnell anpassen. Die Datei wird sofort heruntergeladen.
|
||||||
|
|
||||||
|
### 10.2 Import
|
||||||
|
|
||||||
|
Beim Import einer zuvor exportierten `.json`-Datei gilt:
|
||||||
|
|
||||||
|
- Maximale Dateigröße: **50 MB**
|
||||||
|
- Nur Dateien im WBF-Format werden akzeptiert
|
||||||
|
- **Benutzer-IDs werden beim Import automatisch gemappt** — Threads, Posts, Likes, Reaktionen und alle anderen nutzerbezogenen Daten werden korrekt auf die neuen Datenbank-IDs übertragen, auch wenn sich diese von der Quelldatenbank unterscheiden
|
||||||
|
- Nach dem Import werden alle Zähler (Beitrags-, Thread- und Reaktionszähler) automatisch neu berechnet
|
||||||
|
- Der Superadmin kann per Import nie überschrieben werden
|
||||||
|
|
||||||
|
Über die **Überschreiben-Optionen** lässt sich pro Bereich steuern, ob bestehende Daten ersetzt oder Duplikate übersprungen werden sollen.
|
||||||
|
|
||||||
|
> ⚠️ Erstelle vor jedem Import einen aktuellen Export als Sicherung. Benutzer-Exporte enthalten Passwort-Hashes — teile diese Dateien nicht öffentlich.
|
||||||
|
|
||||||
|
### 10.3 Deinstallation
|
||||||
|
|
||||||
|
Unter **Business Forum › ⚠️ Deinstallieren** oder beim Löschen des Plugins im WordPress-Backend werden vollständig entfernt:
|
||||||
|
|
||||||
|
- Alle Forum-Datenbanktabellen (`wp_forum_*`)
|
||||||
|
- Alle Plugin-Optionen in `wp_options`
|
||||||
- Transients
|
- Transients
|
||||||
- geplanter Cron-Job
|
- Geplante Cron-Jobs
|
||||||
- automatisch erstellte Forum-Seite
|
- Automatisch erstellte Forum-Seite
|
||||||
- zugehörige Upload-Unterverzeichnisse
|
- Upload-Unterverzeichnis `wbf-avatars`
|
||||||
|
|
||||||
Das ist eine echte Datenlöschung. Vorher immer Backup erstellen.
|
> **Das ist eine echte, unwiderrufliche Datenlöschung. Immer vorher einen vollständigen Export erstellen.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 11) FAQ / Troubleshooting
|
## 11) FAQ / Troubleshooting
|
||||||
### Login funktioniert nicht
|
|
||||||
- Prüfen, ob das Konto gesperrt ist
|
|
||||||
- Bei zeitlicher Sperre Ablaufzeit abwarten
|
|
||||||
- Bei Registrierung "Nur Einladung" gültigen Invite-Code nutzen
|
|
||||||
|
|
||||||
### Registrierung nicht sichtbar
|
**Login funktioniert nicht**
|
||||||
- In Einstellungen den Registrierungsmodus prüfen
|
Prüfen ob das Konto gesperrt ist. Bei temporärer Sperre das Ablaufdatum abwarten. Bei „Nur Einladung" einen gültigen Invite-Code verwenden.
|
||||||
- Bei deaktiviertem Modus ist keine Selbstregistrierung möglich
|
|
||||||
|
|
||||||
### Keine E-Mails kommen an
|
**Registrierung nicht sichtbar**
|
||||||
- WordPress-Mailversand prüfen (SMTP Plugin empfohlen)
|
In den Einstellungen den Registrierungsmodus prüfen. Bei deaktiviertem Modus ist keine Selbstregistrierung möglich.
|
||||||
- Admin-E-Mail in WordPress kontrollieren
|
|
||||||
|
|
||||||
### Upload von Bildern/Avatar scheitert
|
**Keine E-Mails kommen an**
|
||||||
- Dateityp prüfen (nur JPG/PNG/GIF/WebP)
|
WordPress-Mailversand prüfen. Ein SMTP-Plugin wird empfohlen. Die Admin-E-Mail in WordPress kontrollieren.
|
||||||
- Dateigröße prüfen (Avatar 2 MB, Beitrag 5 MB)
|
|
||||||
- Schreibrechte in Uploads prüfen
|
|
||||||
|
|
||||||
### Benutzer werden automatisch ausgeloggt
|
**Upload von Bildern / Avatar scheitert**
|
||||||
- Auto-Logout in den Forum-Einstellungen prüfen
|
Dateityp prüfen (nur JPG/PNG/GIF/WebP). Dateigröße prüfen (Avatar 2 MB, Beitrag 5 MB). Schreibrechte im Uploads-Verzeichnis prüfen.
|
||||||
|
|
||||||
### Forum ist plötzlich "offline"
|
**Import schlägt fehl oder überschreibt falsche Daten**
|
||||||
- Wartungsmodus in den Einstellungen deaktivieren
|
Sicherstellen, dass die Datei aus einer WBF-Installation stammt. Überschreiben-Optionen gezielt setzen. Bei sehr großen Backups `upload_max_filesize` und `post_max_size` in der `php.ini` erhöhen.
|
||||||
|
|
||||||
### Suche liefert keine Ergebnisse
|
**Benutzer werden automatisch ausgeloggt**
|
||||||
- Suchbegriff muss mindestens 2 Zeichen haben
|
Auto-Logout in den Forum-Einstellungen prüfen (Wert in Minuten, 0 = deaktiviert).
|
||||||
|
|
||||||
|
**Forum ist plötzlich „offline"**
|
||||||
|
Wartungsmodus in den Einstellungen deaktivieren.
|
||||||
|
|
||||||
|
**Suche liefert keine Ergebnisse**
|
||||||
|
Der Suchbegriff muss mindestens 2 Zeichen lang sein.
|
||||||
|
|
||||||
|
**Nach dem Import stimmen Beitragszähler nicht**
|
||||||
|
Ab Version 1.0.2 werden Zähler nach jedem Import automatisch neu berechnet. Bei älteren Imports einmalig einen neuen Import mit der aktuellen Version durchführen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Kurz-Checkliste für den Live-Betrieb
|
## Kurz-Checkliste für den Live-Betrieb
|
||||||
|
|
||||||
1. Setup-Wizard abschließen
|
1. Setup-Wizard abschließen
|
||||||
2. Forum-Seite mit `[business_forum]` bereitstellen
|
2. Forum-Seite mit `[business_forum]` bereitstellen
|
||||||
3. Rollen und Kategorien final konfigurieren
|
3. Rollen und Kategorien final konfigurieren
|
||||||
4. Registrierungsmodus festlegen
|
4. Registrierungsmodus festlegen
|
||||||
5. Regeln/Nutzungsbedingungen hinterlegen
|
5. Regeln / Nutzungsbedingungen hinterlegen
|
||||||
6. E-Mail-Versand testen
|
6. E-Mail-Versand testen
|
||||||
7. Backup-Export erstellen
|
7. Vollständigen Backup-Export erstellen
|
||||||
|
|
||||||
Viel Erfolg mit deinem Forum!
|
Viel Erfolg mit deinem Forum!
|
||||||
@@ -31,6 +31,7 @@ add_action( 'admin_menu', function() {
|
|||||||
add_submenu_page( 'wbf-admin', 'Wortfilter', 'Wortfilter', 'manage_options', 'wbf-wordfilter', 'wbf_admin_wordfilter' );
|
add_submenu_page( 'wbf-admin', 'Wortfilter', 'Wortfilter', 'manage_options', 'wbf-wordfilter', 'wbf_admin_wordfilter' );
|
||||||
add_submenu_page( 'wbf-admin', 'Export / Import','Export / Import','manage_options', 'wbf-export', 'wbf_admin_export' );
|
add_submenu_page( 'wbf-admin', 'Export / Import','Export / Import','manage_options', 'wbf-export', 'wbf_admin_export' );
|
||||||
add_submenu_page( 'wbf-admin', '⚠️ Deinstallieren', '⚠️ Deinstallieren', 'manage_options', 'wbf-uninstall', 'wbf_admin_uninstall' );
|
add_submenu_page( 'wbf-admin', '⚠️ Deinstallieren', '⚠️ Deinstallieren', 'manage_options', 'wbf-uninstall', 'wbf_admin_uninstall' );
|
||||||
|
add_submenu_page( 'wbf-admin', '🔔 Updates', '🔔 Updates', 'manage_options', 'wbf-updates', 'wbf_admin_updates' );
|
||||||
}, 10 );
|
}, 10 );
|
||||||
|
|
||||||
// Meldungs-Badge im Menü (separater Hook mit Priorität 999, läuft nach der Registrierung)
|
// Meldungs-Badge im Menü (separater Hook mit Priorität 999, läuft nach der Registrierung)
|
||||||
@@ -84,6 +85,7 @@ add_action( 'admin_init', function() {
|
|||||||
case 'settings':
|
case 'settings':
|
||||||
$data['settings'] = get_option('wbf_settings', []);
|
$data['settings'] = get_option('wbf_settings', []);
|
||||||
$data['profile_fields'] = get_option('wbf_profile_fields', []);
|
$data['profile_fields'] = get_option('wbf_profile_fields', []);
|
||||||
|
$data['reactions_cfg'] = get_option('wbf_reactions', []);
|
||||||
break;
|
break;
|
||||||
case 'roles':
|
case 'roles':
|
||||||
$data['roles'] = get_option('wbf_custom_roles', []);
|
$data['roles'] = get_option('wbf_custom_roles', []);
|
||||||
@@ -125,6 +127,16 @@ add_action( 'admin_init', function() {
|
|||||||
);
|
);
|
||||||
$data['subscriptions'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_subscriptions ORDER BY id ASC", ARRAY_A );
|
$data['subscriptions'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_subscriptions ORDER BY id ASC", ARRAY_A );
|
||||||
break;
|
break;
|
||||||
|
case 'polls':
|
||||||
|
$data['polls'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_polls ORDER BY id ASC", ARRAY_A );
|
||||||
|
$data['poll_votes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_poll_votes ORDER BY id ASC", ARRAY_A );
|
||||||
|
break;
|
||||||
|
case 'bookmarks':
|
||||||
|
$data['bookmarks'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_bookmarks ORDER BY id ASC", ARRAY_A );
|
||||||
|
break;
|
||||||
|
case 'prefixes':
|
||||||
|
$data['prefixes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_prefixes ORDER BY sort_order ASC", ARRAY_A );
|
||||||
|
break;
|
||||||
case 'interactions':
|
case 'interactions':
|
||||||
$data['likes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_likes ORDER BY id ASC", ARRAY_A );
|
$data['likes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_likes ORDER BY id ASC", ARRAY_A );
|
||||||
$data['reactions'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_reactions ORDER BY id ASC", ARRAY_A );
|
$data['reactions'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_reactions ORDER BY id ASC", ARRAY_A );
|
||||||
@@ -139,6 +151,9 @@ add_action( 'admin_init', function() {
|
|||||||
case 'invites':
|
case 'invites':
|
||||||
$data['invites'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_invites ORDER BY id ASC", ARRAY_A );
|
$data['invites'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_invites ORDER BY id ASC", ARRAY_A );
|
||||||
break;
|
break;
|
||||||
|
case 'ignore_list':
|
||||||
|
$data['ignore_list'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_ignore_list ORDER BY id ASC", ARRAY_A );
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +354,7 @@ function wbf_admin_page() {
|
|||||||
$online_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE last_active >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)");
|
$online_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE last_active >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)");
|
||||||
$invite_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_invites WHERE use_count < max_uses AND (expires_at IS NULL OR expires_at > NOW())");
|
$invite_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_invites WHERE use_count < max_uses AND (expires_at IS NULL OR expires_at > NOW())");
|
||||||
$banned_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE role='banned'");
|
$banned_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE role='banned'");
|
||||||
|
$update_info = wbf_update_available(); // null oder array mit Versionsdaten
|
||||||
|
|
||||||
// ── System ────────────────────────────────────────────────────────────────
|
// ── System ────────────────────────────────────────────────────────────────
|
||||||
$php_ver = PHP_VERSION;
|
$php_ver = PHP_VERSION;
|
||||||
@@ -537,6 +553,7 @@ function wbf_admin_page() {
|
|||||||
['wbf-trash', 'fas fa-trash-can', 'Papierkorb', $deleted_count>0?$deleted_count:0, true],
|
['wbf-trash', 'fas fa-trash-can', 'Papierkorb', $deleted_count>0?$deleted_count:0, true],
|
||||||
['wbf-export', 'fas fa-database', 'Export / Import'],
|
['wbf-export', 'fas fa-database', 'Export / Import'],
|
||||||
['wbf-settings', 'fas fa-gear', 'Einstellungen'],
|
['wbf-settings', 'fas fa-gear', 'Einstellungen'],
|
||||||
|
['wbf-updates', 'fas fa-arrow-up-from-bracket', '🔔 Updates', $update_info ? 1 : 0],
|
||||||
];
|
];
|
||||||
foreach ($nav as $n):
|
foreach ($nav as $n):
|
||||||
if ($n===null) { echo '<div class="wbf-nav__sep"></div>'; continue; }
|
if ($n===null) { echo '<div class="wbf-nav__sep"></div>'; continue; }
|
||||||
@@ -1201,6 +1218,65 @@ function wbf_admin_members() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Nutzer löschen (DSGVO Art. 17 / Hard Delete) ─────────────────────────
|
||||||
|
if ( isset( $_POST['wbf_delete_member'] ) && check_admin_referer( 'wbf_delete_member_nonce' ) ) {
|
||||||
|
$uid = (int) ( $_POST['user_id'] ?? 0 );
|
||||||
|
$mode = sanitize_key( $_POST['delete_mode'] ?? 'anonymize' );
|
||||||
|
if ( $uid ) {
|
||||||
|
$target = WBF_DB::get_user( $uid );
|
||||||
|
if ( ! $target ) {
|
||||||
|
echo '<div class="notice notice-error is-dismissible"><p>❌ Nutzer nicht gefunden.</p></div>';
|
||||||
|
} elseif ( $target->role === WBF_Roles::SUPERADMIN ) {
|
||||||
|
echo '<div class="notice notice-error is-dismissible"><p>❌ Der Superadmin kann nicht gelöscht werden.</p></div>';
|
||||||
|
} else {
|
||||||
|
global $wpdb;
|
||||||
|
$name = esc_html( $target->display_name );
|
||||||
|
|
||||||
|
if ( $mode === 'hard' ) {
|
||||||
|
// Alle nutzerbezogenen Daten entfernen
|
||||||
|
$dep_tables = [
|
||||||
|
'forum_messages' => [ 'from_id', 'to_id' ],
|
||||||
|
'forum_notifications' => [ 'user_id', 'actor_id' ],
|
||||||
|
'forum_subscriptions' => [ 'user_id' ],
|
||||||
|
'forum_bookmarks' => [ 'user_id' ],
|
||||||
|
'forum_likes' => [ 'user_id' ],
|
||||||
|
'forum_reactions' => [ 'user_id' ],
|
||||||
|
'forum_reports' => [ 'reporter_id' ],
|
||||||
|
'forum_poll_votes' => [ 'user_id' ],
|
||||||
|
'forum_remember_tokens' => [ 'user_id' ],
|
||||||
|
'forum_user_meta' => [ 'user_id' ],
|
||||||
|
'forum_ignore_list' => [ 'user_id', 'ignored_id' ],
|
||||||
|
];
|
||||||
|
foreach ( $dep_tables as $tbl => $cols ) {
|
||||||
|
$full = $wpdb->prefix . $tbl;
|
||||||
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$full'" ) !== $full ) continue;
|
||||||
|
foreach ( $cols as $col ) {
|
||||||
|
$wpdb->delete( $full, [ $col => $uid ], [ '%d' ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Threads/Posts auf user_id=0 setzen (Inhalt bleibt)
|
||||||
|
$wpdb->update( "{$wpdb->prefix}forum_threads", [ 'user_id' => 0 ], [ 'user_id' => $uid ], [ '%d' ], [ '%d' ] );
|
||||||
|
$wpdb->update( "{$wpdb->prefix}forum_posts", [ 'user_id' => 0 ], [ 'user_id' => $uid ], [ '%d' ], [ '%d' ] );
|
||||||
|
// Nutzer-Datensatz hart loeschen
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_users", [ 'id' => $uid ], [ '%d' ] );
|
||||||
|
echo "<div class='notice notice-success is-dismissible'><p>🗑 Nutzer <strong>{$name}</strong> dauerhaft geloescht. Threads/Posts anonym gesetzt.</p></div>";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// DSGVO Art. 17 Anonymisierung (Standard)
|
||||||
|
$ok = WBF_DB::delete_user_gdpr( $uid );
|
||||||
|
if ( $ok ) {
|
||||||
|
echo "<div class='notice notice-success is-dismissible'><p>✅ Nutzer <strong>{$name}</strong> anonymisiert (DSGVO Art. 17). Threads und Beitraege bleiben erhalten.</p></div>";
|
||||||
|
} else {
|
||||||
|
echo "<div class='notice notice-error is-dismissible'><p>❌ Anonymisierung fehlgeschlagen.</p></div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete_transient( 'wbf_flood_' . $uid );
|
||||||
|
delete_transient( 'wbf_flood_ts_' . $uid );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Profil bearbeiten ─────────────────────────────────────────────────────
|
// ── Profil bearbeiten ─────────────────────────────────────────────────────
|
||||||
if ( isset( $_POST['wbf_edit_user'] ) && check_admin_referer( 'wbf_edit_user_nonce' ) ) {
|
if ( isset( $_POST['wbf_edit_user'] ) && check_admin_referer( 'wbf_edit_user_nonce' ) ) {
|
||||||
$uid = (int) $_POST['user_id'];
|
$uid = (int) $_POST['user_id'];
|
||||||
@@ -1238,6 +1314,35 @@ function wbf_admin_members() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Admin: einzelnen Ignore-Eintrag entfernen ─────────────────────────────
|
||||||
|
if ( isset( $_POST['wbf_admin_remove_ignore'] ) && check_admin_referer( 'wbf_admin_ignore_nonce' ) ) {
|
||||||
|
if ( current_user_can('manage_options') ) {
|
||||||
|
$uid = (int) ( $_POST['user_id'] ?? 0 );
|
||||||
|
$ignored_id = (int) ( $_POST['ignored_id'] ?? 0 );
|
||||||
|
if ( $uid && $ignored_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
$wpdb->delete(
|
||||||
|
"{$wpdb->prefix}forum_ignore_list",
|
||||||
|
[ 'user_id' => $uid, 'ignored_id' => $ignored_id ],
|
||||||
|
[ '%d', '%d' ]
|
||||||
|
);
|
||||||
|
echo '<div class="notice notice-success is-dismissible"><p>Ignore-Eintrag entfernt.</p></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Admin: gesamte Ignore-Liste eines Users leeren ────────────────────────
|
||||||
|
if ( isset( $_POST['wbf_admin_clear_ignores'] ) && check_admin_referer( 'wbf_admin_ignore_nonce' ) ) {
|
||||||
|
if ( current_user_can('manage_options') ) {
|
||||||
|
$uid = (int) ( $_POST['user_id'] ?? 0 );
|
||||||
|
if ( $uid ) {
|
||||||
|
global $wpdb;
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_ignore_list", [ 'user_id' => $uid ], [ '%d' ] );
|
||||||
|
echo '<div class="notice notice-success is-dismissible"><p>Ignore-Liste vollständig geleert.</p></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$members = WBF_DB::get_all_users( 200 );
|
$members = WBF_DB::get_all_users( 200 );
|
||||||
?>
|
?>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -1350,6 +1455,11 @@ function wbf_admin_members() {
|
|||||||
onclick="var d=document.getElementById('wbf-edit-user-<?php echo (int)$m->id; ?>');d.style.display=d.style.display==='none'?'block':'none'">
|
onclick="var d=document.getElementById('wbf-edit-user-<?php echo (int)$m->id; ?>');d.style.display=d.style.display==='none'?'block':'none'">
|
||||||
Profil bearbeiten
|
Profil bearbeiten
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="button button-small"
|
||||||
|
style="color:#dc2626;border-color:#dc2626"
|
||||||
|
onclick="var d=document.getElementById('wbf-delete-user-<?php echo (int)$m->id; ?>');d.style.display=d.style.display==='none'?'block':'none'">
|
||||||
|
🗑️ Löschen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="wbf-ban-reason" style="display:<?php echo $m->role === 'banned' ? 'block' : 'none'; ?>;margin-top:3px">
|
<div class="wbf-ban-reason" style="display:<?php echo $m->role === 'banned' ? 'block' : 'none'; ?>;margin-top:3px">
|
||||||
<input type="text" name="ban_reason" value="<?php echo $ban_reason; ?>"
|
<input type="text" name="ban_reason" value="<?php echo $ban_reason; ?>"
|
||||||
@@ -1472,6 +1582,128 @@ function wbf_admin_members() {
|
|||||||
<button type="submit" name="wbf_edit_user" class="button button-primary button-small">Änderungen speichern</button>
|
<button type="submit" name="wbf_edit_user" class="button button-primary button-small">Änderungen speichern</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Ignore-Liste des Nutzers -->
|
||||||
|
<?php
|
||||||
|
$admin_ignore_list = WBF_DB::get_ignore_list( $m->id );
|
||||||
|
?>
|
||||||
|
<div style="margin-top:14px;border-top:1px solid #e0e0e0;padding-top:12px">
|
||||||
|
<strong style="font-size:12px;color:#555;text-transform:uppercase;letter-spacing:.04em">
|
||||||
|
<span class="dashicons dashicons-hidden" style="font-size:14px;width:14px;height:14px;vertical-align:-2px"></span>
|
||||||
|
Ignorierte Nutzer (<?php echo count($admin_ignore_list); ?>)
|
||||||
|
</strong>
|
||||||
|
<?php if ( empty($admin_ignore_list) ): ?>
|
||||||
|
<p style="font-size:12px;color:#999;margin:6px 0 0">Dieser Nutzer ignoriert niemanden.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table style="width:100%;font-size:12px;margin-top:8px;border-collapse:collapse">
|
||||||
|
<thead>
|
||||||
|
<tr style="background:#f0f0f0">
|
||||||
|
<th style="padding:4px 6px;text-align:left;font-weight:600">Nutzer</th>
|
||||||
|
<th style="padding:4px 6px;text-align:left;font-weight:600">Ignoriert seit</th>
|
||||||
|
<th style="padding:4px 6px;text-align:center;font-weight:600">Entfernen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ( $admin_ignore_list as $ign ): ?>
|
||||||
|
<tr style="border-bottom:1px solid #eee">
|
||||||
|
<td style="padding:4px 6px">
|
||||||
|
<strong><?php echo esc_html($ign->display_name); ?></strong>
|
||||||
|
<span style="color:#999"> @<?php echo esc_html($ign->username); ?></span>
|
||||||
|
</td>
|
||||||
|
<td style="padding:4px 6px;color:#666">
|
||||||
|
<?php echo esc_html(date_i18n('d.m.Y H:i', strtotime($ign->ignored_since))); ?>
|
||||||
|
</td>
|
||||||
|
<td style="padding:4px 6px;text-align:center">
|
||||||
|
<form method="post" style="display:inline">
|
||||||
|
<?php wp_nonce_field('wbf_admin_ignore_nonce'); ?>
|
||||||
|
<input type="hidden" name="user_id" value="<?php echo (int)$m->id; ?>">
|
||||||
|
<input type="hidden" name="ignored_id" value="<?php echo (int)$ign->id; ?>">
|
||||||
|
<button type="submit" name="wbf_admin_remove_ignore"
|
||||||
|
class="button button-small"
|
||||||
|
style="color:#c00;border-color:#c00"
|
||||||
|
onclick="return confirm('Ignore-Eintrag für <?php echo esc_js($ign->display_name); ?> entfernen?')">
|
||||||
|
<span class="dashicons dashicons-no" style="font-size:13px;width:13px;height:13px;vertical-align:-2px"></span>
|
||||||
|
Entfernen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<form method="post" style="margin-top:8px">
|
||||||
|
<?php wp_nonce_field('wbf_admin_ignore_nonce'); ?>
|
||||||
|
<input type="hidden" name="user_id" value="<?php echo (int)$m->id; ?>">
|
||||||
|
<button type="submit" name="wbf_admin_clear_ignores"
|
||||||
|
class="button button-small"
|
||||||
|
style="color:#c00;border-color:#c00"
|
||||||
|
onclick="return confirm('Gesamte Ignore-Liste von <?php echo esc_js($m->display_name); ?> leeren?')">
|
||||||
|
<span class="dashicons dashicons-trash" style="font-size:13px;width:13px;height:13px;vertical-align:-2px"></span>
|
||||||
|
Alle Einträge löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div><!-- /#wbf-edit-user -->
|
||||||
|
|
||||||
|
<!-- Löschen-Dialog -->
|
||||||
|
<div id="wbf-delete-user-<?php echo (int)$m->id; ?>"
|
||||||
|
style="display:none;margin-top:8px;padding:14px 16px;background:#fff5f5;border:1px solid #fca5a5;border-radius:6px;max-width:480px">
|
||||||
|
<p style="margin:0 0 10px;font-weight:700;color:#dc2626;font-size:.9rem">
|
||||||
|
🗑️ Nutzer löschen: <?php echo esc_html($m->display_name); ?>
|
||||||
|
</p>
|
||||||
|
<p style="font-size:.82rem;color:#6b7280;margin:0 0 12px">
|
||||||
|
Wähle wie der Account behandelt werden soll:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Option A: DSGVO Anonymisierung -->
|
||||||
|
<form method="post" style="margin-bottom:8px">
|
||||||
|
<?php wp_nonce_field( 'wbf_delete_member_nonce' ); ?>
|
||||||
|
<input type="hidden" name="user_id" value="<?php echo (int)$m->id; ?>">
|
||||||
|
<input type="hidden" name="delete_mode" value="anonymize">
|
||||||
|
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:6px;padding:10px 12px;margin-bottom:6px">
|
||||||
|
<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer">
|
||||||
|
<span>
|
||||||
|
<strong style="font-size:.83rem;color:#374151">📋 DSGVO Anonymisieren</strong>
|
||||||
|
<span style="display:block;font-size:.77rem;color:#9ca3af;margin-top:2px">
|
||||||
|
Account wird anonymisiert, Threads & Beiträge bleiben unter „Gelöschter Nutzer" erhalten.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="wbf_delete_member" class="button"
|
||||||
|
style="font-size:.8rem;height:28px;line-height:28px;background:#fff8f0;border-color:#f97316;color:#c2410c"
|
||||||
|
onclick="return confirm('Nutzer <?php echo esc_js($m->display_name); ?> anonymisieren?\n\nDer Account wird nach DSGVO Art. 17 anonymisiert. Alle Inhalte bleiben anonym erhalten.')">
|
||||||
|
📋 Anonymisieren
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Option B: Hard Delete -->
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field( 'wbf_delete_member_nonce' ); ?>
|
||||||
|
<input type="hidden" name="user_id" value="<?php echo (int)$m->id; ?>">
|
||||||
|
<input type="hidden" name="delete_mode" value="hard">
|
||||||
|
<div style="background:#fff;border:1px solid #fca5a5;border-radius:6px;padding:10px 12px;margin-bottom:6px">
|
||||||
|
<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer">
|
||||||
|
<span>
|
||||||
|
<strong style="font-size:.83rem;color:#dc2626">⚠️ Dauerhaft löschen</strong>
|
||||||
|
<span style="display:block;font-size:.77rem;color:#9ca3af;margin-top:2px">
|
||||||
|
Account + alle nutzerbezogenen Daten werden unwiderruflich gelöscht. Threads/Posts bleiben anonym erhalten.
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="wbf_delete_member" class="button"
|
||||||
|
style="font-size:.8rem;height:28px;line-height:28px;background:#fef2f2;border-color:#dc2626;color:#dc2626"
|
||||||
|
onclick="return confirm('⚠️ ACHTUNG: Nutzer <?php echo esc_js($m->display_name); ?> DAUERHAFT löschen?\n\nDieser Vorgang kann NICHT rückgängig gemacht werden!\n\nAlle persönlichen Daten, Nachrichten und Einstellungen werden unwiderruflich entfernt.')">
|
||||||
|
🗑️ Dauerhaft löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<button type="button" class="button" style="margin-top:8px;font-size:.78rem"
|
||||||
|
onclick="document.getElementById('wbf-delete-user-<?php echo (int)$m->id; ?>').style.display='none'">
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
@@ -2353,6 +2585,11 @@ function wbf_admin_export() {
|
|||||||
update_option('wbf_profile_fields', $data['profile_fields']);
|
update_option('wbf_profile_fields', $data['profile_fields']);
|
||||||
$imported[] = 'Profilfelder-Definitionen (' . count($data['profile_fields']) . ')';
|
$imported[] = 'Profilfelder-Definitionen (' . count($data['profile_fields']) . ')';
|
||||||
}
|
}
|
||||||
|
// Reaktionen-Konfiguration
|
||||||
|
if ( isset($data['reactions_cfg']) && is_array($data['reactions_cfg']) ) {
|
||||||
|
update_option('wbf_reactions', $data['reactions_cfg']);
|
||||||
|
$imported[] = 'Reaktionen-Konfiguration';
|
||||||
|
}
|
||||||
|
|
||||||
// ── Rollen ────────────────────────────────────────────────
|
// ── Rollen ────────────────────────────────────────────────
|
||||||
if ( ! empty($data['roles']) ) {
|
if ( ! empty($data['roles']) ) {
|
||||||
@@ -2378,8 +2615,21 @@ function wbf_admin_export() {
|
|||||||
if ( $existing === 0 || $force ) {
|
if ( $existing === 0 || $force ) {
|
||||||
if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_categories");
|
if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_categories");
|
||||||
foreach ($data['categories'] as $cat) {
|
foreach ($data['categories'] as $cat) {
|
||||||
unset($cat['id']);
|
// BUGFIX: Original-ID behalten! Threads zeigen direkt auf diese IDs.
|
||||||
$wpdb->insert("{$wpdb->prefix}forum_categories", $cat);
|
$wpdb->query( $wpdb->prepare(
|
||||||
|
"INSERT INTO {$wpdb->prefix}forum_categories
|
||||||
|
(id, parent_id, name, slug, description, icon, sort_order, thread_count, post_count, min_role, guest_visible)
|
||||||
|
VALUES (%d,%d,%s,%s,%s,%s,%d,%d,%d,%s,%d)
|
||||||
|
ON DUPLICATE KEY UPDATE parent_id=%d, name=%s, slug=%s, description=%s, icon=%s, sort_order=%d, thread_count=%d, post_count=%d, min_role=%s, guest_visible=%d",
|
||||||
|
(int)($cat['id']), (int)($cat['parent_id'] ?? 0), $cat['name'] ?? '', $cat['slug'] ?? '',
|
||||||
|
$cat['description'] ?? '', $cat['icon'] ?? 'fas fa-comments',
|
||||||
|
(int)($cat['sort_order'] ?? 0), (int)($cat['thread_count'] ?? 0),
|
||||||
|
(int)($cat['post_count'] ?? 0), $cat['min_role'] ?? 'member', (int)($cat['guest_visible'] ?? 1),
|
||||||
|
(int)($cat['parent_id'] ?? 0), $cat['name'] ?? '', $cat['slug'] ?? '',
|
||||||
|
$cat['description'] ?? '', $cat['icon'] ?? 'fas fa-comments',
|
||||||
|
(int)($cat['sort_order'] ?? 0), (int)($cat['thread_count'] ?? 0),
|
||||||
|
(int)($cat['post_count'] ?? 0), $cat['min_role'] ?? 'member', (int)($cat['guest_visible'] ?? 1)
|
||||||
|
) );
|
||||||
}
|
}
|
||||||
$imported[] = 'Kategorien (' . count($data['categories']) . ')';
|
$imported[] = 'Kategorien (' . count($data['categories']) . ')';
|
||||||
} else {
|
} else {
|
||||||
@@ -2439,11 +2689,17 @@ function wbf_admin_export() {
|
|||||||
if ( ! empty($data['threads']) ) {
|
if ( ! empty($data['threads']) ) {
|
||||||
$force = ! empty($_POST['import_force_threads']);
|
$force = ! empty($_POST['import_force_threads']);
|
||||||
if ($force) {
|
if ($force) {
|
||||||
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_posts");
|
// Alle abhängigen Tabellen mitbereinigen (verhindert verwaiste Einträge)
|
||||||
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_threads");
|
$wpdb->query("SET FOREIGN_KEY_CHECKS=0");
|
||||||
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_thread_tags");
|
foreach ([
|
||||||
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_tags");
|
'forum_posts', 'forum_threads', 'forum_thread_tags', 'forum_tags',
|
||||||
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_subscriptions");
|
'forum_subscriptions', 'forum_polls', 'forum_poll_votes',
|
||||||
|
'forum_bookmarks', 'forum_prefixes', 'forum_likes', 'forum_reactions',
|
||||||
|
'forum_notifications', 'forum_ignore_list',
|
||||||
|
] as $_tbl) {
|
||||||
|
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}$_tbl");
|
||||||
|
}
|
||||||
|
$wpdb->query("SET FOREIGN_KEY_CHECKS=1");
|
||||||
}
|
}
|
||||||
foreach ($data['threads'] ?? [] as $t) { $wpdb->insert("{$wpdb->prefix}forum_threads", $t); }
|
foreach ($data['threads'] ?? [] as $t) { $wpdb->insert("{$wpdb->prefix}forum_threads", $t); }
|
||||||
foreach ($data['posts'] ?? [] as $p) { $wpdb->insert("{$wpdb->prefix}forum_posts", $p); }
|
foreach ($data['posts'] ?? [] as $p) { $wpdb->insert("{$wpdb->prefix}forum_posts", $p); }
|
||||||
@@ -2478,6 +2734,61 @@ function wbf_admin_export() {
|
|||||||
$imported[] = 'Threads + Posts (' . count($data['threads']) . ' / ' . count($data['posts'] ?? []) . ')';
|
$imported[] = 'Threads + Posts (' . count($data['threads']) . ' / ' . count($data['posts'] ?? []) . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Thread-Präfixe ────────────────────────────────────────
|
||||||
|
// ── Thread-Präfixe ────────────────────────────────────────
|
||||||
|
if ( ! empty($data['prefixes']) ) {
|
||||||
|
$force = ! empty($_POST['import_force_prefixes']);
|
||||||
|
if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_prefixes");
|
||||||
|
$pc = 0;
|
||||||
|
foreach ($data['prefixes'] as $row) {
|
||||||
|
$wpdb->query( $wpdb->prepare(
|
||||||
|
"INSERT INTO {$wpdb->prefix}forum_prefixes (id, label, color, bg_color, sort_order)
|
||||||
|
VALUES (%d,%s,%s,%s,%d)
|
||||||
|
ON DUPLICATE KEY UPDATE label=%s, color=%s, bg_color=%s, sort_order=%d",
|
||||||
|
(int)$row['id'], $row['label'], $row['color'] ?? '#ffffff',
|
||||||
|
$row['bg_color'] ?? '#475569', (int)($row['sort_order'] ?? 0),
|
||||||
|
$row['label'], $row['color'] ?? '#ffffff',
|
||||||
|
$row['bg_color'] ?? '#475569', (int)($row['sort_order'] ?? 0)
|
||||||
|
) );
|
||||||
|
$pc++;
|
||||||
|
}
|
||||||
|
if ($pc) $imported[] = "Thread-Präfixe ($pc)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Umfragen + Abstimmungen ───────────────────────────────
|
||||||
|
if ( ! empty($data['polls']) ) {
|
||||||
|
$force = ! empty($_POST['import_force_polls']);
|
||||||
|
if ($force) {
|
||||||
|
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_poll_votes");
|
||||||
|
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_polls");
|
||||||
|
}
|
||||||
|
$pc = 0;
|
||||||
|
foreach ($data['polls'] as $row) {
|
||||||
|
$wpdb->query( $wpdb->prepare(
|
||||||
|
"INSERT INTO {$wpdb->prefix}forum_polls (id, thread_id, question, options, multi, ends_at, created_at)
|
||||||
|
VALUES (%d,%d,%s,%s,%d,%s,%s)
|
||||||
|
ON DUPLICATE KEY UPDATE thread_id=%d, question=%s, options=%s, multi=%d, ends_at=%s",
|
||||||
|
(int)$row['id'], (int)$row['thread_id'], $row['question'] ?? '',
|
||||||
|
$row['options'] ?? '[]', (int)($row['multi'] ?? 0),
|
||||||
|
$row['ends_at'] ?? null, $row['created_at'] ?? current_time('mysql'),
|
||||||
|
(int)$row['thread_id'], $row['question'] ?? '',
|
||||||
|
$row['options'] ?? '[]', (int)($row['multi'] ?? 0), $row['ends_at'] ?? null
|
||||||
|
) );
|
||||||
|
$pc++;
|
||||||
|
}
|
||||||
|
foreach ($data['poll_votes'] ?? [] as $row) { unset($row['id']); $wpdb->replace("{$wpdb->prefix}forum_poll_votes", $row); }
|
||||||
|
$imported[] = "Umfragen ($pc)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lesezeichen ───────────────────────────────────────────
|
||||||
|
if ( ! empty($data['bookmarks']) ) {
|
||||||
|
$force = ! empty($_POST['import_force_bookmarks']);
|
||||||
|
if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_bookmarks");
|
||||||
|
$bc = 0;
|
||||||
|
foreach ($data['bookmarks'] as $row) { unset($row['id']); $wpdb->replace("{$wpdb->prefix}forum_bookmarks", $row); $bc++; }
|
||||||
|
if ($bc) $imported[] = "Lesezeichen ($bc)";
|
||||||
|
}
|
||||||
|
|
||||||
// ── Likes, Reaktionen, Benachrichtigungen ─────────────────
|
// ── Likes, Reaktionen, Benachrichtigungen ─────────────────
|
||||||
if ( ! empty($data['likes']) ) {
|
if ( ! empty($data['likes']) ) {
|
||||||
$force = ! empty($_POST['import_force_threads']);
|
$force = ! empty($_POST['import_force_threads']);
|
||||||
@@ -2539,6 +2850,19 @@ function wbf_admin_export() {
|
|||||||
if ($count) $imported[] = "Einladungen ($count)";
|
if ($count) $imported[] = "Einladungen ($count)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Ignore-Liste ──────────────────────────────────────────
|
||||||
|
if ( ! empty($data['ignore_list']) ) {
|
||||||
|
$force = ! empty($_POST['import_force_ignore']);
|
||||||
|
if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_ignore_list");
|
||||||
|
$count = 0;
|
||||||
|
foreach ($data['ignore_list'] as $row) {
|
||||||
|
unset($row['id']);
|
||||||
|
$wpdb->replace("{$wpdb->prefix}forum_ignore_list", $row);
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
if ($count) $imported[] = "Ignore-Einträge ($count)";
|
||||||
|
}
|
||||||
|
|
||||||
if ( empty($imported) ) {
|
if ( empty($imported) ) {
|
||||||
$notice = ['warning', 'Nichts importiert — Datei enthielt keine gültigen Abschnitte.'];
|
$notice = ['warning', 'Nichts importiert — Datei enthielt keine gültigen Abschnitte.'];
|
||||||
} else {
|
} else {
|
||||||
@@ -2581,8 +2905,12 @@ function wbf_admin_export() {
|
|||||||
'categories' => ['📂', 'Kategorien', 'Alle Kategorien inkl. Hierarchie'],
|
'categories' => ['📂', 'Kategorien', 'Alle Kategorien inkl. Hierarchie'],
|
||||||
'users' => ['👥', 'Benutzer & Profilfelder', 'Accounts, Ban-Status, Profilfeld-Werte'],
|
'users' => ['👥', 'Benutzer & Profilfelder', 'Accounts, Ban-Status, Profilfeld-Werte'],
|
||||||
'threads' => ['💬', 'Threads, Posts & Abos', 'Alle Inhalte, Tags & Abonnements'],
|
'threads' => ['💬', 'Threads, Posts & Abos', 'Alle Inhalte, Tags & Abonnements'],
|
||||||
|
'polls' => ['📊', 'Umfragen', 'Alle Umfragen inkl. Abstimmungen'],
|
||||||
|
'bookmarks' => ['🔖', 'Lesezeichen', 'Alle gespeicherten Thread-Lesezeichen'],
|
||||||
|
'prefixes' => ['🏷️', 'Thread-Präfixe', 'Alle konfigurierten Präfix-Labels & Farben'],
|
||||||
'interactions' => ['❤️', 'Likes & Reaktionen', 'Likes, Reaktionen, Benachrichtigungen'],
|
'interactions' => ['❤️', 'Likes & Reaktionen', 'Likes, Reaktionen, Benachrichtigungen'],
|
||||||
'messages' => ['✉️', 'Privatnachrichten', 'Alle DM-Konversationen'],
|
'messages' => ['✉️', 'Privatnachrichten', 'Alle DM-Konversationen'],
|
||||||
|
'ignore_list' => ['🚫', 'Ignore-Liste', 'Alle Nutzer-Blockierungen'],
|
||||||
'reports' => ['🚩', 'Meldungen', 'Gemeldete Beiträge inkl. Status'],
|
'reports' => ['🚩', 'Meldungen', 'Gemeldete Beiträge inkl. Status'],
|
||||||
'invites' => ['📨', 'Einladungen', 'Alle Einladungscodes inkl. Nutzungsstand'],
|
'invites' => ['📨', 'Einladungen', 'Alle Einladungscodes inkl. Nutzungsstand'],
|
||||||
];
|
];
|
||||||
@@ -2644,6 +2972,10 @@ function wbf_admin_export() {
|
|||||||
'import_force_cats' => 'Kategorien überschreiben (löscht bestehende)',
|
'import_force_cats' => 'Kategorien überschreiben (löscht bestehende)',
|
||||||
'import_force_users' => 'Benutzer aktualisieren (bei gleichem Username) inkl. Profilfeld-Werte',
|
'import_force_users' => 'Benutzer aktualisieren (bei gleichem Username) inkl. Profilfeld-Werte',
|
||||||
'import_force_threads' => 'Threads, Posts, Likes, Reaktionen & Abonnements überschreiben',
|
'import_force_threads' => 'Threads, Posts, Likes, Reaktionen & Abonnements überschreiben',
|
||||||
|
'import_force_polls' => 'Umfragen & Abstimmungen überschreiben',
|
||||||
|
'import_force_bookmarks'=> 'Lesezeichen überschreiben',
|
||||||
|
'import_force_prefixes' => 'Thread-Präfixe überschreiben',
|
||||||
|
'import_force_ignore' => 'Ignore-Liste überschreiben',
|
||||||
'import_force_messages' => 'Privatnachrichten überschreiben',
|
'import_force_messages' => 'Privatnachrichten überschreiben',
|
||||||
'import_force_reports' => 'Meldungen überschreiben',
|
'import_force_reports' => 'Meldungen überschreiben',
|
||||||
'import_force_invites' => 'Einladungen überschreiben',
|
'import_force_invites' => 'Einladungen überschreiben',
|
||||||
@@ -2868,6 +3200,142 @@ function wbf_admin_profile_fields() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Deinstallations-Seite ─────────────────────────────────────────────────────
|
// ── Deinstallations-Seite ─────────────────────────────────────────────────────
|
||||||
|
// ── Update-Seite ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function wbf_admin_updates() {
|
||||||
|
if ( ! current_user_can('manage_options') ) return;
|
||||||
|
|
||||||
|
$latest = wbf_get_latest_release();
|
||||||
|
$update = wbf_update_available();
|
||||||
|
$cur_ver = WBF_VERSION;
|
||||||
|
$admin_url = admin_url('admin.php?page=wbf-updates');
|
||||||
|
|
||||||
|
// Manuelles Refresh
|
||||||
|
$refresh_url = wp_nonce_url( add_query_arg('wbf_refresh_update', '1', $admin_url), 'wbf_refresh_update' );
|
||||||
|
?>
|
||||||
|
<div class="wrap" style="max-width:860px">
|
||||||
|
<h1 style="display:flex;align-items:center;gap:10px">
|
||||||
|
<span style="font-size:1.4rem">🔔</span> WP Business Forum — Updates
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- ── Status-Karte ─────────────────────────────────── -->
|
||||||
|
<div style="margin-top:20px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.06)">
|
||||||
|
|
||||||
|
<?php if ( $update ): ?>
|
||||||
|
<!-- UPDATE VERFÜGBAR -->
|
||||||
|
<div style="background:linear-gradient(135deg,#fffbeb,#fef3c7);border-bottom:2px solid #fcd34d;padding:18px 24px;display:flex;align-items:center;gap:14px">
|
||||||
|
<span style="font-size:2.2rem">📦</span>
|
||||||
|
<div>
|
||||||
|
<strong style="font-size:1.1rem;color:#92400e">Update verfügbar!</strong>
|
||||||
|
<p style="margin:.2rem 0 0;color:#78350f">
|
||||||
|
Version <strong><?php echo esc_html($update['version']); ?></strong> wurde veröffentlicht.
|
||||||
|
Du verwendest aktuell <strong><?php echo esc_html($cur_ver); ?></strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="<?php echo esc_url($update['url']); ?>" target="_blank" rel="noopener"
|
||||||
|
class="button button-primary"
|
||||||
|
style="margin-left:auto;background:#f59e0b;border-color:#d97706;white-space:nowrap;font-size:.9rem;padding:6px 16px">
|
||||||
|
📥 Zum Download
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- AKTUELL -->
|
||||||
|
<div style="background:linear-gradient(135deg,#f0fdf4,#dcfce7);border-bottom:2px solid #86efac;padding:18px 24px;display:flex;align-items:center;gap:14px">
|
||||||
|
<span style="font-size:2.2rem">✅</span>
|
||||||
|
<div>
|
||||||
|
<strong style="font-size:1.1rem;color:#166534">Du verwendest die neueste Version</strong>
|
||||||
|
<p style="margin:.2rem 0 0;color:#15803d">Version <?php echo esc_html($cur_ver); ?> ist aktuell.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div style="padding:22px 24px">
|
||||||
|
|
||||||
|
<!-- ── Versionen-Tabelle ──────────────────────── -->
|
||||||
|
<table style="width:100%;border-collapse:collapse;margin-bottom:20px">
|
||||||
|
<tr style="border-bottom:1px solid #f3f4f6">
|
||||||
|
<td style="padding:10px 0;color:#6b7280;font-size:.875rem;width:220px">Installierte Version</td>
|
||||||
|
<td style="padding:10px 0;font-weight:700"><?php echo esc_html($cur_ver); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-bottom:1px solid #f3f4f6">
|
||||||
|
<td style="padding:10px 0;color:#6b7280;font-size:.875rem">Neueste Version</td>
|
||||||
|
<td style="padding:10px 0;font-weight:700">
|
||||||
|
<?php if ($latest && !empty($latest['version'])): ?>
|
||||||
|
<?php echo esc_html($latest['version']); ?>
|
||||||
|
<?php if ($update): ?>
|
||||||
|
<span style="margin-left:6px;background:#fef3c7;color:#92400e;border:1px solid #fcd34d;border-radius:12px;padding:2px 8px;font-size:.75rem;font-weight:600">NEU</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<span style="color:#9ca3af">Nicht abrufbar</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php if ($latest && !empty($latest['published'])): ?>
|
||||||
|
<tr style="border-bottom:1px solid #f3f4f6">
|
||||||
|
<td style="padding:10px 0;color:#6b7280;font-size:.875rem">Veröffentlicht am</td>
|
||||||
|
<td style="padding:10px 0"><?php echo esc_html(date_i18n('d.m.Y H:i', strtotime($latest['published']))); ?> Uhr</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
<tr style="border-bottom:1px solid #f3f4f6">
|
||||||
|
<td style="padding:10px 0;color:#6b7280;font-size:.875rem">Update-Quelle</td>
|
||||||
|
<td style="padding:10px 0">
|
||||||
|
<a href="<?php echo esc_url(WBF_RELEASES_PAGE); ?>" target="_blank" rel="noopener"
|
||||||
|
style="color:#2563eb;text-decoration:none">
|
||||||
|
<i class="fas fa-code-branch" style="margin-right:5px"></i>
|
||||||
|
git.viper.ipv64.net — WP-Business-Forum
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:10px 0;color:#6b7280;font-size:.875rem">Letzter Check</td>
|
||||||
|
<td style="padding:10px 0;display:flex;align-items:center;gap:12px">
|
||||||
|
<?php
|
||||||
|
$cached = get_transient(WBF_UPDATE_TRANSIENT);
|
||||||
|
echo ($cached !== false) ? '<span style="color:#059669">✔ Cache aktiv (12h)</span>' : '<span style="color:#9ca3af">—</span>';
|
||||||
|
?>
|
||||||
|
<a href="<?php echo esc_url($refresh_url); ?>"
|
||||||
|
style="color:#6b7280;font-size:.8rem;text-decoration:none;border:1px solid #e5e7eb;border-radius:5px;padding:3px 9px">
|
||||||
|
🔄 Jetzt prüfen
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php if ($latest && !empty($latest['body'])): ?>
|
||||||
|
<!-- ── Changelog ──────────────────────────────── -->
|
||||||
|
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px 18px">
|
||||||
|
<h3 style="margin:0 0 10px;font-size:.9rem;color:#374151;display:flex;align-items:center;gap:7px">
|
||||||
|
<span>📋</span> Release Notes — v<?php echo esc_html($latest['version']); ?>
|
||||||
|
</h3>
|
||||||
|
<pre style="white-space:pre-wrap;font-family:inherit;font-size:.82rem;color:#4b5563;margin:0;line-height:1.6;max-height:300px;overflow-y:auto"><?php echo esc_html(mb_substr($latest['body'], 0, 3000)); ?></pre>
|
||||||
|
<div style="margin-top:12px;padding-top:10px;border-top:1px solid #e2e8f0">
|
||||||
|
<a href="<?php echo esc_url($latest['url']); ?>" target="_blank" rel="noopener"
|
||||||
|
class="button" style="font-size:.82rem">
|
||||||
|
Vollständige Release-Seite öffnen →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- ── Update-Anleitung ───────────────────────── -->
|
||||||
|
<div style="margin-top:18px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;padding:14px 16px">
|
||||||
|
<strong style="font-size:.85rem;color:#1e40af">📖 Update-Anleitung</strong>
|
||||||
|
<ol style="margin:.6rem 0 0 1.2rem;padding:0;font-size:.82rem;color:#1e3a8a;line-height:1.8">
|
||||||
|
<li>Lade die neue <code>.zip</code> von der <a href="<?php echo esc_url(WBF_RELEASES_PAGE); ?>" target="_blank" rel="noopener" style="color:#2563eb">Release-Seite</a> herunter.</li>
|
||||||
|
<li>Erstelle vorher ein Backup über <a href="<?php echo esc_url(admin_url('admin.php?page=wbf-export')); ?>" style="color:#2563eb">Export / Import</a>.</li>
|
||||||
|
<li>Lade die neue Version per FTP/SFTP hoch und überschreibe die alten Dateien — oder nutze den WP-Plugins-Bereich (Plugin deaktivieren → löschen → neu hochladen).</li>
|
||||||
|
<li>Aktiviere das Plugin. Das DB-Schema wird automatisch aktualisiert.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Deinstallations-Seite ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
function wbf_admin_uninstall() {
|
function wbf_admin_uninstall() {
|
||||||
if ( ! current_user_can('manage_options') ) return;
|
if ( ! current_user_can('manage_options') ) return;
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ if ( ! function_exists('wbf_get_settings') ) {
|
|||||||
'rules_accept_required' => '1',
|
'rules_accept_required' => '1',
|
||||||
'rules_title' => 'Forum-Regeln & Nutzungsbedingungen',
|
'rules_title' => 'Forum-Regeln & Nutzungsbedingungen',
|
||||||
'rules_content' => "**1. Respektvoller Umgang**\nBehandle alle Mitglieder freundlich und respektvoll. Beleidigungen, Mobbing und Diskriminierung sind nicht toleriert.\n\n**2. Keine Spam-Inhalte**\nWerbung, Spam und irrelevante Links sind verboten.\n\n**3. Keine illegalen Inhalte**\nJegliche Inhalte, die gegen geltendes Recht verstoßen, sind streng verboten.\n\n**4. Themenrelevanz**\nBeiträge sollten zur jeweiligen Kategorie passen.\n\n**5. Urheberrecht**\nVeröffentliche keine Inhalte, an denen du keine Rechte besitzt.\n\n**6. Datenschutz**\nTeile keine persönlichen Daten anderer Personen ohne deren Zustimmung.\n\n**7. Moderations-Entscheidungen**\nEntscheidungen der Moderatoren sind zu respektieren. Bei Fragen wende dich direkt ans Team.\n\nVerstöße können zur Verwarnung oder dauerhaften Sperrung führen.",
|
'rules_content' => "**1. Respektvoller Umgang**\nBehandle alle Mitglieder freundlich und respektvoll. Beleidigungen, Mobbing und Diskriminierung sind nicht toleriert.\n\n**2. Keine Spam-Inhalte**\nWerbung, Spam und irrelevante Links sind verboten.\n\n**3. Keine illegalen Inhalte**\nJegliche Inhalte, die gegen geltendes Recht verstoßen, sind streng verboten.\n\n**4. Themenrelevanz**\nBeiträge sollten zur jeweiligen Kategorie passen.\n\n**5. Urheberrecht**\nVeröffentliche keine Inhalte, an denen du keine Rechte besitzt.\n\n**6. Datenschutz**\nTeile keine persönlichen Daten anderer Personen ohne deren Zustimmung.\n\n**7. Moderations-Entscheidungen**\nEntscheidungen der Moderatoren sind zu respektieren. Bei Fragen wende dich direkt ans Team.\n\nVerstöße können zur Verwarnung oder dauerhaften Sperrung führen.",
|
||||||
|
// Ignore/Block-System: Rollen die nicht geblockt werden können (kommagetrennte Schlüssel)
|
||||||
|
'ignore_blocked_roles' => 'superadmin,admin,moderator',
|
||||||
];
|
];
|
||||||
|
|
||||||
$saved = get_option( 'wbf_settings', [] );
|
$saved = get_option( 'wbf_settings', [] );
|
||||||
@@ -63,6 +65,38 @@ if ( ! function_exists('wbf_get_settings') ) {
|
|||||||
|
|
||||||
// ── Admin-Seite ───────────────────────────────────────────────────────────────
|
// ── Admin-Seite ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt ein Array der Rollen-Keys zurück die nicht geblockt/ignoriert werden können.
|
||||||
|
* Superadmin ist immer enthalten — unabhängig von der Einstellung.
|
||||||
|
*
|
||||||
|
* @return string[] z.B. ['superadmin', 'admin', 'moderator']
|
||||||
|
*/
|
||||||
|
if ( ! function_exists('wbf_get_ignore_blocked_roles') ) {
|
||||||
|
function wbf_get_ignore_blocked_roles() {
|
||||||
|
$raw = wbf_get_settings()['ignore_blocked_roles'] ?? 'superadmin,admin,moderator';
|
||||||
|
$keys = array_filter( array_map( 'trim', explode( ',', $raw ) ) );
|
||||||
|
// superadmin immer schützen
|
||||||
|
if ( ! in_array('superadmin', $keys, true) ) {
|
||||||
|
$keys[] = 'superadmin';
|
||||||
|
}
|
||||||
|
return array_values( $keys );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft ob ein User ignoriert/geblockt werden darf.
|
||||||
|
*
|
||||||
|
* @param object $target Forum-User-Objekt
|
||||||
|
* @return bool true = darf ignoriert werden, false = nicht erlaubt
|
||||||
|
*/
|
||||||
|
if ( ! function_exists('wbf_can_be_ignored') ) {
|
||||||
|
function wbf_can_be_ignored( $target ) {
|
||||||
|
if ( ! $target ) return false;
|
||||||
|
$blocked_roles = wbf_get_ignore_blocked_roles();
|
||||||
|
return ! in_array( $target->role, $blocked_roles, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! function_exists('wbf_admin_settings') ) {
|
if ( ! function_exists('wbf_admin_settings') ) {
|
||||||
function wbf_admin_settings() {
|
function wbf_admin_settings() {
|
||||||
|
|
||||||
@@ -96,6 +130,18 @@ function wbf_admin_settings() {
|
|||||||
// rules_content separat (nicht in $fields, da textarea mit eigener Behandlung)
|
// rules_content separat (nicht in $fields, da textarea mit eigener Behandlung)
|
||||||
$settings['rules_content'] = sanitize_textarea_field( $_POST['rules_content'] ?? '' );
|
$settings['rules_content'] = sanitize_textarea_field( $_POST['rules_content'] ?? '' );
|
||||||
|
|
||||||
|
// ignore_blocked_roles: kommagetrennte Liste der gewählten Rollen-Keys
|
||||||
|
$all_role_keys = array_keys( WBF_Roles::get_all() );
|
||||||
|
$checked_roles = array_intersect(
|
||||||
|
array_map( 'sanitize_key', (array)( $_POST['ignore_blocked_roles'] ?? [] ) ),
|
||||||
|
$all_role_keys
|
||||||
|
);
|
||||||
|
// superadmin ist immer blockiert — kann nicht entfernt werden
|
||||||
|
if ( ! in_array('superadmin', $checked_roles, true) ) {
|
||||||
|
$checked_roles[] = 'superadmin';
|
||||||
|
}
|
||||||
|
$settings['ignore_blocked_roles'] = implode( ',', $checked_roles );
|
||||||
|
|
||||||
update_option( 'wbf_settings', $settings );
|
update_option( 'wbf_settings', $settings );
|
||||||
echo '<div class="notice notice-success is-dismissible"><p>✅ Einstellungen gespeichert!</p></div>';
|
echo '<div class="notice notice-success is-dismissible"><p>✅ Einstellungen gespeichert!</p></div>';
|
||||||
}
|
}
|
||||||
@@ -401,6 +447,51 @@ function wbf_admin_settings() {
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<!-- ── Ignore / Block-System ────────────────────────── -->
|
||||||
|
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
|
||||||
|
🚫 Ignore / Block-System
|
||||||
|
</h2>
|
||||||
|
<table class="form-table" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><label>Nicht blockierbare Rollen</label></th>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$blocked_roles = array_filter( array_map('trim', explode(',', $s['ignore_blocked_roles'] ?? 'superadmin,admin,moderator')) );
|
||||||
|
$all_roles = WBF_Roles::get_sorted();
|
||||||
|
foreach ( $all_roles as $key => $role ):
|
||||||
|
$is_superadmin = ($key === 'superadmin');
|
||||||
|
$is_checked = in_array($key, $blocked_roles, true);
|
||||||
|
$rc = esc_attr($role['color']);
|
||||||
|
$rb = esc_attr($role['bg_color']);
|
||||||
|
?>
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;margin-bottom:7px;cursor:<?php echo $is_superadmin?'not-allowed':'pointer'; ?>">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="ignore_blocked_roles[]"
|
||||||
|
value="<?php echo esc_attr($key); ?>"
|
||||||
|
<?php checked($is_checked, true); ?>
|
||||||
|
<?php echo $is_superadmin ? 'disabled' : ''; ?>
|
||||||
|
style="width:16px;height:16px;accent-color:<?php echo $rc; ?>">
|
||||||
|
<?php if ($is_superadmin): ?>
|
||||||
|
<!-- superadmin immer als hidden mitschicken da disabled nicht übermittelt wird -->
|
||||||
|
<input type="hidden" name="ignore_blocked_roles[]" value="superadmin">
|
||||||
|
<?php endif; ?>
|
||||||
|
<span style="display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:20px;font-size:.78rem;font-weight:700;color:<?php echo $rc; ?>;background:<?php echo $rb; ?>;border:1px solid <?php echo $rc; ?>">
|
||||||
|
<i class="<?php echo esc_attr($role['icon'] ?? 'fas fa-user'); ?>"></i>
|
||||||
|
<?php echo esc_html($role['label']); ?>
|
||||||
|
</span>
|
||||||
|
<?php if ($is_superadmin): ?>
|
||||||
|
<span style="font-size:.72rem;color:#999">(immer geschützt)</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<p class="description" style="margin-top:8px">
|
||||||
|
Nutzer mit diesen Rollen können von anderen Mitgliedern <strong>nicht</strong> geblockt oder ignoriert werden.
|
||||||
|
Superadmin ist permanent geschützt und kann nicht abgewählt werden.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<?php submit_button(
|
<?php submit_button(
|
||||||
'💾 Einstellungen speichern',
|
'💾 Einstellungen speichern',
|
||||||
'primary',
|
'primary',
|
||||||
|
|||||||
@@ -148,15 +148,17 @@
|
|||||||
border-color: var(--c-border-d);
|
border-color: var(--c-border-d);
|
||||||
}
|
}
|
||||||
.wbf-btn:hover { background: var(--c-surface); color: var(--c-text); border-color: var(--c-primary); }
|
.wbf-btn:hover { background: var(--c-surface); color: var(--c-text); border-color: var(--c-primary); }
|
||||||
.wbf-btn--primary {
|
.wbf-btn--primary,
|
||||||
background: var(--c-primary); color: #fff;
|
a.wbf-btn--primary {
|
||||||
|
background: var(--c-primary); color: #fff !important;
|
||||||
border-color: var(--c-primary);
|
border-color: var(--c-primary);
|
||||||
box-shadow: 0 0 12px rgba(0,180,216,.3);
|
box-shadow: 0 0 12px rgba(0,180,216,.3);
|
||||||
}
|
}
|
||||||
.wbf-btn--primary:hover {
|
.wbf-btn--primary:hover,
|
||||||
|
a.wbf-btn--primary:hover {
|
||||||
background: var(--c-primary-d); border-color: var(--c-primary-d);
|
background: var(--c-primary-d); border-color: var(--c-primary-d);
|
||||||
box-shadow: 0 0 20px rgba(0,180,216,.45);
|
box-shadow: 0 0 20px rgba(0,180,216,.45);
|
||||||
color: #fff;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
.wbf-btn--outline {
|
.wbf-btn--outline {
|
||||||
background: transparent; border-color: rgba(255,255,255,.18);
|
background: transparent; border-color: rgba(255,255,255,.18);
|
||||||
@@ -2831,4 +2833,286 @@ select.wbf-cf-input option { background: var(--c-surface2); color: var(--c-text)
|
|||||||
border-color: #fbbf24;
|
border-color: #fbbf24;
|
||||||
color: #fbbf24;
|
color: #fbbf24;
|
||||||
background: rgba(251,191,36,.12);
|
background: rgba(251,191,36,.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Ignore / Block ────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* Eingeklappter Post-Wrapper */
|
||||||
|
.wbf-post--ignored {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(148,163,184,.15);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info-Bar die anstelle des Posts erscheint */
|
||||||
|
.wbf-ignored-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .75rem;
|
||||||
|
padding: .6rem 1rem;
|
||||||
|
background: rgba(71,85,105,.18);
|
||||||
|
color: var(--c-muted);
|
||||||
|
font-size: .8rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.wbf-ignored-bar span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .4rem;
|
||||||
|
}
|
||||||
|
.wbf-ignored-bar strong {
|
||||||
|
color: var(--c-text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "Trotzdem anzeigen"-Button in der ignored-bar */
|
||||||
|
.wbf-show-ignored-btn {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid rgba(148,163,184,.3);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--c-muted);
|
||||||
|
font-size: .75rem;
|
||||||
|
padding: 2px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.wbf-show-ignored-btn:hover {
|
||||||
|
border-color: var(--c-primary);
|
||||||
|
color: var(--c-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore-Button in Post-Footer */
|
||||||
|
.wbf-ignore-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--c-muted);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: .82rem;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .3rem;
|
||||||
|
}
|
||||||
|
.wbf-ignore-btn:hover {
|
||||||
|
color: #f97316;
|
||||||
|
background: rgba(249,115,22,.08);
|
||||||
|
}
|
||||||
|
/* Profil-Button-Variante (mit Border via wbf-btn--sm) */
|
||||||
|
.wbf-btn.wbf-ignore-btn {
|
||||||
|
border: 1.5px solid rgba(148,163,184,.3);
|
||||||
|
padding: .35rem .75rem;
|
||||||
|
}
|
||||||
|
.wbf-btn.wbf-ignore-btn:hover,
|
||||||
|
.wbf-btn.wbf-ignore-btn[data-ignored="1"] {
|
||||||
|
border-color: #f97316;
|
||||||
|
color: #f97316;
|
||||||
|
background: rgba(249,115,22,.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignorierte-Nutzer-Liste im Profil */
|
||||||
|
.wbf-ignore-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
.wbf-ignore-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .75rem;
|
||||||
|
padding: .6rem .75rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: rgba(255,255,255,.03);
|
||||||
|
border: 1px solid var(--c-border);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.wbf-ignore-item:hover {
|
||||||
|
background: rgba(255,255,255,.05);
|
||||||
|
}
|
||||||
|
.wbf-ignore-item__avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.wbf-ignore-item__info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .15rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.wbf-ignore-item__name {
|
||||||
|
font-size: .88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--c-text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.wbf-ignore-item__name:hover {
|
||||||
|
color: var(--c-primary);
|
||||||
|
}
|
||||||
|
.wbf-ignore-item__since {
|
||||||
|
font-size: .73rem;
|
||||||
|
color: var(--c-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Profil-Tabs ─────────────────────────────────────────────────────────── */
|
||||||
|
.wbf-profile-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-bottom: 2px solid var(--c-border);
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.wbf-profile-tabs::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
.wbf-profile-tab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .45rem;
|
||||||
|
padding: .7rem 1.1rem;
|
||||||
|
font-size: .88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--c-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -2px;
|
||||||
|
transition: color .15s, border-color .15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
letter-spacing: .01em;
|
||||||
|
}
|
||||||
|
.wbf-profile-tab:hover {
|
||||||
|
color: var(--c-text);
|
||||||
|
border-bottom-color: rgba(255,255,255,.2);
|
||||||
|
}
|
||||||
|
.wbf-profile-tab.active {
|
||||||
|
color: var(--c-primary);
|
||||||
|
border-bottom-color: var(--c-primary);
|
||||||
|
}
|
||||||
|
.wbf-profile-tab i {
|
||||||
|
font-size: .8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.wbf-profile-tab {
|
||||||
|
padding: .6rem .75rem;
|
||||||
|
font-size: .82rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Notification Preferences ────────────────────────────────────────────── */
|
||||||
|
.wbf-notif-pref-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
.wbf-notif-pref {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: .75rem 1rem;
|
||||||
|
background: rgba(255,255,255,.03);
|
||||||
|
border: 1px solid var(--c-border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.wbf-notif-pref:hover {
|
||||||
|
background: rgba(255,255,255,.05);
|
||||||
|
}
|
||||||
|
.wbf-notif-pref__info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .2rem;
|
||||||
|
}
|
||||||
|
.wbf-notif-pref__info span {
|
||||||
|
font-size: .88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--c-text);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .4rem;
|
||||||
|
}
|
||||||
|
.wbf-notif-pref__info span i {
|
||||||
|
color: var(--c-primary);
|
||||||
|
font-size: .78rem;
|
||||||
|
}
|
||||||
|
.wbf-notif-pref__info small {
|
||||||
|
font-size: .75rem;
|
||||||
|
color: var(--c-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Toggle-Switch ───────────────────────────────────────────────────────── */
|
||||||
|
.wbf-toggle {
|
||||||
|
position: relative;
|
||||||
|
width: 42px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(148,163,184,.25);
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: background .2s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid rgba(148,163,184,.2);
|
||||||
|
}
|
||||||
|
.wbf-toggle--on {
|
||||||
|
background: var(--c-primary);
|
||||||
|
border-color: var(--c-primary);
|
||||||
|
}
|
||||||
|
.wbf-toggle__knob {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: transform .2s;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,.3);
|
||||||
|
}
|
||||||
|
.wbf-toggle--on .wbf-toggle__knob {
|
||||||
|
transform: translateX(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Profil-Sidebar: Online-Status & Zuletzt aktiv ──────────────────────── */
|
||||||
|
.wbf-profile-online-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .35rem;
|
||||||
|
font-size: .75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #22c55e;
|
||||||
|
margin-top: .25rem;
|
||||||
|
letter-spacing: .02em;
|
||||||
|
}
|
||||||
|
.wbf-profile-online-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #22c55e;
|
||||||
|
box-shadow: 0 0 6px #22c55e;
|
||||||
|
animation: wbf-pulse-green 2s infinite;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
@keyframes wbf-pulse-green {
|
||||||
|
0%, 100% { opacity: 1; box-shadow: 0 0 6px #22c55e; }
|
||||||
|
50% { opacity: .7; box-shadow: 0 0 10px #22c55e; }
|
||||||
|
}
|
||||||
|
.wbf-profile-lastseen {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .3rem;
|
||||||
|
font-size: .73rem;
|
||||||
|
color: var(--c-muted);
|
||||||
|
margin-top: .25rem;
|
||||||
|
}
|
||||||
|
.wbf-profile-lastseen i {
|
||||||
|
font-size: .68rem;
|
||||||
|
opacity: .7;
|
||||||
}
|
}
|
||||||
@@ -1944,6 +1944,55 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* ── E-Mail-Adresse ändern ──────────────────────────────────────────── */
|
||||||
|
$(document).on('click', '#wbfSaveEmail', function () {
|
||||||
|
var $btn = $(this);
|
||||||
|
var email = $('#wbfNewEmail').val().trim();
|
||||||
|
var password = $('#wbfEmailPassword').val();
|
||||||
|
var $msg = $('#wbfEmailMsg');
|
||||||
|
if (!email) { $msg.removeClass('wbf-ok').addClass('wbf-err').text('Bitte E-Mail eingeben.'); return; }
|
||||||
|
if (!password) { $msg.removeClass('wbf-ok').addClass('wbf-err').text('Bitte Passwort eingeben.'); return; }
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
wbfPost('wbf_change_email', { new_email: email, password: password }, function (d) {
|
||||||
|
$msg.removeClass('wbf-err').addClass('wbf-ok').text(d.message || 'E-Mail geaendert.');
|
||||||
|
$('#wbfNewEmail').val('');
|
||||||
|
$('#wbfEmailPassword').val('');
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
}, function (d) {
|
||||||
|
$msg.removeClass('wbf-ok').addClass('wbf-err').text(d.message || 'Fehler.');
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Toggle-Switch (Notification Prefs) ─────────────────────────────── */
|
||||||
|
$(document).on('click', '.wbf-toggle', function () {
|
||||||
|
var $t = $(this);
|
||||||
|
var on = String($t.data('state')) === '1';
|
||||||
|
var val = on ? '0' : '1';
|
||||||
|
$t.data('state', val).attr('data-state', val);
|
||||||
|
if (val === '1') { $t.addClass('wbf-toggle--on'); }
|
||||||
|
else { $t.removeClass('wbf-toggle--on'); }
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Benachrichtigungs-Einstellungen speichern ───────────────────────── */
|
||||||
|
$(document).on('click', '#wbfSaveNotifPrefs', function () {
|
||||||
|
var $btn = $(this);
|
||||||
|
var $msg = $('#wbfNotifPrefsMsg');
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
wbfPost('wbf_save_notification_prefs', {
|
||||||
|
notify_reply: String($('#wbfNotifReply').data('state')) === '1' ? '1' : '0',
|
||||||
|
notify_mention: String($('#wbfNotifMention').data('state')) === '1' ? '1' : '0',
|
||||||
|
notify_message: String($('#wbfNotifMessage').data('state')) === '1' ? '1' : '0'
|
||||||
|
}, function (d) {
|
||||||
|
$msg.removeClass('wbf-err').addClass('wbf-ok').text(d.message || 'Gespeichert!');
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
}, function (d) {
|
||||||
|
$msg.removeClass('wbf-ok').addClass('wbf-err').text(d.message || 'Fehler.');
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/* ── Lesezeichen ────────────────────────────────────────────────────── */
|
/* ── Lesezeichen ────────────────────────────────────────────────────── */
|
||||||
$(document).on('click', '.wbf-bookmark-btn', function () {
|
$(document).on('click', '.wbf-bookmark-btn', function () {
|
||||||
var $btn = $(this);
|
var $btn = $(this);
|
||||||
@@ -1959,4 +2008,110 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ── Nutzer ignorieren / Ignorierung aufheben ────────────────────────── */
|
||||||
|
$(document).on('click', '.wbf-ignore-btn', function () {
|
||||||
|
var $btn = $(this);
|
||||||
|
var ignoredId = parseInt($btn.data('id'), 10);
|
||||||
|
var name = $btn.data('name') || 'diesen Nutzer';
|
||||||
|
var isIgnored = String($btn.data('ignored')) === '1';
|
||||||
|
|
||||||
|
// Bestätigung nur beim Ignorieren, nicht beim Entblocken
|
||||||
|
if (!isIgnored) {
|
||||||
|
if (!confirm(name + ' ignorieren?\n\nDessen Beiträge werden in Threads ausgeblendet und DMs werden blockiert.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
|
||||||
|
wbfPost('wbf_toggle_ignore', { ignored_id: ignoredId }, function (d) {
|
||||||
|
var nowIgnored = d.ignored;
|
||||||
|
|
||||||
|
// Alle Buttons mit dieser User-ID auf der Seite aktualisieren
|
||||||
|
$('.wbf-ignore-btn[data-id="' + ignoredId + '"]').each(function () {
|
||||||
|
var $b = $(this);
|
||||||
|
$b.data('ignored', nowIgnored ? '1' : '0');
|
||||||
|
$b.attr('data-ignored', nowIgnored ? '1' : '0');
|
||||||
|
|
||||||
|
// Icon + Label aktualisieren
|
||||||
|
$b.find('i').attr('class', 'fas fa-' + (nowIgnored ? 'eye' : 'eye-slash'));
|
||||||
|
|
||||||
|
// Button-Variante (Post-Footer, klein ohne wbf-btn)
|
||||||
|
if (!$b.hasClass('wbf-btn')) {
|
||||||
|
$b.text('');
|
||||||
|
$b.append('<i class="fas fa-' + (nowIgnored ? 'eye' : 'eye-slash') + '"></i> ' + (nowIgnored ? 'Entblocken' : 'Ignorieren'));
|
||||||
|
} else {
|
||||||
|
// Profil-Variante mit wbf-btn
|
||||||
|
$b.html('<i class="fas fa-' + (nowIgnored ? 'eye' : 'eye-slash') + '"></i> ' + (nowIgnored ? 'Ignorierung aufheben' : 'Nutzer ignorieren'));
|
||||||
|
}
|
||||||
|
$b.prop('disabled', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Posts des Users auf der aktuellen Seite ein-/ausblenden
|
||||||
|
$('.wbf-post, .wbf-post--op').each(function () {
|
||||||
|
var $post = $(this);
|
||||||
|
// Buttons innerhalb dieses Posts mit der User-ID suchen
|
||||||
|
var $ib = $post.find('.wbf-ignore-btn[data-id="' + ignoredId + '"]');
|
||||||
|
if (!$ib.length) return;
|
||||||
|
|
||||||
|
if (nowIgnored) {
|
||||||
|
// Ignoriert → Overlay zeigen wenn noch nicht vorhanden
|
||||||
|
if (!$post.find('.wbf-ignored-bar').length) {
|
||||||
|
var barHtml = '<div class="wbf-ignored-bar">' +
|
||||||
|
'<span><i class="fas fa-eye-slash"></i> Beitrag von ignoriertem Nutzer: <strong>' +
|
||||||
|
$('<span>').text(name).html() + '</strong></span>' +
|
||||||
|
'<button class="wbf-show-ignored-btn" type="button">Trotzdem anzeigen</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="wbf-ignored-content" style="display:none">';
|
||||||
|
$post.addClass('wbf-post--ignored');
|
||||||
|
$post.prepend(barHtml);
|
||||||
|
// Restlichen Inhalt in ignored-content verschieben
|
||||||
|
$post.children(':not(.wbf-ignored-bar):not(.wbf-ignored-content)').wrapAll('<div class="wbf-ignored-content-inner">');
|
||||||
|
$post.find('.wbf-ignored-content').append($post.find('.wbf-ignored-content-inner').children());
|
||||||
|
$post.find('.wbf-ignored-content-inner').remove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Entblockt → Overlay entfernen
|
||||||
|
var $bar = $post.find('.wbf-ignored-bar');
|
||||||
|
var $content = $post.find('.wbf-ignored-content');
|
||||||
|
if ($bar.length) {
|
||||||
|
// Inhalt wieder nach oben holen
|
||||||
|
$content.children().unwrap();
|
||||||
|
$bar.remove();
|
||||||
|
$post.removeClass('wbf-post--ignored');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ignore-Liste im Profil aktualisieren (falls Nutzer auf eigener Profil-Seite)
|
||||||
|
if (!nowIgnored) {
|
||||||
|
// Eintrag aus der Liste entfernen
|
||||||
|
$('#wbf-ignore-item-' + ignoredId).fadeOut(300, function () {
|
||||||
|
$(this).remove();
|
||||||
|
var remaining = $('#wbfIgnoreList .wbf-ignore-item').length;
|
||||||
|
$('#wbfIgnoreCount').text(remaining);
|
||||||
|
if (remaining === 0) {
|
||||||
|
$('#wbfIgnoreList').replaceWith('<p class="wbf-profile-empty" id="wbfIgnoreEmpty">Du ignorierst niemanden.</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toast-Meldung
|
||||||
|
var $t = $('<div class="wbf-toast">' + (d.message || (nowIgnored ? name + ' ignoriert.' : 'Ignorierung aufgehoben.')) + '</div>').appendTo('body');
|
||||||
|
setTimeout(function () { $t.remove(); }, 3000);
|
||||||
|
|
||||||
|
}, function () {
|
||||||
|
// Fehler-Callback
|
||||||
|
$('.wbf-ignore-btn[data-id="' + ignoredId + '"]').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* "Trotzdem anzeigen" — eingeklappten ignorierten Post aufdecken */
|
||||||
|
$(document).on('click', '.wbf-show-ignored-btn', function () {
|
||||||
|
var $bar = $(this).closest('.wbf-ignored-bar');
|
||||||
|
var $content = $bar.next('.wbf-ignored-content');
|
||||||
|
$content.slideDown(200);
|
||||||
|
$bar.hide();
|
||||||
|
});
|
||||||
|
|
||||||
}(jQuery));
|
}(jQuery));
|
||||||
@@ -18,6 +18,9 @@ class WBF_Ajax {
|
|||||||
'wbf_create_poll',
|
'wbf_create_poll',
|
||||||
'wbf_toggle_bookmark',
|
'wbf_toggle_bookmark',
|
||||||
'wbf_set_thread_prefix',
|
'wbf_set_thread_prefix',
|
||||||
|
'wbf_toggle_ignore',
|
||||||
|
'wbf_change_email',
|
||||||
|
'wbf_save_notification_prefs',
|
||||||
];
|
];
|
||||||
foreach ($actions as $action) {
|
foreach ($actions as $action) {
|
||||||
add_action('wp_ajax_nopriv_' . $action, [__CLASS__, str_replace('wbf_','handle_',$action)]);
|
add_action('wp_ajax_nopriv_' . $action, [__CLASS__, str_replace('wbf_','handle_',$action)]);
|
||||||
@@ -411,9 +414,37 @@ class WBF_Ajax {
|
|||||||
if (empty($_FILES['avatar'])) wp_send_json_error(['message'=>'Keine Datei.']);
|
if (empty($_FILES['avatar'])) wp_send_json_error(['message'=>'Keine Datei.']);
|
||||||
|
|
||||||
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
|
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
|
||||||
$mime = $_FILES['avatar']['type'] ?? '';
|
|
||||||
if (!in_array($mime, $allowed_types)) wp_send_json_error(['message'=>'Nur JPG, PNG, GIF und WebP erlaubt.']);
|
// Dateigröße vor dem MIME-Check prüfen
|
||||||
if ($_FILES['avatar']['size'] > 2 * 1024 * 1024) wp_send_json_error(['message'=>'Maximale Dateigröße: 2 MB.']);
|
if ( $_FILES['avatar']['size'] > 2 * 1024 * 1024 ) {
|
||||||
|
wp_send_json_error(['message'=>'Maximale Dateigröße: 2 MB.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server-seitige MIME-Typ-Prüfung — $_FILES['type'] kommt vom Client
|
||||||
|
// und ist beliebig fälschbar (z.B. PHP-Datei als image/jpeg getarnt).
|
||||||
|
// finfo_file() liest den echten Magic-Byte der temporären Datei.
|
||||||
|
$tmp = $_FILES['avatar']['tmp_name'] ?? '';
|
||||||
|
if ( ! $tmp || ! is_uploaded_file( $tmp ) ) {
|
||||||
|
wp_send_json_error(['message'=>'Ungültiger Datei-Upload.']);
|
||||||
|
}
|
||||||
|
if ( function_exists('finfo_open') ) {
|
||||||
|
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||||
|
$real_mime = finfo_file( $finfo, $tmp );
|
||||||
|
finfo_close( $finfo );
|
||||||
|
} else {
|
||||||
|
// Fallback: exif_imagetype() wenn finfo nicht verfügbar
|
||||||
|
$et_map = [
|
||||||
|
IMAGETYPE_JPEG => 'image/jpeg',
|
||||||
|
IMAGETYPE_PNG => 'image/png',
|
||||||
|
IMAGETYPE_GIF => 'image/gif',
|
||||||
|
IMAGETYPE_WEBP => 'image/webp',
|
||||||
|
];
|
||||||
|
$et = @exif_imagetype( $tmp );
|
||||||
|
$real_mime = $et_map[$et] ?? '';
|
||||||
|
}
|
||||||
|
if ( ! in_array( $real_mime, $allowed_types, true ) ) {
|
||||||
|
wp_send_json_error(['message'=>'Nur JPG, PNG, GIF und WebP erlaubt.']);
|
||||||
|
}
|
||||||
|
|
||||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||||
@@ -468,16 +499,36 @@ class WBF_Ajax {
|
|||||||
|
|
||||||
// Nur Bilder erlauben
|
// Nur Bilder erlauben
|
||||||
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
|
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
|
||||||
$mime = $_FILES['image']['type'] ?? '';
|
|
||||||
if ( ! in_array($mime, $allowed_types) ) {
|
|
||||||
wp_send_json_error(['message' => 'Nur JPG, PNG, GIF und WebP erlaubt.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max 5 MB
|
// Max 5 MB — Größe zuerst prüfen bevor teure MIME-Erkennung läuft
|
||||||
if ( $_FILES['image']['size'] > 5 * 1024 * 1024 ) {
|
if ( $_FILES['image']['size'] > 5 * 1024 * 1024 ) {
|
||||||
wp_send_json_error(['message' => 'Maximale Dateigröße: 5 MB.']);
|
wp_send_json_error(['message' => 'Maximale Dateigröße: 5 MB.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server-seitige MIME-Typ-Prüfung — $_FILES['type'] ist client-kontrolliert
|
||||||
|
// und kann beliebig auf 'image/jpeg' gesetzt werden, auch für .php-Dateien.
|
||||||
|
$tmp = $_FILES['image']['tmp_name'] ?? '';
|
||||||
|
if ( ! $tmp || ! is_uploaded_file( $tmp ) ) {
|
||||||
|
wp_send_json_error(['message' => 'Ungültiger Datei-Upload.']);
|
||||||
|
}
|
||||||
|
if ( function_exists('finfo_open') ) {
|
||||||
|
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||||
|
$real_mime = finfo_file( $finfo, $tmp );
|
||||||
|
finfo_close( $finfo );
|
||||||
|
} else {
|
||||||
|
$et_map = [
|
||||||
|
IMAGETYPE_JPEG => 'image/jpeg',
|
||||||
|
IMAGETYPE_PNG => 'image/png',
|
||||||
|
IMAGETYPE_GIF => 'image/gif',
|
||||||
|
IMAGETYPE_WEBP => 'image/webp',
|
||||||
|
];
|
||||||
|
$et = @exif_imagetype( $tmp );
|
||||||
|
$real_mime = $et_map[$et] ?? '';
|
||||||
|
}
|
||||||
|
if ( ! in_array( $real_mime, $allowed_types, true ) ) {
|
||||||
|
wp_send_json_error(['message' => 'Nur JPG, PNG, GIF und WebP erlaubt.']);
|
||||||
|
}
|
||||||
|
|
||||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||||
@@ -588,6 +639,18 @@ class WBF_Ajax {
|
|||||||
$is_mod = WBF_DB::can($user, 'delete_post');
|
$is_mod = WBF_DB::can($user, 'delete_post');
|
||||||
if ( ! $is_own && ! $is_mod ) wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
if ( ! $is_own && ! $is_mod ) wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
||||||
|
|
||||||
|
// Post-Bearbeitungslimit prüfen — gilt auch für Thread-Erstbeiträge
|
||||||
|
// (spiegelt identisches Verhalten zu handle_edit_post() wider)
|
||||||
|
if ( $is_own && ! $is_mod ) {
|
||||||
|
$limit_min = (int)( wbf_get_settings()['post_edit_limit'] ?? 30 );
|
||||||
|
if ( $limit_min > 0 ) {
|
||||||
|
$age_min = ( time() - strtotime( $thread->created_at ) ) / 60;
|
||||||
|
if ( $age_min > $limit_min ) {
|
||||||
|
wp_send_json_error(['message' => "Bearbeitung nur innerhalb von {$limit_min} Minuten nach dem Erstellen möglich."]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$wpdb->update(
|
$wpdb->update(
|
||||||
"{$wpdb->prefix}forum_threads",
|
"{$wpdb->prefix}forum_threads",
|
||||||
@@ -682,6 +745,11 @@ class WBF_Ajax {
|
|||||||
if ($to_id === (int)$user->id) wp_send_json_error(['message'=>'Du kannst dir nicht selbst schreiben.']);
|
if ($to_id === (int)$user->id) wp_send_json_error(['message'=>'Du kannst dir nicht selbst schreiben.']);
|
||||||
if (!WBF_DB::get_user($to_id)) wp_send_json_error(['message'=>'Empfänger nicht gefunden.']);
|
if (!WBF_DB::get_user($to_id)) wp_send_json_error(['message'=>'Empfänger nicht gefunden.']);
|
||||||
|
|
||||||
|
// DM-Blockierung: Empfänger hat Sender ignoriert
|
||||||
|
if ( WBF_DB::is_ignored( $to_id, $user->id ) ) {
|
||||||
|
wp_send_json_error(['message' => 'Diese Person akzeptiert keine Nachrichten von dir.']);
|
||||||
|
}
|
||||||
|
|
||||||
$id = WBF_DB::send_message($user->id, $to_id, $content);
|
$id = WBF_DB::send_message($user->id, $to_id, $content);
|
||||||
// Notify recipient
|
// Notify recipient
|
||||||
WBF_DB::create_notification($to_id, 'message', $id, $user->id);
|
WBF_DB::create_notification($to_id, 'message', $id, $user->id);
|
||||||
@@ -739,14 +807,22 @@ class WBF_Ajax {
|
|||||||
// ── User-Autocomplete (für @Erwähnungen + DM) ─────────────────────────────
|
// ── User-Autocomplete (für @Erwähnungen + DM) ─────────────────────────────
|
||||||
|
|
||||||
public static function handle_user_suggest() {
|
public static function handle_user_suggest() {
|
||||||
|
// Nur eingeloggte Nutzer dürfen die User-Suche nutzen
|
||||||
|
// (verhindert Enumeration aller Usernamen + Rollendaten durch Gäste)
|
||||||
|
if ( ! WBF_Auth::is_forum_logged_in() ) {
|
||||||
|
wp_send_json_success(['users'=>[]]);
|
||||||
|
}
|
||||||
$q = sanitize_text_field($_POST['q'] ?? $_GET['q'] ?? '');
|
$q = sanitize_text_field($_POST['q'] ?? $_GET['q'] ?? '');
|
||||||
if (mb_strlen($q) < 1) wp_send_json_success(['users'=>[]]);
|
if (mb_strlen($q) < 1) wp_send_json_success(['users'=>[]]);
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$like = $wpdb->esc_like($q) . '%';
|
$like = $wpdb->esc_like($q) . '%';
|
||||||
|
// Rolle wird bewusst NICHT zurückgegeben — nicht für Autocomplete nötig
|
||||||
|
// und verhindert Informationsleck über Rollen-Verteilung im Forum.
|
||||||
$users = $wpdb->get_results($wpdb->prepare(
|
$users = $wpdb->get_results($wpdb->prepare(
|
||||||
"SELECT id, username, display_name, avatar_url, role
|
"SELECT id, username, display_name, avatar_url
|
||||||
FROM {$wpdb->prefix}forum_users
|
FROM {$wpdb->prefix}forum_users
|
||||||
WHERE username LIKE %s OR display_name LIKE %s
|
WHERE (username LIKE %s OR display_name LIKE %s)
|
||||||
|
AND role != 'banned'
|
||||||
ORDER BY display_name ASC LIMIT 8",
|
ORDER BY display_name ASC LIMIT 8",
|
||||||
$like, $like
|
$like, $like
|
||||||
));
|
));
|
||||||
@@ -817,6 +893,13 @@ class WBF_Ajax {
|
|||||||
private static function send_notification_email( $to_user, $type, $actor_name, $extra = [] ) {
|
private static function send_notification_email( $to_user, $type, $actor_name, $extra = [] ) {
|
||||||
if ( ! $to_user || empty($to_user->email) ) return;
|
if ( ! $to_user || empty($to_user->email) ) return;
|
||||||
|
|
||||||
|
// Prüfen ob der User diesen Benachrichtigungstyp aktiviert hat
|
||||||
|
// Standard: alle aktiviert (1). User kann im Profil deaktivieren (0).
|
||||||
|
$pref_key = 'notify_' . $type; // notify_reply, notify_mention, notify_message
|
||||||
|
$meta = WBF_DB::get_user_meta( $to_user->id );
|
||||||
|
// Nur deaktivieren wenn explizit auf '0' gesetzt — Standard ist aktiviert
|
||||||
|
if ( isset($meta[$pref_key]) && $meta[$pref_key] === '0' ) return;
|
||||||
|
|
||||||
$blog_name = get_bloginfo('name');
|
$blog_name = get_bloginfo('name');
|
||||||
$forum_url = wbf_get_forum_url();
|
$forum_url = wbf_get_forum_url();
|
||||||
$from_email = get_option('admin_email');
|
$from_email = get_option('admin_email');
|
||||||
@@ -897,6 +980,17 @@ class WBF_Ajax {
|
|||||||
$email = sanitize_email( $_POST['email'] ?? '' );
|
$email = sanitize_email( $_POST['email'] ?? '' );
|
||||||
if ( ! is_email($email) ) wp_send_json_error(['message'=>'Ungültige E-Mail-Adresse.']);
|
if ( ! is_email($email) ) wp_send_json_error(['message'=>'Ungültige E-Mail-Adresse.']);
|
||||||
|
|
||||||
|
// ── Rate-Limiting: max. 1 Reset-Mail pro E-Mail-Adresse alle 15 Minuten ──
|
||||||
|
// Verhindert, dass ein Angreifer tausende Reset-Mails pro Sekunde
|
||||||
|
// für beliebige Adressen triggert und den Mail-Server überlastet.
|
||||||
|
$rate_key = 'wbf_pwreset_' . md5( strtolower( $email ) );
|
||||||
|
if ( get_transient( $rate_key ) !== false ) {
|
||||||
|
// Immer Erfolg melden — kein Leak ob Rate-Limit oder kein Account
|
||||||
|
wp_send_json_success(['message'=>'Falls diese E-Mail registriert ist, wurde eine E-Mail gesendet.']);
|
||||||
|
}
|
||||||
|
// Cooldown setzen — 15 Minuten
|
||||||
|
set_transient( $rate_key, 1, 15 * MINUTE_IN_SECONDS );
|
||||||
|
|
||||||
$user = WBF_DB::get_user_by('email', $email);
|
$user = WBF_DB::get_user_by('email', $email);
|
||||||
// Immer Erfolg melden (kein User-Enumeration)
|
// Immer Erfolg melden (kein User-Enumeration)
|
||||||
if ( ! $user ) {
|
if ( ! $user ) {
|
||||||
@@ -922,7 +1016,13 @@ class WBF_Ajax {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function handle_reset_password() {
|
public static function handle_reset_password() {
|
||||||
self::verify();
|
// Kein self::verify() hier — Gäste haben keine Forum-Session.
|
||||||
|
// Das Reset-Token selbst authentifiziert die Anfrage.
|
||||||
|
// Wir prüfen trotzdem den WP-Nonce als CSRF-Schutz; dieser wird
|
||||||
|
// von wp_localize_script für alle Besucher (auch Gäste) generiert.
|
||||||
|
if ( ! check_ajax_referer( 'wbf_nonce', 'nonce', false ) ) {
|
||||||
|
wp_send_json_error(['message' => 'Sicherheitsfehler.']);
|
||||||
|
}
|
||||||
$token = sanitize_text_field( $_POST['token'] ?? '' );
|
$token = sanitize_text_field( $_POST['token'] ?? '' );
|
||||||
$password = $_POST['password'] ?? '';
|
$password = $_POST['password'] ?? '';
|
||||||
$password2= $_POST['password2'] ?? '';
|
$password2= $_POST['password2'] ?? '';
|
||||||
@@ -1207,6 +1307,90 @@ class WBF_Ajax {
|
|||||||
wp_send_json_success(['prefix' => $prefix]);
|
wp_send_json_success(['prefix' => $prefix]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── E-Mail-Adresse ändern ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public static function handle_change_email() {
|
||||||
|
self::verify();
|
||||||
|
$user = WBF_Auth::get_current_user();
|
||||||
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
||||||
|
|
||||||
|
$new_email = sanitize_email( $_POST['new_email'] ?? '' );
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if ( ! is_email($new_email) ) {
|
||||||
|
wp_send_json_error(['message' => 'Ungültige E-Mail-Adresse.']);
|
||||||
|
}
|
||||||
|
if ( empty($password) ) {
|
||||||
|
wp_send_json_error(['message' => 'Bitte aktuelles Passwort zur Bestätigung eingeben.']);
|
||||||
|
}
|
||||||
|
if ( ! password_verify($password, $user->password) ) {
|
||||||
|
wp_send_json_error(['message' => 'Falsches Passwort.']);
|
||||||
|
}
|
||||||
|
if ( strtolower($new_email) === strtolower($user->email) ) {
|
||||||
|
wp_send_json_error(['message' => 'Das ist bereits deine aktuelle E-Mail-Adresse.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob E-Mail bereits vergeben
|
||||||
|
$existing = WBF_DB::get_user_by('email', $new_email);
|
||||||
|
if ( $existing && (int)$existing->id !== (int)$user->id ) {
|
||||||
|
wp_send_json_error(['message' => 'Diese E-Mail-Adresse ist bereits registriert.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
WBF_DB::update_user($user->id, ['email' => $new_email]);
|
||||||
|
wp_send_json_success(['message' => 'E-Mail-Adresse erfolgreich geändert.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Benachrichtigungs-Einstellungen speichern ─────────────────────────────
|
||||||
|
|
||||||
|
public static function handle_save_notification_prefs() {
|
||||||
|
self::verify();
|
||||||
|
$user = WBF_Auth::get_current_user();
|
||||||
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
||||||
|
|
||||||
|
$allowed = ['notify_reply', 'notify_mention', 'notify_message'];
|
||||||
|
foreach ( $allowed as $key ) {
|
||||||
|
// 1 wenn Checkbox aktiviert, 0 wenn deaktiviert
|
||||||
|
$val = isset($_POST[$key]) && $_POST[$key] === '1' ? '1' : '0';
|
||||||
|
WBF_DB::set_user_meta($user->id, $key, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(['message' => 'Benachrichtigungs-Einstellungen gespeichert.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── User ignorieren / Ignorierung aufheben ────────────────────────────────
|
||||||
|
|
||||||
|
public static function handle_toggle_ignore() {
|
||||||
|
self::verify();
|
||||||
|
$user = WBF_Auth::get_current_user();
|
||||||
|
if ( ! $user ) wp_send_json_error( ['message' => 'Nicht eingeloggt.'] );
|
||||||
|
|
||||||
|
$ignored_id = (int)( $_POST['ignored_id'] ?? 0 );
|
||||||
|
if ( ! $ignored_id ) wp_send_json_error( ['message' => 'Ungültiger Nutzer.'] );
|
||||||
|
if ( $ignored_id === (int)$user->id ) {
|
||||||
|
wp_send_json_error( ['message' => 'Du kannst dich nicht selbst ignorieren.'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = WBF_DB::get_user( $ignored_id );
|
||||||
|
if ( ! $target ) wp_send_json_error( ['message' => 'Nutzer nicht gefunden.'] );
|
||||||
|
|
||||||
|
// Prüfen ob diese Rolle geblockt werden darf (konfigurierbar in den Einstellungen)
|
||||||
|
if ( ! wbf_can_be_ignored( $target ) ) {
|
||||||
|
$role_label = WBF_Roles::get($target->role)['label'] ?? $target->role;
|
||||||
|
wp_send_json_error( ['message' => 'Nutzer mit der Rolle "' . $role_label . '" können nicht ignoriert werden.'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$now_ignored = WBF_DB::toggle_ignore( $user->id, $ignored_id );
|
||||||
|
|
||||||
|
wp_send_json_success( [
|
||||||
|
'ignored' => $now_ignored,
|
||||||
|
'ignored_id' => $ignored_id,
|
||||||
|
'display_name' => $target->display_name,
|
||||||
|
'message' => $now_ignored
|
||||||
|
? esc_html( $target->display_name ) . ' wird jetzt ignoriert.'
|
||||||
|
: 'Ignorierung von ' . esc_html( $target->display_name ) . ' aufgehoben.',
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add_action( 'init', [ 'WBF_Ajax', 'init' ] );
|
add_action( 'init', [ 'WBF_Ajax', 'init' ] );
|
||||||
@@ -279,6 +279,18 @@ class WBF_DB {
|
|||||||
) $charset;";
|
) $charset;";
|
||||||
dbDelta( $sql_bookmarks );
|
dbDelta( $sql_bookmarks );
|
||||||
|
|
||||||
|
// ── Ignore-Liste ──────────────────────────────────────────────────────
|
||||||
|
$sql_ignore = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_ignore_list (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
ignored_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY user_ignored (user_id, ignored_id),
|
||||||
|
KEY ignored_id (ignored_id)
|
||||||
|
) $charset;";
|
||||||
|
dbDelta( $sql_ignore );
|
||||||
|
|
||||||
// ── prefix_id zu threads ──────────────────────────────────────────────
|
// ── prefix_id zu threads ──────────────────────────────────────────────
|
||||||
self::maybe_add_column( "{$wpdb->prefix}forum_threads", 'prefix_id',
|
self::maybe_add_column( "{$wpdb->prefix}forum_threads", 'prefix_id',
|
||||||
"ALTER TABLE {$wpdb->prefix}forum_threads ADD COLUMN prefix_id BIGINT UNSIGNED DEFAULT NULL" );
|
"ALTER TABLE {$wpdb->prefix}forum_threads ADD COLUMN prefix_id BIGINT UNSIGNED DEFAULT NULL" );
|
||||||
@@ -623,7 +635,8 @@ class WBF_DB {
|
|||||||
FROM {$wpdb->prefix}forum_threads t
|
FROM {$wpdb->prefix}forum_threads t
|
||||||
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_id
|
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_id
|
||||||
JOIN {$wpdb->prefix}forum_categories c ON c.id = t.category_id
|
JOIN {$wpdb->prefix}forum_categories c ON c.id = t.category_id
|
||||||
AND t.status != 'archived' ORDER BY t.created_at DESC LIMIT %d", $limit
|
WHERE t.status != 'archived' AND t.deleted_at IS NULL
|
||||||
|
ORDER BY t.created_at DESC LIMIT %d", $limit
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1207,13 +1220,18 @@ class WBF_DB {
|
|||||||
global $wpdb;
|
global $wpdb;
|
||||||
$token = bin2hex( random_bytes(32) );
|
$token = bin2hex( random_bytes(32) );
|
||||||
$hash = hash( 'sha256', $token );
|
$hash = hash( 'sha256', $token );
|
||||||
// Alte Tokens löschen
|
// Altes Token dieses Users zurücksetzen bevor ein neues gesetzt wird
|
||||||
$wpdb->delete( "{$wpdb->prefix}forum_users", [] ); // nur placeholder
|
$wpdb->query( $wpdb->prepare(
|
||||||
|
"UPDATE {$wpdb->prefix}forum_users
|
||||||
|
SET reset_token=NULL, reset_token_expires=NULL
|
||||||
|
WHERE id=%d",
|
||||||
|
(int) $user_id
|
||||||
|
) );
|
||||||
$wpdb->query( $wpdb->prepare(
|
$wpdb->query( $wpdb->prepare(
|
||||||
"UPDATE {$wpdb->prefix}forum_users
|
"UPDATE {$wpdb->prefix}forum_users
|
||||||
SET reset_token=%s, reset_token_expires=DATE_ADD(NOW(), INTERVAL 1 HOUR)
|
SET reset_token=%s, reset_token_expires=DATE_ADD(NOW(), INTERVAL 1 HOUR)
|
||||||
WHERE id=%d",
|
WHERE id=%d",
|
||||||
$hash, $user_id
|
$hash, (int) $user_id
|
||||||
) );
|
) );
|
||||||
return $token; // Klartext-Token → per E-Mail senden
|
return $token; // Klartext-Token → per E-Mail senden
|
||||||
}
|
}
|
||||||
@@ -1689,6 +1707,128 @@ class WBF_DB {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Ignore-Liste ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public static function toggle_ignore( $user_id, $ignored_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
$user_id = (int) $user_id;
|
||||||
|
$ignored_id = (int) $ignored_id;
|
||||||
|
if ( self::is_ignored( $user_id, $ignored_id ) ) {
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_ignore_list", [
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'ignored_id' => $ignored_id,
|
||||||
|
] );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$wpdb->replace( "{$wpdb->prefix}forum_ignore_list", [
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'ignored_id' => $ignored_id,
|
||||||
|
] );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function is_ignored( $user_id, $ignored_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
$table = "{$wpdb->prefix}forum_ignore_list";
|
||||||
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) return false;
|
||||||
|
return (bool) $wpdb->get_var( $wpdb->prepare(
|
||||||
|
"SELECT id FROM {$wpdb->prefix}forum_ignore_list WHERE user_id=%d AND ignored_id=%d",
|
||||||
|
(int) $user_id, (int) $ignored_id
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gibt alle ignorierten User-IDs als int-Array zurück */
|
||||||
|
public static function get_ignored_ids( $user_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
$table = "{$wpdb->prefix}forum_ignore_list";
|
||||||
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) return [];
|
||||||
|
$ids = $wpdb->get_col( $wpdb->prepare(
|
||||||
|
"SELECT ignored_id FROM {$wpdb->prefix}forum_ignore_list WHERE user_id=%d",
|
||||||
|
(int) $user_id
|
||||||
|
) );
|
||||||
|
return array_map( 'intval', $ids );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vollständige Ignore-Liste mit User-Daten */
|
||||||
|
public static function get_ignore_list( $user_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
$table = "{$wpdb->prefix}forum_ignore_list";
|
||||||
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) return [];
|
||||||
|
return $wpdb->get_results( $wpdb->prepare(
|
||||||
|
"SELECT u.id, u.username, u.display_name, u.avatar_url, u.role,
|
||||||
|
il.created_at AS ignored_since
|
||||||
|
FROM {$wpdb->prefix}forum_ignore_list il
|
||||||
|
JOIN {$wpdb->prefix}forum_users u ON u.id = il.ignored_id
|
||||||
|
WHERE il.user_id = %d
|
||||||
|
ORDER BY il.created_at DESC",
|
||||||
|
(int) $user_id
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── DSGVO Art. 17: Konto vollständig löschen ──────────────────────────────
|
||||||
|
|
||||||
|
public static function delete_user_gdpr( $user_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
$user_id = (int) $user_id;
|
||||||
|
$user = self::get_user( $user_id );
|
||||||
|
if ( ! $user ) return false;
|
||||||
|
if ( $user->role === 'superadmin' ) return false;
|
||||||
|
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_messages", [ 'from_id' => $user_id ] );
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_messages", [ 'to_id' => $user_id ] );
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_remember_tokens", [ 'user_id' => $user_id ] );
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_notifications", [ 'user_id' => $user_id ] );
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_notifications", [ 'actor_id' => $user_id ] );
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_subscriptions", [ 'user_id' => $user_id ] );
|
||||||
|
|
||||||
|
$table_bm = "{$wpdb->prefix}forum_bookmarks";
|
||||||
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_bm'" ) === $table_bm ) {
|
||||||
|
$wpdb->delete( $table_bm, [ 'user_id' => $user_id ] );
|
||||||
|
}
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_likes", [ 'user_id' => $user_id ] );
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_reactions", [ 'user_id' => $user_id ] );
|
||||||
|
$wpdb->delete( "{$wpdb->prefix}forum_reports", [ 'reporter_id' => $user_id ] );
|
||||||
|
|
||||||
|
$table_pv = "{$wpdb->prefix}forum_poll_votes";
|
||||||
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_pv'" ) === $table_pv ) {
|
||||||
|
$wpdb->delete( $table_pv, [ 'user_id' => $user_id ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore-Liste beidseitig bereinigen
|
||||||
|
$table_il = "{$wpdb->prefix}forum_ignore_list";
|
||||||
|
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_il'" ) === $table_il ) {
|
||||||
|
$wpdb->delete( $table_il, [ 'user_id' => $user_id ] );
|
||||||
|
$wpdb->delete( $table_il, [ 'ignored_id' => $user_id ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_transient( 'wbf_flood_' . $user_id );
|
||||||
|
delete_transient( 'wbf_flood_ts_' . $user_id );
|
||||||
|
|
||||||
|
self::delete_user_meta_all( $user_id );
|
||||||
|
|
||||||
|
$anon_hash = substr( hash( 'sha256', $user_id . wp_salt() . microtime( true ) ), 0, 12 );
|
||||||
|
$wpdb->update(
|
||||||
|
"{$wpdb->prefix}forum_users",
|
||||||
|
[
|
||||||
|
'username' => 'deleted_' . $anon_hash,
|
||||||
|
'email' => 'deleted_' . $anon_hash . '@deleted.invalid',
|
||||||
|
'password' => '',
|
||||||
|
'display_name' => 'Gelöschter Nutzer',
|
||||||
|
'avatar_url' => '',
|
||||||
|
'bio' => '',
|
||||||
|
'signature' => '',
|
||||||
|
'ban_reason' => '',
|
||||||
|
'reset_token' => null,
|
||||||
|
'reset_token_expires' => null,
|
||||||
|
'pre_ban_role' => '',
|
||||||
|
'ban_until' => null,
|
||||||
|
'role' => 'banned',
|
||||||
|
],
|
||||||
|
[ 'id' => $user_id ]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Wortfilter ────────────────────────────────────────────────────────────
|
// ── Wortfilter ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public static function get_word_filter() {
|
public static function get_word_filter() {
|
||||||
@@ -1711,25 +1851,29 @@ class WBF_DB {
|
|||||||
// ── Flood Control ─────────────────────────────────────────────────────────
|
// ── Flood Control ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public static function check_flood( $user_id ) {
|
public static function check_flood( $user_id ) {
|
||||||
|
$user_id = (int) $user_id;
|
||||||
|
if ( $user_id <= 0 ) return true; // kein eingeloggter User — kein Flood-Check
|
||||||
$interval = (int)( wbf_get_settings()['flood_interval'] ?? 0 );
|
$interval = (int)( wbf_get_settings()['flood_interval'] ?? 0 );
|
||||||
if ( $interval <= 0 ) return true; // deaktiviert
|
if ( $interval <= 0 ) return true; // deaktiviert
|
||||||
$key = 'wbf_flood_' . (int)$user_id;
|
$key = 'wbf_flood_' . (int)$user_id;
|
||||||
|
$ts_key = 'wbf_flood_ts_' . (int)$user_id;
|
||||||
$last = get_transient( $key );
|
$last = get_transient( $key );
|
||||||
if ( $last !== false ) {
|
if ( $last !== false ) {
|
||||||
return false; // noch gesperrt
|
return false; // noch gesperrt
|
||||||
}
|
}
|
||||||
set_transient( $key, time(), $interval );
|
set_transient( $key, 1, $interval );
|
||||||
|
set_transient( $ts_key, time(), $interval + 5 );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function flood_remaining( $user_id ) {
|
public static function flood_remaining( $user_id ) {
|
||||||
$interval = (int)( wbf_get_settings()['flood_interval'] ?? 0 );
|
$interval = (int)( wbf_get_settings()['flood_interval'] ?? 0 );
|
||||||
if ( $interval <= 0 ) return 0;
|
if ( $interval <= 0 ) return 0;
|
||||||
$key = 'wbf_flood_' . (int)$user_id;
|
$ts_key = 'wbf_flood_ts_' . (int)$user_id;
|
||||||
$last = get_transient( $key );
|
$sent = get_transient( $ts_key );
|
||||||
if ( $last === false ) return 0;
|
if ( $sent === false ) return 0;
|
||||||
// Transients speichern keine genaue Restzeit — wir schätzen über $interval
|
$remaining = $interval - ( time() - (int)$sent );
|
||||||
return $interval;
|
return max( 0, $remaining );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
1202
includes/class-forum-export.php
Normal file
1202
includes/class-forum-export.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ class WBF_Roles {
|
|||||||
private static function default_roles() {
|
private static function default_roles() {
|
||||||
return [
|
return [
|
||||||
'superadmin' => [
|
'superadmin' => [
|
||||||
'label' => 'Superadmin',
|
'label' => 'Admin',
|
||||||
'level' => 100,
|
'level' => 100,
|
||||||
'color' => '#e11d48',
|
'color' => '#e11d48',
|
||||||
'bg_color' => 'rgba(225,29,72,.15)',
|
'bg_color' => 'rgba(225,29,72,.15)',
|
||||||
|
|||||||
@@ -341,9 +341,9 @@ class WBF_Shortcodes {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
<?php self::render_forum_footer(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(), $current); ?>
|
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(), $current); ?>
|
||||||
<?php self::render_forum_footer(); ?>
|
|
||||||
<?php self::render_auth_modal(); ?>
|
<?php self::render_auth_modal(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
@@ -500,9 +500,9 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; endif; ?>
|
<?php endif; endif; ?>
|
||||||
|
<?php self::render_forum_footer(); ?>
|
||||||
|
|
||||||
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(),$current,$cat->id); ?>
|
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(),$current,$cat->id); ?>
|
||||||
<?php self::render_forum_footer(); ?>
|
|
||||||
<?php self::render_auth_modal(); ?>
|
<?php self::render_auth_modal(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
@@ -731,6 +731,23 @@ class WBF_Shortcodes {
|
|||||||
<i class="fas fa-pen"></i> Bearbeiten
|
<i class="fas fa-pen"></i> Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
// Ignore-Button: nur wenn der Thread-Autor nicht der eingeloggte User ist
|
||||||
|
// und die Rolle blockiert werden darf (konfigurierbar in Einstellungen)
|
||||||
|
$op_author = WBF_DB::get_user((int)$thread->user_id);
|
||||||
|
if ($current && (int)$current->id !== (int)$thread->user_id && wbf_can_be_ignored($op_author)):
|
||||||
|
$op_is_ignored = WBF_DB::is_ignored($current->id, (int)$thread->user_id);
|
||||||
|
?>
|
||||||
|
<button class="wbf-ignore-btn"
|
||||||
|
data-id="<?php echo (int)$thread->user_id; ?>"
|
||||||
|
data-name="<?php echo esc_attr($thread->display_name); ?>"
|
||||||
|
data-ignored="<?php echo $op_is_ignored ? '1' : '0'; ?>"
|
||||||
|
title="<?php echo $op_is_ignored ? 'Ignorierung aufheben' : 'Nutzer ignorieren'; ?>"
|
||||||
|
style="background:none;border:none;cursor:pointer;color:var(--c-muted,#94a3b8);padding:2px 6px;border-radius:4px;font-size:.82rem">
|
||||||
|
<i class="fas fa-<?php echo $op_is_ignored ? 'eye' : 'eye-slash'; ?>"></i>
|
||||||
|
<?php echo $op_is_ignored ? 'Entblocken' : 'Ignorieren'; ?>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -774,8 +791,8 @@ class WBF_Shortcodes {
|
|||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="wbf-notice wbf-notice--warning"><i class="fas fa-lock"></i> Dieser Thread ist geschlossen.</div>
|
<div class="wbf-notice wbf-notice--warning"><i class="fas fa-lock"></i> Dieser Thread ist geschlossen.</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
|
||||||
<?php self::render_forum_footer(); ?>
|
<?php self::render_forum_footer(); ?>
|
||||||
|
</div>
|
||||||
<?php self::render_auth_modal(); ?>
|
<?php self::render_auth_modal(); ?>
|
||||||
<?php self::render_report_modal(); ?>
|
<?php self::render_report_modal(); ?>
|
||||||
<?php if (WBF_DB::can($current,'manage_cats')): self::render_move_modal(WBF_DB::get_categories_flat(), $id); endif; ?>
|
<?php if (WBF_DB::can($current,'manage_cats')): self::render_move_modal(WBF_DB::get_categories_flat(), $id); endif; ?>
|
||||||
@@ -843,6 +860,22 @@ class WBF_Shortcodes {
|
|||||||
<i class="fas fa-pen"></i> Bearbeiten
|
<i class="fas fa-pen"></i> Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
// Ignore-Button im Post-Footer
|
||||||
|
$post_author = WBF_DB::get_user((int)$post->user_id);
|
||||||
|
if ($current && (int)$current->id !== (int)$post->user_id && wbf_can_be_ignored($post_author)):
|
||||||
|
$post_is_ignored = WBF_DB::is_ignored($current->id, (int)$post->user_id);
|
||||||
|
?>
|
||||||
|
<button class="wbf-ignore-btn"
|
||||||
|
data-id="<?php echo (int)$post->user_id; ?>"
|
||||||
|
data-name="<?php echo esc_attr($post->display_name); ?>"
|
||||||
|
data-ignored="<?php echo $post_is_ignored ? '1' : '0'; ?>"
|
||||||
|
title="<?php echo $post_is_ignored ? 'Ignorierung aufheben' : 'Nutzer ignorieren'; ?>"
|
||||||
|
style="background:none;border:none;cursor:pointer;color:var(--c-muted,#94a3b8);padding:2px 6px;border-radius:4px;font-size:.82rem">
|
||||||
|
<i class="fas fa-<?php echo $post_is_ignored ? 'eye' : 'eye-slash'; ?>"></i>
|
||||||
|
<?php echo $post_is_ignored ? 'Entblocken' : 'Ignorieren'; ?>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
<?php echo self::mod_tools_post($post->id,$current); ?>
|
<?php echo self::mod_tools_post($post->id,$current); ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -870,14 +903,23 @@ class WBF_Shortcodes {
|
|||||||
</div></div>
|
</div></div>
|
||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
}
|
}
|
||||||
$user_posts = WBF_DB::get_user_posts( $profile->id, 50 );
|
$user_posts = WBF_DB::get_user_posts( $profile->id, 50 );
|
||||||
|
$bookmarks = $is_own ? WBF_DB::get_user_bookmarks($current->id, 50) : [];
|
||||||
|
$ignore_list = $is_own ? WBF_DB::get_ignore_list($current->id) : [];
|
||||||
|
$cf_defs = WBF_DB::get_profile_field_defs();
|
||||||
|
$cf_vals = WBF_DB::get_user_meta( $profile->id );
|
||||||
|
// Aktiven Tab aus URL lesen (tab=1|2|3), Standard: 1 für eigenes, 2 für fremdes
|
||||||
|
$active_tab = (int)($_GET['ptab'] ?? ($is_own ? 1 : 2));
|
||||||
|
$active_tab = in_array($active_tab, [1,2,3]) ? $active_tab : ($is_own ? 1 : 2);
|
||||||
|
// Tab 1 + 3 nur für eigenes Profil
|
||||||
|
if (!$is_own && $active_tab !== 2) $active_tab = 2;
|
||||||
|
|
||||||
ob_start(); ?>
|
ob_start(); ?>
|
||||||
<div class="wbf-wrap">
|
<div class="wbf-wrap">
|
||||||
<?php self::render_topbar($current); ?>
|
<?php self::render_topbar($current); ?>
|
||||||
<div class="wbf-container wbf-mt">
|
<div class="wbf-container wbf-mt">
|
||||||
<nav class="wbf-breadcrumb">
|
<nav class="wbf-breadcrumb">
|
||||||
<a href="<?php echo esc_url(remove_query_arg('forum_profile')); ?>"><i class="fas fa-home"></i> Forum</a>
|
<a href="<?php echo esc_url(remove_query_arg(['forum_profile', 'ptab'])); ?>"><i class="fas fa-home"></i> Forum</a>
|
||||||
<span>/</span><span>Profil</span>
|
<span>/</span><span>Profil</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -885,8 +927,6 @@ class WBF_Shortcodes {
|
|||||||
|
|
||||||
<!-- ── SIDEBAR ─────────────────────────────────────────── -->
|
<!-- ── SIDEBAR ─────────────────────────────────────────── -->
|
||||||
<aside class="wbf-profile-sidebar">
|
<aside class="wbf-profile-sidebar">
|
||||||
|
|
||||||
<!-- Avatar -->
|
|
||||||
<div class="wbf-profile-sidebar__avatar-wrap">
|
<div class="wbf-profile-sidebar__avatar-wrap">
|
||||||
<img src="<?php echo esc_url($profile->avatar_url); ?>"
|
<img src="<?php echo esc_url($profile->avatar_url); ?>"
|
||||||
alt="<?php echo esc_attr($profile->display_name); ?>"
|
alt="<?php echo esc_attr($profile->display_name); ?>"
|
||||||
@@ -898,15 +938,24 @@ class WBF_Shortcodes {
|
|||||||
</label>
|
</label>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Name + Badge + Username -->
|
|
||||||
<div class="wbf-profile-sidebar__identity">
|
<div class="wbf-profile-sidebar__identity">
|
||||||
<h2><?php echo esc_html($profile->display_name); ?></h2>
|
<h2><?php echo esc_html($profile->display_name); ?></h2>
|
||||||
<?php echo self::role_badge($profile->role); ?>
|
<?php echo self::role_badge($profile->role); ?>
|
||||||
<span class="wbf-profile-sidebar__username">@<?php echo esc_html($profile->username); ?></span>
|
<span class="wbf-profile-sidebar__username">@<?php echo esc_html($profile->username); ?></span>
|
||||||
|
<?php
|
||||||
|
$profile_online = WBF_DB::is_online($profile->id, 15);
|
||||||
|
if ($profile_online): ?>
|
||||||
|
<span class="wbf-profile-online-badge">
|
||||||
|
<span class="wbf-profile-online-dot"></span> Online
|
||||||
|
</span>
|
||||||
|
<?php else:
|
||||||
|
$last = $profile->last_active ?? null;
|
||||||
|
if ($last && $last !== '0000-00-00 00:00:00'): ?>
|
||||||
|
<span class="wbf-profile-lastseen">
|
||||||
|
<i class="fas fa-clock"></i> Zuletzt aktiv: <?php echo self::time_ago($last); ?>
|
||||||
|
</span>
|
||||||
|
<?php endif; endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats -->
|
|
||||||
<div class="wbf-profile-sidebar__stats">
|
<div class="wbf-profile-sidebar__stats">
|
||||||
<div class="wbf-profile-sidebar__stat">
|
<div class="wbf-profile-sidebar__stat">
|
||||||
<span><?php echo (int)$profile->post_count; ?></span>
|
<span><?php echo (int)$profile->post_count; ?></span>
|
||||||
@@ -917,65 +966,94 @@ class WBF_Shortcodes {
|
|||||||
<em>Dabei seit</em>
|
<em>Dabei seit</em>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Level-Fortschritt -->
|
|
||||||
<?php $level_bar = WBF_Levels::progress_bar((int)$profile->post_count); if ($level_bar): ?>
|
<?php $level_bar = WBF_Levels::progress_bar((int)$profile->post_count); if ($level_bar): ?>
|
||||||
<div class="wbf-profile-sidebar__section">
|
<div class="wbf-profile-sidebar__section"><?php echo $level_bar; ?></div>
|
||||||
<?php echo $level_bar; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Bio -->
|
|
||||||
<?php if (!empty($profile->bio)): ?>
|
<?php if (!empty($profile->bio)): ?>
|
||||||
<div class="wbf-profile-sidebar__section">
|
<div class="wbf-profile-sidebar__section">
|
||||||
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-align-left"></i> Bio</span>
|
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-align-left"></i> Bio</span>
|
||||||
<p><?php echo nl2br(esc_html($profile->bio)); ?></p>
|
<p><?php echo nl2br(esc_html($profile->bio)); ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Signatur -->
|
|
||||||
<?php if (!empty($profile->signature)): ?>
|
<?php if (!empty($profile->signature)): ?>
|
||||||
<div class="wbf-profile-sidebar__section">
|
<div class="wbf-profile-sidebar__section">
|
||||||
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-pen-nib"></i> Signatur</span>
|
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-pen-nib"></i> Signatur</span>
|
||||||
<p class="wbf-profile-sidebar__sig"><?php echo nl2br(esc_html($profile->signature)); ?></p>
|
<p class="wbf-profile-sidebar__sig"><?php echo nl2br(esc_html($profile->signature)); ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<!-- Öffentliche Custom Fields -->
|
||||||
<!-- Benutzerdefinierte Profilfelder (öffentliche) -->
|
<?php foreach ($cf_defs as $def):
|
||||||
<?php
|
if (!$is_own && empty($def['public'])) continue;
|
||||||
$cf_defs_pub = WBF_DB::get_profile_field_defs();
|
$val = trim($cf_vals[$def['key']] ?? '');
|
||||||
$cf_vals_pub = WBF_DB::get_user_meta( $profile->id );
|
if ($val === '') continue; ?>
|
||||||
foreach ( $cf_defs_pub as $def ):
|
|
||||||
if ( ! $is_own && empty($def['public']) ) continue;
|
|
||||||
$val = trim( $cf_vals_pub[ $def['key'] ] ?? '' );
|
|
||||||
if ( $val === '' ) continue;
|
|
||||||
?>
|
|
||||||
<div class="wbf-profile-sidebar__section">
|
<div class="wbf-profile-sidebar__section">
|
||||||
<span class="wbf-profile-sidebar__section-label">
|
<span class="wbf-profile-sidebar__section-label">
|
||||||
<i class="fas fa-<?php echo $def['type']==='url'?'link':($def['type']==='number'?'hashtag':'tag'); ?>"></i>
|
<i class="fas fa-<?php echo $def['type']==='url'?'link':($def['type']==='number'?'hashtag':'tag'); ?>"></i>
|
||||||
<?php echo esc_html($def['label']); ?>
|
<?php echo esc_html($def['label']); ?>
|
||||||
</span>
|
</span>
|
||||||
<?php if ( $def['type'] === 'url' ): ?>
|
<?php if ($def['type'] === 'url'): ?>
|
||||||
<a href="<?php echo esc_url($val); ?>" target="_blank" rel="noopener noreferrer"
|
<a href="<?php echo esc_url($val); ?>" target="_blank" rel="noopener noreferrer"
|
||||||
style="color:var(--c-primary);font-size:.85rem;word-break:break-all">
|
style="color:var(--c-primary);font-size:.85rem;word-break:break-all">
|
||||||
<?php echo esc_html( mb_strtolower( preg_replace('#^https?://#i','',$val) ) ); ?>
|
<?php echo esc_html(mb_strtolower(preg_replace('#^https?://#i','',$val))); ?>
|
||||||
</a>
|
</a>
|
||||||
<?php elseif ( $def['type'] === 'textarea' ): ?>
|
<?php elseif ($def['type'] === 'textarea'): ?>
|
||||||
<p style="font-size:.85rem"><?php echo nl2br(esc_html($val)); ?></p>
|
<p style="font-size:.85rem"><?php echo nl2br(esc_html($val)); ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p style="font-size:.85rem"><?php echo esc_html($val); ?></p>
|
<p style="font-size:.85rem"><?php echo esc_html($val); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- ── MAIN ────────────────────────────────────────────── -->
|
<!-- ── MAIN ────────────────────────────────────────────── -->
|
||||||
<div class="wbf-profile-main">
|
<div class="wbf-profile-main">
|
||||||
|
|
||||||
<!-- Profil bearbeiten (nur eigenes) -->
|
<!-- DM-Button + Ignorieren-Button (nur auf fremden Profilen) -->
|
||||||
|
<?php if ($current && !$is_own && WBF_Roles::level($profile->role) >= 0): ?>
|
||||||
|
<div style="display:flex;justify-content:flex-end;gap:.5rem;margin-bottom:.75rem;flex-wrap:wrap">
|
||||||
|
<a href="?forum_dm=inbox&with=<?php echo (int)$profile->id; ?>"
|
||||||
|
class="wbf-btn wbf-btn--sm wbf-btn--primary">
|
||||||
|
<i class="fas fa-envelope"></i> Nachricht senden
|
||||||
|
</a>
|
||||||
|
<?php if ( wbf_can_be_ignored($profile) ):
|
||||||
|
$viewer_ignores = WBF_DB::is_ignored($current->id, $profile->id); ?>
|
||||||
|
<button class="wbf-ignore-btn wbf-btn wbf-btn--sm<?php echo $viewer_ignores?' wbf-btn--primary':''; ?>"
|
||||||
|
data-id="<?php echo (int)$profile->id; ?>"
|
||||||
|
data-name="<?php echo esc_attr($profile->display_name); ?>"
|
||||||
|
data-ignored="<?php echo $viewer_ignores?'1':'0'; ?>">
|
||||||
|
<i class="fas fa-<?php echo $viewer_ignores?'eye':'eye-slash'; ?>"></i>
|
||||||
|
<?php echo $viewer_ignores?'Ignorierung aufheben':'Nutzer ignorieren'; ?>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- ── TAB-NAVIGATION ─────────────────────────────── -->
|
||||||
<?php if ($is_own): ?>
|
<?php if ($is_own): ?>
|
||||||
<div class="wbf-profile-card"> <div class="wbf-profile-card__header">
|
<div class="wbf-profile-tabs">
|
||||||
|
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=1"
|
||||||
|
class="wbf-profile-tab<?php echo $active_tab===1?' active':''; ?>">
|
||||||
|
<i class="fas fa-sliders"></i> Profil
|
||||||
|
</a>
|
||||||
|
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=2"
|
||||||
|
class="wbf-profile-tab<?php echo $active_tab===2?' active':''; ?>">
|
||||||
|
<i class="fas fa-comments"></i> Aktivität
|
||||||
|
</a>
|
||||||
|
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=3"
|
||||||
|
class="wbf-profile-tab<?php echo $active_tab===3?' active':''; ?>">
|
||||||
|
<i class="fas fa-shield-halved"></i> Privatsphäre
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════
|
||||||
|
TAB 1 — Profil bearbeiten + Weitere Profilangaben
|
||||||
|
══════════════════════════════════════════════════ -->
|
||||||
|
<?php if ($is_own && $active_tab === 1): ?>
|
||||||
|
|
||||||
|
<!-- Profil bearbeiten -->
|
||||||
|
<div class="wbf-profile-card">
|
||||||
|
<div class="wbf-profile-card__header">
|
||||||
<i class="fas fa-sliders"></i> Profil bearbeiten
|
<i class="fas fa-sliders"></i> Profil bearbeiten
|
||||||
</div>
|
</div>
|
||||||
<div class="wbf-profile-card__body">
|
<div class="wbf-profile-card__body">
|
||||||
@@ -995,7 +1073,10 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
<div class="wbf-form-row">
|
<div class="wbf-form-row">
|
||||||
<label>Signatur <small>(max. 300 Zeichen)</small></label>
|
<label>Signatur <small>(max. 300 Zeichen)</small></label>
|
||||||
<div class="wbf-form-row" style="display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem">
|
<textarea id="wbfEditSignature" rows="2" maxlength="300" placeholder="Deine Signatur…"><?php echo esc_textarea($profile->signature ?? ''); ?></textarea>
|
||||||
|
<div class="wbf-sig-counter"><span id="wbfSigCount"><?php echo mb_strlen($profile->signature??''); ?></span>/300</div>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-form-row" style="display:flex;align-items:center;gap:.75rem">
|
||||||
<label style="font-size:.82rem;color:var(--c-muted)">Profil öffentlich sichtbar</label>
|
<label style="font-size:.82rem;color:var(--c-muted)">Profil öffentlich sichtbar</label>
|
||||||
<?php $pub = (int)($profile->profile_public ?? 1); ?>
|
<?php $pub = (int)($profile->profile_public ?? 1); ?>
|
||||||
<button type="button" id="wbfToggleProfileVis"
|
<button type="button" id="wbfToggleProfileVis"
|
||||||
@@ -1005,9 +1086,6 @@ class WBF_Shortcodes {
|
|||||||
<?php echo $pub?'Öffentlich':'Privat'; ?>
|
<?php echo $pub?'Öffentlich':'Privat'; ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="wbfEditSignature" rows="2" maxlength="300" placeholder="Deine Signatur…"><?php echo esc_textarea($profile->signature ?? ''); ?></textarea>
|
|
||||||
<div class="wbf-sig-counter"><span id="wbfSigCount"><?php echo mb_strlen($profile->signature??''); ?></span>/300</div>
|
|
||||||
</div>
|
|
||||||
<div class="wbf-profile-card__footer">
|
<div class="wbf-profile-card__footer">
|
||||||
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile">
|
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile">
|
||||||
<i class="fas fa-save"></i> Speichern
|
<i class="fas fa-save"></i> Speichern
|
||||||
@@ -1017,43 +1095,67 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── Benutzerdefinierte Profilfelder ──────────────── -->
|
<!-- E-Mail-Adresse ändern -->
|
||||||
<?php
|
<div class="wbf-profile-card">
|
||||||
$cf_defs = WBF_DB::get_profile_field_defs();
|
<div class="wbf-profile-card__header">
|
||||||
$cf_vals = WBF_DB::get_user_meta( $profile->id );
|
<i class="fas fa-envelope"></i> E-Mail-Adresse
|
||||||
if ( ! empty( $cf_defs ) ):
|
</div>
|
||||||
?>
|
<div class="wbf-profile-card__body">
|
||||||
|
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:1rem">
|
||||||
|
Aktuelle Adresse: <strong style="color:var(--c-text)"><?php echo esc_html($profile->email); ?></strong>
|
||||||
|
</p>
|
||||||
|
<div class="wbf-profile-edit-grid">
|
||||||
|
<div class="wbf-form-row">
|
||||||
|
<label>Neue E-Mail-Adresse</label>
|
||||||
|
<input type="email" id="wbfNewEmail" placeholder="neue@email.de" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="wbf-form-row">
|
||||||
|
<label>Aktuelles Passwort <small>(zur Bestätigung)</small></label>
|
||||||
|
<input type="password" id="wbfEmailPassword" placeholder="••••••" autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__footer">
|
||||||
|
<button class="wbf-btn wbf-btn--primary" id="wbfSaveEmail">
|
||||||
|
<i class="fas fa-envelope"></i> E-Mail ändern
|
||||||
|
</button>
|
||||||
|
<span class="wbf-msg" id="wbfEmailMsg"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Weitere Profilangaben -->
|
||||||
|
<?php if (!empty($cf_defs)): ?>
|
||||||
<div class="wbf-profile-card">
|
<div class="wbf-profile-card">
|
||||||
<div class="wbf-profile-card__header">
|
<div class="wbf-profile-card__header">
|
||||||
<i class="fas fa-sliders"></i> Weitere Profilangaben
|
<i class="fas fa-sliders"></i> Weitere Profilangaben
|
||||||
</div>
|
</div>
|
||||||
<div class="wbf-profile-card__body">
|
<div class="wbf-profile-card__body">
|
||||||
<div class="wbf-profile-edit-grid">
|
<div class="wbf-profile-edit-grid">
|
||||||
<?php foreach ( $cf_defs as $def ):
|
<?php foreach ($cf_defs as $def):
|
||||||
$k = esc_attr( $def['key'] );
|
$k = esc_attr($def['key']);
|
||||||
$lbl = esc_html( $def['label'] );
|
$lbl = esc_html($def['label']);
|
||||||
$ph = esc_attr( $def['placeholder'] ?? '' );
|
$ph = esc_attr($def['placeholder'] ?? '');
|
||||||
$val = esc_attr( $cf_vals[ $def['key'] ] ?? '' );
|
$val = esc_attr($cf_vals[$def['key']] ?? '');
|
||||||
$req = ! empty($def['required']) ? 'required' : '';
|
$req = !empty($def['required']) ? 'required' : '';
|
||||||
?>
|
?>
|
||||||
<div class="wbf-form-row">
|
<div class="wbf-form-row">
|
||||||
<label><?php echo $lbl; ?><?php if($req): ?> <span style="color:var(--c-danger)">*</span><?php endif; ?></label>
|
<label><?php echo $lbl; ?><?php if($req): ?> <span style="color:var(--c-danger)">*</span><?php endif; ?></label>
|
||||||
<?php if ( $def['type'] === 'textarea' ): ?>
|
<?php if ($def['type'] === 'textarea'): ?>
|
||||||
<textarea class="wbf-cf-input" data-field="cf_<?php echo $k; ?>"
|
<textarea class="wbf-cf-input" data-field="cf_<?php echo $k; ?>"
|
||||||
rows="2" placeholder="<?php echo $ph; ?>"
|
rows="2" placeholder="<?php echo $ph; ?>"
|
||||||
<?php echo $req; ?>><?php echo esc_textarea( $cf_vals[$def['key']] ?? '' ); ?></textarea>
|
<?php echo $req; ?>><?php echo esc_textarea($cf_vals[$def['key']] ?? ''); ?></textarea>
|
||||||
<?php elseif ( $def['type'] === 'select' ):
|
<?php elseif ($def['type'] === 'select'):
|
||||||
$opts = array_filter( array_map('trim', explode("\n", $def['options'] ?? '')) );
|
$opts = array_filter(array_map('trim', explode("\n", $def['options'] ?? '')));
|
||||||
?>
|
?>
|
||||||
<select class="wbf-cf-input" data-field="cf_<?php echo $k; ?>">
|
<select class="wbf-cf-input" data-field="cf_<?php echo $k; ?>">
|
||||||
<option value="">— Bitte wählen —</option>
|
<option value="">— Bitte wählen —</option>
|
||||||
<?php foreach ( $opts as $opt ): ?>
|
<?php foreach ($opts as $opt): ?>
|
||||||
<option value="<?php echo esc_attr($opt); ?>"
|
<option value="<?php echo esc_attr($opt); ?>"
|
||||||
<?php selected( $cf_vals[$def['key']] ?? '', $opt ); ?>><?php echo esc_html($opt); ?></option>
|
<?php selected($cf_vals[$def['key']] ?? '', $opt); ?>><?php echo esc_html($opt); ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<input type="<?php echo $def['type'] === 'url' ? 'url' : ($def['type'] === 'number' ? 'number' : 'text'); ?>"
|
<input type="<?php echo $def['type']==='url'?'url':($def['type']==='number'?'number':'text'); ?>"
|
||||||
class="wbf-cf-input"
|
class="wbf-cf-input"
|
||||||
data-field="cf_<?php echo $k; ?>"
|
data-field="cf_<?php echo $k; ?>"
|
||||||
value="<?php echo $val; ?>"
|
value="<?php echo $val; ?>"
|
||||||
@@ -1073,95 +1175,15 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- ── DSGVO: Konto löschen ──────────────────────────── -->
|
<?php endif; /* end Tab 1 */ ?>
|
||||||
<div class="wbf-profile-card" style="border-color:rgba(240,82,82,.25)">
|
|
||||||
<div class="wbf-profile-card__header" style="color:var(--c-danger);background:rgba(240,82,82,.06);border-bottom-color:rgba(240,82,82,.15)">
|
|
||||||
<i class="fas fa-shield-halved"></i> Datenschutz & Konto löschen
|
|
||||||
</div>
|
|
||||||
<div class="wbf-profile-card__body">
|
|
||||||
<p style="font-size:.85rem;color:var(--c-text-dim);margin-bottom:1rem;line-height:1.6">
|
|
||||||
Gemäß <strong>DSGVO Art. 17</strong> (Recht auf Vergessenwerden) kannst du die vollständige Löschung deines Kontos und aller personenbezogenen Daten beantragen.<br>
|
|
||||||
<span style="color:var(--c-muted);font-size:.8rem">Deine Beiträge bleiben anonymisiert sichtbar. Direktnachrichten, Likes, Profilinformationen und alle persönlichen Daten werden dauerhaft gelöscht.</span>
|
|
||||||
</p>
|
|
||||||
<div id="wbfGdprBox" style="background:rgba(240,82,82,.06);border:1px solid rgba(240,82,82,.2);border-radius:var(--radius-sm);padding:1.1rem;display:none">
|
|
||||||
<p style="font-size:.82rem;font-weight:700;color:var(--c-danger);margin-bottom:.9rem"><i class="fas fa-triangle-exclamation"></i> Diese Aktion ist unwiderruflich.</p>
|
|
||||||
<div class="wbf-form-row">
|
|
||||||
<label style="font-size:.72rem">Passwort zur Bestätigung</label>
|
|
||||||
<input type="password" id="wbfGdprPassword" placeholder="Dein aktuelles Passwort" autocomplete="current-password">
|
|
||||||
</div>
|
|
||||||
<label style="display:flex;align-items:center;gap:.6rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:1rem">
|
|
||||||
<input type="checkbox" id="wbfGdprConfirm" style="width:15px;height:15px;accent-color:var(--c-danger);cursor:pointer">
|
|
||||||
Ich verstehe, dass mein Konto und alle persönlichen Daten unwiderruflich gelöscht werden.
|
|
||||||
</label>
|
|
||||||
<div style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap">
|
|
||||||
<button class="wbf-btn wbf-btn--sm" id="wbfGdprCancel" onclick="document.getElementById('wbfGdprBox').style.display='none';document.getElementById('wbfGdprToggle').style.display=''">
|
|
||||||
<i class="fas fa-xmark"></i> Abbrechen
|
|
||||||
</button>
|
|
||||||
<button class="wbf-btn wbf-btn--sm" id="wbfGdprSubmit"
|
|
||||||
style="background:rgba(240,82,82,.15);color:var(--c-danger);border-color:rgba(240,82,82,.4)">
|
|
||||||
<i class="fas fa-trash-can"></i> Konto endgültig löschen
|
|
||||||
</button>
|
|
||||||
<span class="wbf-msg" id="wbfGdprMsg"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="wbf-btn wbf-btn--sm" id="wbfGdprToggle"
|
|
||||||
style="background:rgba(240,82,82,.08);color:var(--c-danger);border-color:rgba(240,82,82,.3)"
|
|
||||||
onclick="document.getElementById('wbfGdprBox').style.display='';document.getElementById('wbfGdprToggle').style.display='none'">
|
|
||||||
<i class="fas fa-trash-can"></i> Konto löschen (DSGVO Art. 17)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php endif; /* end $is_own */ ?>
|
<!-- ══════════════════════════════════════════════════
|
||||||
|
TAB 2 — Lesezeichen + Beiträge
|
||||||
<!-- Beiträge -->
|
══════════════════════════════════════════════════ -->
|
||||||
<div class="wbf-profile-card">
|
<?php if ($active_tab === 2): ?>
|
||||||
<div class="wbf-profile-card__header">
|
|
||||||
<i class="fas fa-comments"></i> Beiträge
|
|
||||||
<span class="wbf-profile-card__count"><?php echo count($user_posts); ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="wbf-profile-card__body wbf-profile-card__body--posts">
|
|
||||||
<?php if (empty($user_posts)): ?>
|
|
||||||
<p class="wbf-profile-empty">Noch keine Beiträge.</p>
|
|
||||||
<?php else:
|
|
||||||
foreach ($user_posts as $up):
|
|
||||||
$preview = esc_html( mb_substr( strip_tags($up->content), 0, 130 ) );
|
|
||||||
$more = mb_strlen( strip_tags($up->content) ) > 130 ? '…' : '';
|
|
||||||
$is_thread = isset($up->entry_type) && $up->entry_type === 'thread';
|
|
||||||
$anchor = $is_thread
|
|
||||||
? '?forum_thread=' . (int)$up->thread_id
|
|
||||||
: '?forum_thread=' . (int)$up->thread_id . '#post-' . (int)$up->id;
|
|
||||||
?>
|
|
||||||
<div class="wbf-profile-post-item">
|
|
||||||
<div class="wbf-profile-post-item__top">
|
|
||||||
<?php if ($is_thread): ?>
|
|
||||||
<span class="wbf-profile-post-item__type wbf-profile-post-item__type--thread">
|
|
||||||
<i class="fas fa-layer-group"></i> Thread
|
|
||||||
</span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="wbf-profile-post-item__type wbf-profile-post-item__type--reply">
|
|
||||||
<i class="fas fa-reply"></i> Antwort
|
|
||||||
</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
<a href="<?php echo esc_url($anchor); ?>" class="wbf-profile-post-item__title">
|
|
||||||
<?php echo esc_html( mb_substr($up->thread_title, 0, 60) ); ?>
|
|
||||||
</a>
|
|
||||||
<span class="wbf-profile-post-item__cat">
|
|
||||||
<i class="fas fa-folder"></i> <?php echo esc_html($up->cat_name); ?>
|
|
||||||
</span>
|
|
||||||
<span class="wbf-profile-post-item__time"><?php echo self::time_ago($up->created_at); ?></span>
|
|
||||||
</div>
|
|
||||||
<?php if ($preview): ?>
|
|
||||||
<p class="wbf-profile-post-item__preview"><?php echo $preview . $more; ?></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Lesezeichen (nur eigenes Profil) -->
|
<!-- Lesezeichen (nur eigenes Profil) -->
|
||||||
<?php if ($is_own):
|
<?php if ($is_own): ?>
|
||||||
$bookmarks = WBF_DB::get_user_bookmarks($current->id, 50); ?>
|
|
||||||
<div class="wbf-profile-card">
|
<div class="wbf-profile-card">
|
||||||
<div class="wbf-profile-card__header">
|
<div class="wbf-profile-card__header">
|
||||||
<i class="fas fa-bookmark"></i> Lesezeichen
|
<i class="fas fa-bookmark"></i> Lesezeichen
|
||||||
@@ -1186,6 +1208,192 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Beiträge -->
|
||||||
|
<div class="wbf-profile-card">
|
||||||
|
<div class="wbf-profile-card__header">
|
||||||
|
<i class="fas fa-comments"></i> Beiträge
|
||||||
|
<span class="wbf-profile-card__count"><?php echo count($user_posts); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__body wbf-profile-card__body--posts">
|
||||||
|
<?php if (empty($user_posts)): ?>
|
||||||
|
<p class="wbf-profile-empty">Noch keine Beiträge.</p>
|
||||||
|
<?php else:
|
||||||
|
foreach ($user_posts as $up):
|
||||||
|
$preview = esc_html(mb_substr(strip_tags($up->content), 0, 130));
|
||||||
|
$more = mb_strlen(strip_tags($up->content)) > 130 ? '…' : '';
|
||||||
|
$is_thread = isset($up->entry_type) && $up->entry_type === 'thread';
|
||||||
|
$anchor = $is_thread
|
||||||
|
? '?forum_thread=' . (int)$up->thread_id
|
||||||
|
: '?forum_thread=' . (int)$up->thread_id . '#post-' . (int)$up->id;
|
||||||
|
?>
|
||||||
|
<div class="wbf-profile-post-item">
|
||||||
|
<div class="wbf-profile-post-item__top">
|
||||||
|
<?php if ($is_thread): ?>
|
||||||
|
<span class="wbf-profile-post-item__type wbf-profile-post-item__type--thread">
|
||||||
|
<i class="fas fa-layer-group"></i> Thread
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="wbf-profile-post-item__type wbf-profile-post-item__type--reply">
|
||||||
|
<i class="fas fa-reply"></i> Antwort
|
||||||
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<a href="<?php echo esc_url($anchor); ?>" class="wbf-profile-post-item__title">
|
||||||
|
<?php echo esc_html(mb_substr($up->thread_title, 0, 60)); ?>
|
||||||
|
</a>
|
||||||
|
<span class="wbf-profile-post-item__cat">
|
||||||
|
<i class="fas fa-folder"></i> <?php echo esc_html($up->cat_name); ?>
|
||||||
|
</span>
|
||||||
|
<span class="wbf-profile-post-item__time"><?php echo self::time_ago($up->created_at); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php if ($preview): ?>
|
||||||
|
<p class="wbf-profile-post-item__preview"><?php echo $preview . $more; ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; /* end Tab 2 */ ?>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════
|
||||||
|
TAB 3 — Ignorierte Nutzer + Datenschutz
|
||||||
|
══════════════════════════════════════════════════ -->
|
||||||
|
<?php if ($is_own && $active_tab === 3): ?>
|
||||||
|
|
||||||
|
<!-- Ignorierte Nutzer -->
|
||||||
|
<div class="wbf-profile-card">
|
||||||
|
<div class="wbf-profile-card__header">
|
||||||
|
<i class="fas fa-bell"></i> E-Mail-Benachrichtigungen
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__body">
|
||||||
|
<?php
|
||||||
|
$notif_meta = WBF_DB::get_user_meta($current->id);
|
||||||
|
$n_reply = ($notif_meta['notify_reply'] ?? '1') !== '0';
|
||||||
|
$n_mention = ($notif_meta['notify_mention'] ?? '1') !== '0';
|
||||||
|
$n_message = ($notif_meta['notify_message'] ?? '1') !== '0';
|
||||||
|
?>
|
||||||
|
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:1rem">
|
||||||
|
Lege fest bei welchen Ereignissen du eine E-Mail erhältst.
|
||||||
|
</p>
|
||||||
|
<div class="wbf-notif-pref-list">
|
||||||
|
<label class="wbf-notif-pref">
|
||||||
|
<div class="wbf-notif-pref__info">
|
||||||
|
<span><i class="fas fa-reply"></i> Antworten auf meine Threads</span>
|
||||||
|
<small>Wenn jemand in einem deiner Threads antwortet</small>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-toggle<?php echo $n_reply?' wbf-toggle--on':''; ?>"
|
||||||
|
id="wbfNotifReply" data-key="notify_reply" data-state="<?php echo $n_reply?'1':'0'; ?>">
|
||||||
|
<div class="wbf-toggle__knob"></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="wbf-notif-pref">
|
||||||
|
<div class="wbf-notif-pref__info">
|
||||||
|
<span><i class="fas fa-at"></i> @Erwähnungen</span>
|
||||||
|
<small>Wenn dich jemand in einem Beitrag erwähnt</small>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-toggle<?php echo $n_mention?' wbf-toggle--on':''; ?>"
|
||||||
|
id="wbfNotifMention" data-key="notify_mention" data-state="<?php echo $n_mention?'1':'0'; ?>">
|
||||||
|
<div class="wbf-toggle__knob"></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="wbf-notif-pref">
|
||||||
|
<div class="wbf-notif-pref__info">
|
||||||
|
<span><i class="fas fa-envelope"></i> Privatnachrichten</span>
|
||||||
|
<small>Wenn du eine neue Direktnachricht erhältst</small>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-toggle<?php echo $n_message?' wbf-toggle--on':''; ?>"
|
||||||
|
id="wbfNotifMessage" data-key="notify_message" data-state="<?php echo $n_message?'1':'0'; ?>">
|
||||||
|
<div class="wbf-toggle__knob"></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__footer" style="margin-top:1rem">
|
||||||
|
<button class="wbf-btn wbf-btn--primary" id="wbfSaveNotifPrefs">
|
||||||
|
<i class="fas fa-save"></i> Einstellungen speichern
|
||||||
|
</button>
|
||||||
|
<span class="wbf-msg" id="wbfNotifPrefsMsg"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ignorierte Nutzer -->
|
||||||
|
<div class="wbf-profile-card">
|
||||||
|
<div class="wbf-profile-card__header">
|
||||||
|
<i class="fas fa-eye-slash"></i> Ignorierte Nutzer
|
||||||
|
<span class="wbf-profile-card__count" id="wbfIgnoreCount"><?php echo count($ignore_list); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__body" id="wbfIgnoreListWrap">
|
||||||
|
<?php if (empty($ignore_list)): ?>
|
||||||
|
<p class="wbf-profile-empty" id="wbfIgnoreEmpty">Du ignorierst niemanden.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="wbf-ignore-list" id="wbfIgnoreList">
|
||||||
|
<?php foreach ($ignore_list as $ign): ?>
|
||||||
|
<div class="wbf-ignore-item" id="wbf-ignore-item-<?php echo (int)$ign->id; ?>">
|
||||||
|
<a href="?forum_profile=<?php echo (int)$ign->id; ?>" class="wbf-ignore-item__avatar">
|
||||||
|
<?php echo self::avatar($ign->avatar_url, $ign->display_name, 36); ?>
|
||||||
|
</a>
|
||||||
|
<div class="wbf-ignore-item__info">
|
||||||
|
<a href="?forum_profile=<?php echo (int)$ign->id; ?>" class="wbf-ignore-item__name">
|
||||||
|
<?php echo esc_html($ign->display_name); ?>
|
||||||
|
</a>
|
||||||
|
<span class="wbf-ignore-item__since">Ignoriert seit <?php echo self::time_ago($ign->ignored_since); ?></span>
|
||||||
|
</div>
|
||||||
|
<button class="wbf-ignore-btn wbf-btn wbf-btn--sm"
|
||||||
|
data-id="<?php echo (int)$ign->id; ?>"
|
||||||
|
data-name="<?php echo esc_attr($ign->display_name); ?>"
|
||||||
|
data-ignored="1"
|
||||||
|
style="margin-left:auto">
|
||||||
|
<i class="fas fa-eye"></i> Entblocken
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Datenschutz & Konto löschen -->
|
||||||
|
<div class="wbf-profile-card" style="border-color:rgba(240,82,82,.25)">
|
||||||
|
<div class="wbf-profile-card__header" style="color:var(--c-danger);background:rgba(240,82,82,.06);border-bottom-color:rgba(240,82,82,.15)">
|
||||||
|
<i class="fas fa-shield-halved"></i> Datenschutz & Konto löschen
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__body">
|
||||||
|
<p style="font-size:.85rem;color:var(--c-text-dim);margin-bottom:1rem;line-height:1.6">
|
||||||
|
Gemäß <strong>DSGVO Art. 17</strong> (Recht auf Vergessenwerden) kannst du die vollständige Löschung deines Kontos und aller personenbezogenen Daten beantragen.<br>
|
||||||
|
<span style="color:var(--c-muted);font-size:.8rem">Deine Beiträge bleiben anonymisiert sichtbar. Direktnachrichten, Likes, Profilinformationen und alle persönlichen Daten werden dauerhaft gelöscht.</span>
|
||||||
|
</p>
|
||||||
|
<div id="wbfGdprBox" style="background:rgba(240,82,82,.06);border:1px solid rgba(240,82,82,.2);border-radius:var(--radius-sm);padding:1.1rem;display:none">
|
||||||
|
<p style="font-size:.82rem;font-weight:700;color:var(--c-danger);margin-bottom:.9rem"><i class="fas fa-triangle-exclamation"></i> Diese Aktion ist unwiderruflich.</p>
|
||||||
|
<div class="wbf-form-row">
|
||||||
|
<label style="font-size:.72rem">Passwort zur Bestätigung</label>
|
||||||
|
<input type="password" id="wbfGdprPassword" placeholder="Dein aktuelles Passwort" autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
<label style="display:flex;align-items:center;gap:.6rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:1rem">
|
||||||
|
<input type="checkbox" id="wbfGdprConfirm" style="width:15px;height:15px;accent-color:var(--c-danger);cursor:pointer">
|
||||||
|
Ich verstehe, dass mein Konto und alle persönlichen Daten unwiderruflich gelöscht werden.
|
||||||
|
</label>
|
||||||
|
<div style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap">
|
||||||
|
<button class="wbf-btn wbf-btn--sm" id="wbfGdprCancel"
|
||||||
|
onclick="document.getElementById('wbfGdprBox').style.display='none';document.getElementById('wbfGdprToggle').style.display=''">
|
||||||
|
<i class="fas fa-xmark"></i> Abbrechen
|
||||||
|
</button>
|
||||||
|
<button class="wbf-btn wbf-btn--sm" id="wbfGdprSubmit"
|
||||||
|
style="background:rgba(240,82,82,.15);color:var(--c-danger);border-color:rgba(240,82,82,.4)">
|
||||||
|
<i class="fas fa-trash-can"></i> Konto endgültig löschen
|
||||||
|
</button>
|
||||||
|
<span class="wbf-msg" id="wbfGdprMsg"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="wbf-btn wbf-btn--sm" id="wbfGdprToggle"
|
||||||
|
style="background:rgba(240,82,82,.08);color:var(--c-danger);border-color:rgba(240,82,82,.3)"
|
||||||
|
onclick="document.getElementById('wbfGdprBox').style.display='';document.getElementById('wbfGdprToggle').style.display='none'">
|
||||||
|
<i class="fas fa-trash-can"></i> Konto löschen (DSGVO Art. 17)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; /* end Tab 3 */ ?>
|
||||||
|
|
||||||
</div><!-- /.wbf-profile-main -->
|
</div><!-- /.wbf-profile-main -->
|
||||||
</div><!-- /.wbf-profile-layout -->
|
</div><!-- /.wbf-profile-layout -->
|
||||||
</div>
|
</div>
|
||||||
@@ -1193,6 +1401,7 @@ class WBF_Shortcodes {
|
|||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── TAG PAGE ─────────────────────────────────────────────────────────────
|
// ── TAG PAGE ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private static function view_tag() {
|
private static function view_tag() {
|
||||||
@@ -1274,8 +1483,8 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
|
||||||
<?php self::render_forum_footer(); ?>
|
<?php self::render_forum_footer(); ?>
|
||||||
|
</div>
|
||||||
<?php self::render_auth_modal(); ?>
|
<?php self::render_auth_modal(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
@@ -1355,8 +1564,8 @@ class WBF_Shortcodes {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<?php self::render_forum_footer(); ?>
|
<?php self::render_forum_footer(); ?>
|
||||||
|
</div>
|
||||||
<?php self::render_auth_modal(); ?>
|
<?php self::render_auth_modal(); ?>
|
||||||
<?php self::render_dm_compose_modal(); ?>
|
<?php self::render_dm_compose_modal(); ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -1443,8 +1652,8 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
<p style="color:var(--c-muted);font-size:.82rem;margin-top:1rem"><?php echo count($results); ?> Ergebnis(se) gefunden.</p>
|
<p style="color:var(--c-muted);font-size:.82rem;margin-top:1rem"><?php echo count($results); ?> Ergebnis(se) gefunden.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
|
||||||
<?php self::render_forum_footer(); ?>
|
<?php self::render_forum_footer(); ?>
|
||||||
|
</div>
|
||||||
<?php self::render_auth_modal(); ?>
|
<?php self::render_auth_modal(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
@@ -2080,8 +2289,8 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
</div>
|
|
||||||
<?php self::render_forum_footer(); ?>
|
<?php self::render_forum_footer(); ?>
|
||||||
|
</div>
|
||||||
<?php self::render_auth_modal(); ?>
|
<?php self::render_auth_modal(); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
@@ -2120,7 +2329,7 @@ class WBF_Shortcodes {
|
|||||||
if ( ( wbf_get_settings()['rules_enabled'] ?? '1' ) !== '1' ) return;
|
if ( ( wbf_get_settings()['rules_enabled'] ?? '1' ) !== '1' ) return;
|
||||||
$rules_url = esc_url( wbf_get_forum_url() . '?forum_rules=1' );
|
$rules_url = esc_url( wbf_get_forum_url() . '?forum_rules=1' );
|
||||||
?>
|
?>
|
||||||
<div style="border-top:1px solid var(--c-border);margin-top:3rem;padding:1.25rem 1.5rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;background:var(--c-bg2)">
|
<div style="border:1px solid var(--c-border);border-radius:var(--radius);margin-top:2rem;padding:1rem 1.25rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;background:var(--c-surface)">
|
||||||
<span style="font-size:.78rem;color:var(--c-muted)">
|
<span style="font-size:.78rem;color:var(--c-muted)">
|
||||||
<i class="fas fa-shield-halved" style="color:var(--c-primary);margin-right:.35rem"></i>
|
<i class="fas fa-shield-halved" style="color:var(--c-primary);margin-right:.35rem"></i>
|
||||||
Durch die Nutzung des Forums stimmst du unseren Regeln zu.
|
Durch die Nutzung des Forums stimmst du unseren Regeln zu.
|
||||||
|
|||||||
@@ -20,9 +20,12 @@ $tables = [
|
|||||||
'forum_reactions',
|
'forum_reactions',
|
||||||
'forum_notifications',
|
'forum_notifications',
|
||||||
'forum_subscriptions',
|
'forum_subscriptions',
|
||||||
|
'forum_bookmarks', // ← fehlte: Lesezeichen
|
||||||
|
'forum_ignore_list', // ← Ignore/Block-Liste
|
||||||
'forum_invites',
|
'forum_invites',
|
||||||
'forum_thread_tags',
|
'forum_thread_tags',
|
||||||
'forum_tags',
|
'forum_tags',
|
||||||
|
'forum_prefixes', // ← fehlte: Thread-Präfixe
|
||||||
'forum_reports',
|
'forum_reports',
|
||||||
'forum_likes',
|
'forum_likes',
|
||||||
'forum_messages',
|
'forum_messages',
|
||||||
@@ -49,6 +52,7 @@ $options = [
|
|||||||
'wbf_forum_page_id',
|
'wbf_forum_page_id',
|
||||||
'wbf_superadmin_email',
|
'wbf_superadmin_email',
|
||||||
'wbf_db_version',
|
'wbf_db_version',
|
||||||
|
'wbf_word_filter',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ( $options as $option ) {
|
foreach ( $options as $option ) {
|
||||||
@@ -65,8 +69,9 @@ if ( is_multisite() ) {
|
|||||||
// ── 3. Transients löschen ────────────────────────────────────────────────────
|
// ── 3. Transients löschen ────────────────────────────────────────────────────
|
||||||
delete_transient( 'wbf_activation_redirect' );
|
delete_transient( 'wbf_activation_redirect' );
|
||||||
delete_transient( 'wbf_stats_cache' );
|
delete_transient( 'wbf_stats_cache' );
|
||||||
|
delete_transient( 'wbf_update_check' );
|
||||||
|
|
||||||
// Alle wbf_* Transients per LIKE-Query entfernen
|
// Alle wbf_* Transients per LIKE-Query entfernen (inkl. Update-Dismissed-Transients)
|
||||||
$wpdb->query(
|
$wpdb->query(
|
||||||
"DELETE FROM `{$wpdb->options}`
|
"DELETE FROM `{$wpdb->options}`
|
||||||
WHERE `option_name` LIKE '_transient_wbf_%'
|
WHERE `option_name` LIKE '_transient_wbf_%'
|
||||||
@@ -75,6 +80,7 @@ $wpdb->query(
|
|||||||
|
|
||||||
// ── 4. Geplante Cron-Jobs entfernen ──────────────────────────────────────────
|
// ── 4. Geplante Cron-Jobs entfernen ──────────────────────────────────────────
|
||||||
wp_clear_scheduled_hook( 'wbf_check_expired_bans' );
|
wp_clear_scheduled_hook( 'wbf_check_expired_bans' );
|
||||||
|
wp_clear_scheduled_hook( 'wbf_check_for_updates' );
|
||||||
|
|
||||||
// ── 5. Forum-Seite löschen (vom Setup-Wizard erstellt) ───────────────────────
|
// ── 5. Forum-Seite löschen (vom Setup-Wizard erstellt) ───────────────────────
|
||||||
$forum_page_id = get_option( 'wbf_forum_page_id' );
|
$forum_page_id = get_option( 'wbf_forum_page_id' );
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WP Business Forum
|
* Plugin Name: WP Business Forum
|
||||||
* Plugin URI: https://git.viper.ipv64.net/M_Viper/WP-Business-Forum
|
* Plugin URI: https://git.viper.ipv64.net/M_Viper/WP-Business-Forum
|
||||||
* Description: Professionelles Forum mit eigenem Login, Rollen, Signaturen, Hierarchie und Moderations-Tools.
|
* Description: Professionelles Forum mit eigenem Login, Rollen, Signaturen, Hierarchie und Moderations-Tools.
|
||||||
* Version: 1.0.1
|
* Version: 1.0.2
|
||||||
* Author: M_Viper
|
* Author: M_Viper
|
||||||
* Author URI: https://m-viper.de
|
* Author URI: https://m-viper.de
|
||||||
* Text Domain: wp-business-forum
|
* Text Domain: wp-business-forum
|
||||||
@@ -13,7 +13,7 @@ if ( ! defined( 'ABSPATH' ) ) exit;
|
|||||||
|
|
||||||
define( 'WBF_PATH', plugin_dir_path( __FILE__ ) );
|
define( 'WBF_PATH', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'WBF_URL', plugin_dir_url( __FILE__ ) );
|
define( 'WBF_URL', plugin_dir_url( __FILE__ ) );
|
||||||
define( 'WBF_VERSION', '1.0.1' );
|
define( 'WBF_VERSION', '1.0.2' );
|
||||||
|
|
||||||
require_once WBF_PATH . 'includes/class-forum-db.php';
|
require_once WBF_PATH . 'includes/class-forum-db.php';
|
||||||
require_once WBF_PATH . 'includes/class-forum-roles.php';
|
require_once WBF_PATH . 'includes/class-forum-roles.php';
|
||||||
@@ -22,6 +22,7 @@ require_once WBF_PATH . 'includes/class-forum-bbcode.php';
|
|||||||
require_once WBF_PATH . 'includes/class-forum-auth.php';
|
require_once WBF_PATH . 'includes/class-forum-auth.php';
|
||||||
require_once WBF_PATH . 'includes/class-forum-shortcodes.php';
|
require_once WBF_PATH . 'includes/class-forum-shortcodes.php';
|
||||||
require_once WBF_PATH . 'includes/class-forum-ajax.php';
|
require_once WBF_PATH . 'includes/class-forum-ajax.php';
|
||||||
|
require_once WBF_PATH . 'includes/class-forum-export.php';
|
||||||
require_once WBF_PATH . 'admin/forum-admin.php';
|
require_once WBF_PATH . 'admin/forum-admin.php';
|
||||||
require_once WBF_PATH . 'admin/forum-settings.php';
|
require_once WBF_PATH . 'admin/forum-settings.php';
|
||||||
require_once WBF_PATH . 'admin/forum-setup.php';
|
require_once WBF_PATH . 'admin/forum-setup.php';
|
||||||
@@ -33,6 +34,11 @@ register_activation_hook( __FILE__, function() {
|
|||||||
set_transient( 'wbf_activation_redirect', true, 30 );
|
set_transient( 'wbf_activation_redirect', true, 30 );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Export / Import Hooks ─────────────────────────────────────────────────────
|
||||||
|
add_action( 'plugins_loaded', function() {
|
||||||
|
WBF_Export::hooks();
|
||||||
|
}, 5 );
|
||||||
|
|
||||||
// ── Superadmin-Sync ───────────────────────────────────────────────────────────
|
// ── Superadmin-Sync ───────────────────────────────────────────────────────────
|
||||||
add_action( 'wp_login', function() { WBF_Roles::sync_superadmin(); } );
|
add_action( 'wp_login', function() { WBF_Roles::sync_superadmin(); } );
|
||||||
add_action( 'init', function() { WBF_Roles::sync_superadmin(); } );
|
add_action( 'init', function() { WBF_Roles::sync_superadmin(); } );
|
||||||
@@ -57,6 +63,7 @@ if ( ! wp_next_scheduled( 'wbf_check_expired_bans' ) ) {
|
|||||||
|
|
||||||
register_deactivation_hook( __FILE__, function() {
|
register_deactivation_hook( __FILE__, function() {
|
||||||
wp_clear_scheduled_hook( 'wbf_check_expired_bans' );
|
wp_clear_scheduled_hook( 'wbf_check_expired_bans' );
|
||||||
|
wp_clear_scheduled_hook( 'wbf_check_for_updates' );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
@@ -98,4 +105,156 @@ add_action( 'wp_enqueue_scripts', function() {
|
|||||||
'forum_url' => wbf_get_forum_url(),
|
'forum_url' => wbf_get_forum_url(),
|
||||||
'reactions' => WBF_DB::get_allowed_reactions(),
|
'reactions' => WBF_DB::get_allowed_reactions(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
// ── Update-Checker ────────────────────────────────────────────────────────────
|
||||||
|
// Prüft täglich gegen die Gitea-Releases-API ob eine neue Version verfügbar ist.
|
||||||
|
// Releases-URL: https://git.viper.ipv64.net/M_Viper/WP-Business-Forum/releases
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
define( 'WBF_UPDATE_API', 'https://git.viper.ipv64.net/api/v1/repos/M_Viper/WP-Business-Forum/releases?limit=1&page=1' );
|
||||||
|
define( 'WBF_RELEASES_PAGE', 'https://git.viper.ipv64.net/M_Viper/WP-Business-Forum/releases' );
|
||||||
|
define( 'WBF_UPDATE_TRANSIENT','wbf_update_check' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt die neueste Release-Info von Gitea (gecacht per Transient, 12h).
|
||||||
|
* Gibt null zurück wenn kein Update verfügbar oder API nicht erreichbar.
|
||||||
|
*
|
||||||
|
* @return array|null ['version'=>string, 'url'=>string, 'name'=>string, 'published'=>string, 'body'=>string]
|
||||||
|
*/
|
||||||
|
function wbf_get_latest_release() {
|
||||||
|
$cached = get_transient( WBF_UPDATE_TRANSIENT );
|
||||||
|
if ( $cached !== false ) {
|
||||||
|
return $cached ?: null; // false = noch nie gecacht, '' = kein Update
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_get( WBF_UPDATE_API, [
|
||||||
|
'timeout' => 8,
|
||||||
|
'user-agent' => 'WP-Business-Forum/' . WBF_VERSION . '; ' . get_bloginfo('url'),
|
||||||
|
'sslverify' => true,
|
||||||
|
] );
|
||||||
|
|
||||||
|
if ( is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200 ) {
|
||||||
|
// Bei Fehler 1h warten bevor erneut versucht
|
||||||
|
set_transient( WBF_UPDATE_TRANSIENT, '', HOUR_IN_SECONDS );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
$releases = json_decode( $body, true );
|
||||||
|
|
||||||
|
if ( empty($releases) || ! is_array($releases) || empty($releases[0]) ) {
|
||||||
|
set_transient( WBF_UPDATE_TRANSIENT, '', 12 * HOUR_IN_SECONDS );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latest = $releases[0];
|
||||||
|
$version = ltrim( $latest['tag_name'] ?? '', 'v' ); // "v1.2.0" → "1.2.0"
|
||||||
|
|
||||||
|
$info = [
|
||||||
|
'version' => $version,
|
||||||
|
'url' => $latest['html_url'] ?? WBF_RELEASES_PAGE,
|
||||||
|
'name' => $latest['name'] ?? $latest['tag_name'] ?? $version,
|
||||||
|
'published' => $latest['published_at'] ?? '',
|
||||||
|
'body' => wp_strip_all_tags( $latest['body'] ?? '' ),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 12 Stunden cachen
|
||||||
|
set_transient( WBF_UPDATE_TRANSIENT, $info, 12 * HOUR_IN_SECONDS );
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft ob ein Update verfügbar ist.
|
||||||
|
* Gibt die Release-Info zurück wenn Gitea-Version > installierte Version.
|
||||||
|
*/
|
||||||
|
function wbf_update_available() {
|
||||||
|
$latest = wbf_get_latest_release();
|
||||||
|
if ( ! $latest || empty($latest['version']) ) return null;
|
||||||
|
if ( version_compare( $latest['version'], WBF_VERSION, '>' ) ) {
|
||||||
|
return $latest;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cron: täglich Update prüfen (Cache warm halten) ──────────────────────────
|
||||||
|
add_action( 'wbf_check_for_updates', function() {
|
||||||
|
delete_transient( WBF_UPDATE_TRANSIENT );
|
||||||
|
wbf_get_latest_release();
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( ! wp_next_scheduled( 'wbf_check_for_updates' ) ) {
|
||||||
|
wp_schedule_event( time(), 'twicedaily', 'wbf_check_for_updates' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Admin-Notice wenn Update verfügbar ───────────────────────────────────────
|
||||||
|
add_action( 'admin_notices', function() {
|
||||||
|
if ( ! current_user_can('manage_options') ) return;
|
||||||
|
|
||||||
|
$update = wbf_update_available();
|
||||||
|
if ( ! $update ) return;
|
||||||
|
|
||||||
|
// Notice ausblenden wenn der User sie weggeklickt hat (per GET-Parameter)
|
||||||
|
if ( isset($_GET['wbf_dismiss_update']) && check_admin_referer('wbf_dismiss_update') ) {
|
||||||
|
set_transient( 'wbf_update_dismissed_' . WBF_VERSION, $update['version'], 7 * DAY_IN_SECONDS );
|
||||||
|
wp_safe_redirect( remove_query_arg(['wbf_dismiss_update','_wpnonce']) );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dismissed = get_transient( 'wbf_update_dismissed_' . WBF_VERSION );
|
||||||
|
if ( $dismissed === $update['version'] ) return;
|
||||||
|
|
||||||
|
$dismiss_url = wp_nonce_url(
|
||||||
|
add_query_arg('wbf_dismiss_update', '1'),
|
||||||
|
'wbf_dismiss_update'
|
||||||
|
);
|
||||||
|
$changelog_url = esc_url( $update['url'] );
|
||||||
|
$new_ver = esc_html( $update['version'] );
|
||||||
|
$cur_ver = esc_html( WBF_VERSION );
|
||||||
|
|
||||||
|
echo "
|
||||||
|
<div class=\"notice notice-warning is-dismissible\" style=\"border-left-color:#f59e0b;padding:12px 15px\">
|
||||||
|
<div style=\"display:flex;align-items:center;gap:14px;flex-wrap:wrap\">
|
||||||
|
<span style=\"font-size:1.6rem\">🔔</span>
|
||||||
|
<div>
|
||||||
|
<strong style=\"font-size:.95rem\">WP Business Forum — Update verfügbar!</strong>
|
||||||
|
<p style=\"margin:.3rem 0 0;color:#374151\">
|
||||||
|
Version <strong>{$new_ver}</strong> ist verfügbar. Du verwendest <strong>{$cur_ver}</strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style=\"display:flex;gap:8px;margin-left:auto\">
|
||||||
|
<a href=\"{$changelog_url}\" target=\"_blank\" rel=\"noopener\"
|
||||||
|
class=\"button button-primary\" style=\"background:#f59e0b;border-color:#d97706\">
|
||||||
|
📋 Changelog & Download
|
||||||
|
</a>
|
||||||
|
<a href=\"" . esc_url($dismiss_url) . "\" class=\"button\">Später erinnern</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>";
|
||||||
|
} );
|
||||||
|
|
||||||
|
// ── Update-Badge im WP-Admin-Menü ─────────────────────────────────────────────
|
||||||
|
add_action( 'admin_menu', function() {
|
||||||
|
$update = wbf_update_available();
|
||||||
|
if ( ! $update ) return;
|
||||||
|
global $menu;
|
||||||
|
if ( ! is_array($menu) ) return;
|
||||||
|
foreach ( $menu as &$item ) {
|
||||||
|
if ( isset($item[2]) && $item[2] === 'wbf-admin' ) {
|
||||||
|
$item[0] .= ' <span class="update-plugins"><span class="plugin-count">1</span></span>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 999 );
|
||||||
|
|
||||||
|
// ── Manuellen Cache-Reset erlauben (für die Admin-UI) ─────────────────────────
|
||||||
|
add_action( 'admin_init', function() {
|
||||||
|
if ( ! isset($_GET['wbf_refresh_update']) ) return;
|
||||||
|
if ( ! current_user_can('manage_options') ) return;
|
||||||
|
if ( ! check_admin_referer('wbf_refresh_update') ) return;
|
||||||
|
delete_transient( WBF_UPDATE_TRANSIENT );
|
||||||
|
wp_safe_redirect( remove_query_arg(['wbf_refresh_update','_wpnonce']) );
|
||||||
|
exit;
|
||||||
|
} );
|
||||||
Reference in New Issue
Block a user