Update from Git Manager GUI

This commit is contained in:
2026-03-29 13:41:26 +02:00
parent 3ea89e9841
commit 3b7fd16301
7 changed files with 369 additions and 106 deletions

View File

@@ -9,6 +9,15 @@ class WBF_Shortcodes {
// ── Helpers ───────────────────────────────────────────────────────────────
/** Alter aus Geburtsdatum berechnen */
public static function calc_age( $date_str ) {
if ( ! $date_str || ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_str) ) return null;
$birth = new DateTime( $date_str );
$today = new DateTime();
if ( $birth > $today ) return null;
return (int) $birth->diff($today)->y;
}
public static function time_ago( $datetime ) {
$diff = time() - strtotime($datetime);
if ($diff < 60) return 'Gerade eben';
@@ -149,8 +158,12 @@ class WBF_Shortcodes {
// ── Router ────────────────────────────────────────────────────────────────
public static function forum_main( $atts ) {
// Server-seitiger Logout-Fallback
// Server-seitiger Logout-Fallback — Nonce-Schutz gegen CSRF
if (isset($_GET['wbf_do_logout'])) {
if ( ! isset($_GET['_wpnonce']) || ! wp_verify_nonce( sanitize_text_field($_GET['_wpnonce']), 'wbf_logout' ) ) {
wp_redirect( wbf_get_forum_url() );
exit;
}
WBF_Auth::logout();
wp_redirect( wbf_get_forum_url() );
exit;
@@ -310,7 +323,7 @@ class WBF_Shortcodes {
</div>
<div class="wbf-profile-widget__actions">
<a href="?forum_profile=<?php echo (int)$current->id; ?>" class="wbf-btn wbf-btn--sm">Profil</a>
<a href="<?php echo esc_url(wbf_get_forum_url() . '?wbf_do_logout=1'); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
<a href="<?php echo esc_url(wp_nonce_url(wbf_get_forum_url() . '?wbf_do_logout=1', 'wbf_logout')); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
</div>
</div>
<?php endif; ?>
@@ -893,7 +906,9 @@ class WBF_Shortcodes {
$is_own = $current && $current->id == $profile->id;
$is_staff = $current && WBF_Roles::level($current->role) >= 50;
// Profil-Sichtbarkeit prüfen
if (!$is_own && !$is_staff && (int)($profile->profile_public ?? 1) === 0) {
// profile_public NULL = Spalte fehlt noch = als öffentlich (1) behandeln
$profile_public = isset($profile->profile_public) ? (int)$profile->profile_public : 1;
if (!$is_own && !$is_staff && $profile_public === 0) {
ob_start(); ?>
<div class="wbf-wrap"><?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
@@ -907,12 +922,18 @@ class WBF_Shortcodes {
$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_cats = WBF_DB::get_profile_field_categories();
$cf_cat_map = array_column( $cf_cats, null, 'id' );
$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;
// Tab-ID: numerisch (14) oder String-Slug (z.B. 'mc' von der Forum-Bridge)
$ptab_raw = $_GET['ptab'] ?? ($is_own ? 1 : 2);
$active_tab = ctype_digit( (string) $ptab_raw ) ? (int) $ptab_raw : sanitize_key( $ptab_raw );
if ( is_int($active_tab) && ! in_array($active_tab, [1,2,3,4]) ) {
$active_tab = $is_own ? 1 : 2;
}
// Tab 1, 3, 4 und String-Tabs nur für eigenes Profil (außer Tab 2 = Aktivität)
if ( ! $is_own && $active_tab !== 2 ) $active_tab = 2;
ob_start(); ?>
<div class="wbf-wrap">
@@ -930,6 +951,7 @@ class WBF_Shortcodes {
<div class="wbf-profile-sidebar__avatar-wrap">
<img src="<?php echo esc_url($profile->avatar_url); ?>"
alt="<?php echo esc_attr($profile->display_name); ?>"
id="wbfProfileAvatar"
class="wbf-profile-sidebar__avatar">
<?php if ($is_own): ?>
<label class="wbf-avatar-upload-btn" title="Avatar ändern">
@@ -981,26 +1003,70 @@ class WBF_Shortcodes {
<p class="wbf-profile-sidebar__sig"><?php echo nl2br(esc_html($profile->signature)); ?></p>
</div>
<?php endif; ?>
<!-- Öffentliche Custom Fields -->
<?php foreach ($cf_defs as $def):
if (!$is_own && empty($def['public'])) continue;
$val = trim($cf_vals[$def['key']] ?? '');
if ($val === '') continue; ?>
<!-- Öffentliche Custom Fields — nach Kategorie gruppiert -->
<?php
$cf_by_cat_sb = [];
foreach ( $cf_defs as $def_sb ) {
if (!$is_own && empty($def_sb['public'])) continue;
$val_sb = trim($cf_vals[$def_sb['key']] ?? '');
if ($val_sb === '') continue;
$cid_sb = $def_sb['category_id'] ?? '';
if (!$cid_sb || !isset($cf_cat_map[$cid_sb])) $cid_sb = '__none__';
$cf_by_cat_sb[$cid_sb][] = ['def'=>$def_sb,'val'=>$val_sb];
}
$sb_sections = $cf_cats;
if (isset($cf_by_cat_sb['__none__'])) {
$sb_sections[] = ['id'=>'__none__','name'=>'Weitere Infos','icon'=>''];
}
foreach ($sb_sections as $scat_sb):
$scid_sb = $scat_sb['id'];
if (empty($cf_by_cat_sb[$scid_sb])) continue;
?>
<div class="wbf-profile-sidebar__section">
<span class="wbf-profile-sidebar__section-label">
<i class="fas fa-<?php echo $def['type']==='url'?'link':($def['type']==='number'?'hashtag':'tag'); ?>"></i>
<?php echo esc_html($def['label']); ?>
<span class="wbf-profile-sidebar__section-label" style="display:flex;align-items:center;gap:5px;margin-bottom:4px">
<?php if(!empty($scat_sb['icon'])): ?>
<span style="font-size:.9rem"><?php echo esc_html($scat_sb['icon']); ?></span>
<?php endif; ?>
<?php echo esc_html($scat_sb['name']); ?>
</span>
<?php if ($def['type'] === 'url'): ?>
<a href="<?php echo esc_url($val); ?>" target="_blank" rel="noopener noreferrer"
style="color:var(--c-primary);font-size:.85rem;word-break:break-all">
<?php echo esc_html(mb_strtolower(preg_replace('#^https?://#i','',$val))); ?>
</a>
<?php elseif ($def['type'] === 'textarea'): ?>
<p style="font-size:.85rem"><?php echo nl2br(esc_html($val)); ?></p>
<?php else: ?>
<p style="font-size:.85rem"><?php echo esc_html($val); ?></p>
<?php endif; ?>
<?php foreach ($cf_by_cat_sb[$scid_sb] as $cf_entry_sb):
$def_sb = $cf_entry_sb['def'];
$val_sb = $cf_entry_sb['val'];
// Auto-Link für Telegram und Discord anhand des Feld-Keys erkennen
$key_sb = strtolower($def_sb['key']);
$is_telegram = strpos($key_sb, 'telegram') !== false;
$is_discord = strpos($key_sb, 'discord') !== false;
?>
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:6px;margin-bottom:4px;font-size:.85rem">
<span style="color:var(--c-muted,#94a3b8);flex-shrink:0"><?php echo esc_html($def_sb['label']); ?></span>
<?php if ($def_sb['type'] === 'url'): ?>
<a href="<?php echo esc_url($val_sb); ?>" target="_blank" rel="noopener noreferrer"
style="color:var(--c-primary);word-break:break-all;text-align:right">
<?php echo esc_html(mb_strtolower(preg_replace('#^https?://#i','',$val_sb))); ?>
</a>
<?php elseif ($is_telegram):
// Username bereinigen: @ und Leerzeichen entfernen
$tg_user = ltrim(trim($val_sb), '@');
$tg_url = 'https://t.me/' . rawurlencode($tg_user);
?>
<a href="<?php echo esc_url($tg_url); ?>" target="_blank" rel="noopener noreferrer"
style="color:#29b6f6;text-align:right;font-size:inherit">
@<?php echo esc_html($tg_user); ?>
</a>
<?php elseif ($is_discord): ?>
<span style="color:#7289da;text-align:right;font-size:inherit">
<?php echo esc_html($val_sb); ?>
</span>
<?php elseif ($def_sb['type'] === 'textarea'): ?>
<span style="text-align:right"><?php echo nl2br(esc_html($val_sb)); ?></span>
<?php elseif ($def_sb['type'] === 'date'):
$age_sb = self::calc_age($val_sb); ?>
<span><?php echo $age_sb !== null ? esc_html((string)$age_sb) . ' Jahre' : '—'; ?></span>
<?php else: ?>
<span style="text-align:right"><?php echo esc_html($val_sb); ?></span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</aside>
@@ -1043,6 +1109,16 @@ class WBF_Shortcodes {
class="wbf-profile-tab<?php echo $active_tab===3?' active':''; ?>">
<i class="fas fa-shield-halved"></i> Privatsphäre
</a>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=4"
class="wbf-profile-tab<?php echo $active_tab===4?' active':''; ?>">
<i class="fas fa-lock"></i> Sicherheit
</a>
<?php if ( class_exists('MC_Gallery_Forum_Bridge') ) : ?>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=mc"
class="wbf-profile-tab<?php echo $active_tab==='mc'?' active':''; ?>">
<i class="fas fa-cubes"></i> Minecraft
</a>
<?php endif; ?>
</div>
<?php endif; ?>
@@ -1057,15 +1133,9 @@ class WBF_Shortcodes {
<i class="fas fa-sliders"></i> Profil bearbeiten
</div>
<div class="wbf-profile-card__body">
<div class="wbf-profile-edit-grid">
<div class="wbf-form-row">
<label>Anzeigename</label>
<input type="text" id="wbfEditName" value="<?php echo esc_attr($profile->display_name); ?>">
</div>
<div class="wbf-form-row">
<label>Neues Passwort <small>(leer = nicht ändern)</small></label>
<input type="password" id="wbfNewPassword" placeholder="••••••">
</div>
<div class="wbf-form-row">
<label>Anzeigename</label>
<input type="text" id="wbfEditName" value="<?php echo esc_attr($profile->display_name); ?>">
</div>
<div class="wbf-form-row">
<label>Bio</label>
@@ -1076,62 +1146,36 @@ class WBF_Shortcodes {
<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>
<?php $pub = (int)($profile->profile_public ?? 1); ?>
<button type="button" id="wbfToggleProfileVis"
class="wbf-btn wbf-btn--sm<?php echo $pub?' wbf-btn--primary':''; ?>"
data-state="<?php echo $pub; ?>">
<i class="fas fa-<?php echo $pub?'eye':'eye-slash'; ?>"></i>
<?php echo $pub?'Öffentlich':'Privat'; ?>
</button>
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile">
<i class="fas fa-save"></i> Speichern
</button>
<span class="wbf-msg" id="wbfProfileMsg"></span>
</div>
</div>
</div>
<!-- E-Mail-Adresse ändern -->
<!-- Weitere Profilangaben — nach Kategorie gruppiert (ohne eigene Speichern-Buttons) -->
<?php
$cf_edit_by_cat = [];
foreach ( $cf_defs as $def_e ) {
$cid_e = $def_e['category_id'] ?? '';
if (!$cid_e || !isset($cf_cat_map[$cid_e])) $cid_e = '__none__';
$cf_edit_by_cat[$cid_e][] = $def_e;
}
$edit_sections = $cf_cats;
if (isset($cf_edit_by_cat['__none__'])) {
$edit_sections[] = ['id'=>'__none__','name'=>'Weitere Angaben','icon'=>'📋'];
}
if (!empty($cf_defs)):
foreach ($edit_sections as $ecat):
$ecid = $ecat['id'];
if (empty($cf_edit_by_cat[$ecid])) continue;
?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-envelope"></i> E-Mail-Adresse
</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__header">
<i class="fas fa-sliders"></i> Weitere Profilangaben
<?php if(!empty($ecat['icon'])): ?>
<span style="margin-right:5px"><?php echo esc_html($ecat['icon']); ?></span>
<?php else: ?><i class="fas fa-sliders"></i><?php endif; ?>
<?php echo esc_html($ecat['name']); ?>
</div>
<div class="wbf-profile-card__body">
<div class="wbf-profile-edit-grid">
<?php foreach ($cf_defs as $def):
<?php foreach ($cf_edit_by_cat[$ecid] as $def):
$k = esc_attr($def['key']);
$lbl = esc_html($def['label']);
$ph = esc_attr($def['placeholder'] ?? '');
@@ -1154,6 +1198,13 @@ class WBF_Shortcodes {
<?php selected($cf_vals[$def['key']] ?? '', $opt); ?>><?php echo esc_html($opt); ?></option>
<?php endforeach; ?>
</select>
<?php elseif ($def['type'] === 'date'): ?>
<input type="date"
class="wbf-cf-input"
data-field="cf_<?php echo $k; ?>"
value="<?php echo esc_attr($cf_vals[$def['key']] ?? ''); ?>"
max="<?php echo date('Y-m-d'); ?>"
<?php echo $req; ?>>
<?php else: ?>
<input type="<?php echo $def['type']==='url'?'url':($def['type']==='number'?'number':'text'); ?>"
class="wbf-cf-input"
@@ -1165,15 +1216,17 @@ class WBF_Shortcodes {
</div>
<?php endforeach; ?>
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfileCf">
<i class="fas fa-save"></i> Speichern
</button>
<span class="wbf-msg" id="wbfProfileCfMsg"></span>
</div>
</div>
</div>
<?php endif; ?>
<?php endforeach; endif; ?>
<!-- Globaler Speichern-Button für Tab 1 -->
<div style="display:flex;align-items:center;gap:1rem;padding:.25rem 0 .5rem">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile" style="min-width:160px">
<i class="fas fa-save"></i> Alles speichern
</button>
<span class="wbf-msg" id="wbfProfileMsg"></span>
</div>
<?php endif; /* end Tab 1 */ ?>
@@ -1260,6 +1313,29 @@ class WBF_Shortcodes {
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 3): ?>
<!-- Profil-Sichtbarkeit -->
<?php $pub = (int)($profile->profile_public ?? 1); ?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-eye"></i> Profil-Sichtbarkeit
</div>
<div class="wbf-profile-card__body">
<div class="wbf-form-row" style="display:flex;align-items:center;gap:1rem">
<div>
<div style="font-size:.9rem;font-weight:600;margin-bottom:3px">Profil öffentlich sichtbar</div>
<div style="font-size:.8rem;color:var(--c-muted)">Wenn deaktiviert, können nur du selbst und Moderatoren dein Profil sehen.</div>
</div>
<button type="button" id="wbfToggleProfileVis"
class="wbf-btn wbf-btn--sm<?php echo $pub?' wbf-btn--primary':''; ?>"
data-state="<?php echo $pub; ?>"
style="margin-left:auto;white-space:nowrap">
<i class="fas fa-<?php echo $pub?'eye':'eye-slash'; ?>"></i>
<?php echo $pub?'Öffentlich':'Privat'; ?>
</button>
</div>
</div>
</div>
<!-- Ignorierte Nutzer -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
@@ -1394,6 +1470,78 @@ class WBF_Shortcodes {
<?php endif; /* end Tab 3 */ ?>
<!-- ══════════════════════════════════════════════════
TAB 4 — Sicherheit (Passwort & E-Mail)
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 4): ?>
<!-- Passwort ändern -->
<div class="wbf-profile-card" style="border-color:rgba(0,180,216,.25)">
<div class="wbf-profile-card__header" style="background:rgba(0,180,216,.07);border-bottom-color:rgba(0,180,216,.18)">
<i class="fas fa-lock" style="color:var(--c-primary)"></i> Passwort ändern
</div>
<div class="wbf-profile-card__body">
<div class="wbf-form-row">
<label>Aktuelles Passwort</label>
<input type="password" id="wbfCurrentPassword" placeholder="Dein aktuelles Passwort" autocomplete="current-password">
</div>
<div class="wbf-profile-edit-grid">
<div class="wbf-form-row">
<label>Neues Passwort <small>(min. 6 Zeichen)</small></label>
<input type="password" id="wbfNewPassword" placeholder="Neues Passwort" autocomplete="new-password">
</div>
<div class="wbf-form-row">
<label>Neues Passwort wiederholen</label>
<input type="password" id="wbfNewPassword2" placeholder="Passwort bestätigen" autocomplete="new-password">
</div>
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSavePassword">
<i class="fas fa-key"></i> Passwort ändern
</button>
<span class="wbf-msg" id="wbfPasswordMsg"></span>
</div>
</div>
</div>
<!-- E-Mail-Adresse ändern -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-envelope"></i> E-Mail-Adresse
</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>
<?php endif; /* end Tab 4 */ ?>
<!-- ══════════════════════════════════════════════════
TAB MC — Minecraft-Konto verknüpfen (Bridge)
Wird nur gerendert wenn MC Gallery Forum Bridge aktiv ist.
══════════════════════════════════════════════════ -->
<?php if ( $is_own && $active_tab === 'mc' && class_exists('MC_Gallery_Forum_Bridge') ) :
echo apply_filters('wbf_profile_tab_content', '', 'minecraft', $profile);
endif; /* end Tab MC */ ?>
</div><!-- /.wbf-profile-main -->
</div><!-- /.wbf-profile-layout -->
</div>
@@ -1608,7 +1756,7 @@ class WBF_Shortcodes {
if ($maint_s === '1' && (!$cur_s || WBF_Roles::level($cur_s->role) < 50)) return self::view_maintenance();
$query = sanitize_text_field($_GET['q'] ?? '');
$current = WBF_Auth::get_current_user();
$results = mb_strlen($query) >= 2 ? WBF_DB::search($query, 40) : [];
$results = mb_strlen($query) >= 2 ? WBF_DB::search($query, 40, $current) : [];
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
@@ -1711,7 +1859,7 @@ class WBF_Shortcodes {
<?php echo esc_html($current->display_name); ?>
<?php echo self::role_badge($current->role); ?>
</a>
<a href="<?php echo esc_url(wbf_get_forum_url() . '?wbf_do_logout=1'); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
<a href="<?php echo esc_url(wp_nonce_url(wbf_get_forum_url() . '?wbf_do_logout=1', 'wbf_logout')); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
<?php else: ?>
<button class="wbf-btn wbf-btn--sm" id="wbfOpenLogin"><?php echo esc_html(wbf_get_settings()['btn_login']); ?></button>
<button class="wbf-btn wbf-btn--sm wbf-btn--primary" id="wbfOpenRegister"><?php echo esc_html(wbf_get_settings()['btn_register']); ?></button>