'wbf-export', 'wbf_err' => 'no_sections', ], admin_url( 'admin.php' ) ) ); exit; } $data = self::build_export( $sections ); $json = wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); if ( $json === false ) { wp_safe_redirect( add_query_arg( [ 'page' => 'wbf-export', 'wbf_err' => 'json_fail', ], admin_url( 'admin.php' ) ) ); exit; } $filename = 'wbf-backup-' . date( 'Y-m-d-His' ) . '.json'; nocache_headers(); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Length: ' . strlen( $json ) ); echo $json; exit; } /** * Baut das Export-Array auf. * * @param string[] $sections Liste der zu exportierenden Sektions-Keys * @return array */ public static function build_export( array $sections ): array { global $wpdb; $data = [ '_meta' => [ 'plugin' => 'WP Business Forum', 'version' => WBF_VERSION, 'exported' => gmdate( 'c' ), 'site' => get_bloginfo( 'url' ), 'sections' => $sections, ], ]; foreach ( $sections as $sec ) { switch ( $sec ) { case 'settings': $data['settings'] = get_option( 'wbf_settings', [] ); $data['profile_fields'] = get_option( 'wbf_profile_fields', [] ); $data['profile_field_cats'] = get_option( 'wbf_profile_field_cats', [] ); $data['reactions_cfg'] = get_option( 'wbf_reactions', [] ); $data['word_filter'] = get_option( 'wbf_word_filter', '' ); break; case 'roles': // Superadmin bewusst mitexportieren (aber beim Import nie anwenden) $data['roles'] = get_option( 'wbf_custom_roles', [] ); break; case 'levels': $data['levels'] = [ 'config' => get_option( 'wbf_level_config', [] ), 'enabled' => get_option( 'wbf_levels_enabled', true ), ]; break; case 'categories': $data['categories'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_categories ORDER BY parent_id ASC, sort_order ASC", ARRAY_A ) ?: []; break; case 'users': $data['users'] = $wpdb->get_results( "SELECT id, username, email, password, display_name, avatar_url, bio, signature, role, pre_ban_role, ban_reason, ban_until, post_count, registered, last_active, profile_public, reset_token, reset_token_expires FROM {$wpdb->prefix}forum_users ORDER BY id ASC", ARRAY_A ) ?: []; // Passwort-Hashes & Reset-Tokens aus der Ausgabe filtern // wenn der Admin NUR Profildaten möchte — entscheidet er selbst $data['user_meta'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_user_meta ORDER BY user_id ASC" ); break; case 'threads': $data['threads'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_threads ORDER BY id ASC", ARRAY_A ) ?: []; $data['posts'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_posts ORDER BY id ASC", ARRAY_A ) ?: []; $data['thread_tags'] = $wpdb->get_results( "SELECT tt.thread_id, tt.tag_id, t.name, t.slug, t.use_count FROM {$wpdb->prefix}forum_thread_tags tt JOIN {$wpdb->prefix}forum_tags t ON t.id = tt.tag_id ORDER BY tt.thread_id ASC", ARRAY_A ) ?: []; $data['subscriptions'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_subscriptions ORDER BY id ASC" ); break; case 'polls': $data['polls'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_polls ORDER BY id ASC" ); $data['poll_votes'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_poll_votes ORDER BY id ASC" ); break; case 'bookmarks': $data['bookmarks'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_bookmarks ORDER BY id ASC" ); break; case 'prefixes': $data['prefixes'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_prefixes ORDER BY sort_order ASC" ); break; case 'interactions': $data['likes'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_likes ORDER BY id ASC" ); $data['reactions'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_reactions ORDER BY id ASC" ); $data['notifications'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_notifications ORDER BY id ASC" ); break; case 'messages': $data['messages'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_messages ORDER BY id ASC" ); break; case 'reports': $data['reports'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_reports ORDER BY id ASC" ); break; case 'invites': $data['invites'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_invites ORDER BY id ASC" ); break; case 'ignore_list': $data['ignore_list'] = self::safe_get( "SELECT * FROM {$wpdb->prefix}forum_ignore_list ORDER BY id ASC" ); break; } } return $data; } // ═══════════════════════════════════════════════════════════════ // IMPORT — Verarbeitung des hochgeladenen JSON // ═══════════════════════════════════════════════════════════════ /** * Importiert ein WBF-Backup. * * @param array $post $_POST-Array (Optionen) * @param array $files $_FILES-Array * @return array [ 'type' => 'success|error|warning', 'message' => string, 'log' => string[] ] */ public static function handle_import( array $post, array $files ): array { // ── Datei-Validierung ───────────────────────────────────── $tmp = $files['import_file']['tmp_name'] ?? ''; if ( empty( $tmp ) || ! is_uploaded_file( $tmp ) ) { return self::result( 'error', 'Keine Datei hochgeladen.' ); } $size = filesize( $tmp ); if ( $size === false || $size > self::MAX_UPLOAD_BYTES ) { return self::result( 'error', sprintf( 'Datei zu groß (%s). Maximum: %s.', size_format( (int) $size ), size_format( self::MAX_UPLOAD_BYTES ) ) ); } $ext = strtolower( pathinfo( $files['import_file']['name'] ?? '', PATHINFO_EXTENSION ) ); if ( $ext !== 'json' ) { return self::result( 'error', 'Nur .json-Dateien werden akzeptiert.' ); } $raw = file_get_contents( $tmp ); $data = json_decode( $raw, true ); if ( ! is_array( $data ) || ! isset( $data['_meta']['plugin'] ) ) { return self::result( 'error', 'Ungültige Datei — kein gültiges WBF-Backup.' ); } if ( $data['_meta']['plugin'] !== 'WP Business Forum' ) { return self::result( 'error', 'Diese Datei stammt nicht von WP Business Forum.' ); } // Versions-Hinweis (kein harter Fehler — Abwärtskompatibilität) $backup_ver = $data['_meta']['version'] ?? '0'; $ver_warning = ''; if ( version_compare( $backup_ver, WBF_VERSION, '>' ) ) { $ver_warning = "ℹ️ Backup-Version ({$backup_ver}) ist neuer als installierte Version (" . WBF_VERSION . "). Einige Felder werden möglicherweise nicht importiert."; } // ── Import durchführen ──────────────────────────────────── global $wpdb; $log = []; $id_map = []; // old_user_id => new_user_id (wird von mehreren Sektionen genutzt) $cat_map = []; // old_cat_id => new_cat_id (für zukünftige Kategorie-Remaps) $has_error = false; // Suppress individual query errors — wir werten $wpdb->last_error selbst aus $suppress = $wpdb->suppress_errors( true ); try { // ── 1. Einstellungen & Konfiguration ───────────────── if ( isset( $data['settings'] ) ) { update_option( 'wbf_settings', $data['settings'] ); $log[] = '✅ Einstellungen importiert.'; } if ( isset( $data['profile_fields'] ) ) { update_option( 'wbf_profile_fields', $data['profile_fields'] ); if ( isset($data['profile_field_cats']) ) update_option( 'wbf_profile_field_cats', $data['profile_field_cats'] ); $log[] = '✅ Profilfeld-Definitionen (' . count( $data['profile_fields'] ) . ') importiert.'; } if ( isset( $data['reactions_cfg'] ) && is_array( $data['reactions_cfg'] ) ) { update_option( 'wbf_reactions', $data['reactions_cfg'] ); $log[] = '✅ Reaktionen-Konfiguration importiert.'; } if ( isset( $data['word_filter'] ) ) { update_option( 'wbf_word_filter', sanitize_textarea_field( $data['word_filter'] ) ); $log[] = '✅ Wortfilter importiert.'; } // ── 2. Rollen ───────────────────────────────────────── if ( isset( $data['roles'] ) && is_array( $data['roles'] ) ) { $roles = $data['roles']; unset( $roles['superadmin'] ); // Superadmin-Rolle NIEMALS überschreiben $current = get_option( 'wbf_custom_roles', [] ); $current['superadmin'] = WBF_Roles::get( 'superadmin' ); update_option( 'wbf_custom_roles', array_merge( $current, $roles ) ); $log[] = '✅ Rollen (' . count( $roles ) . ') importiert.'; } // ── 3. Level-System ─────────────────────────────────── if ( isset( $data['levels'] ) && is_array( $data['levels'] ) ) { if ( array_key_exists( 'config', $data['levels'] ) ) { update_option( 'wbf_level_config', $data['levels']['config'] ); } if ( array_key_exists( 'enabled', $data['levels'] ) ) { update_option( 'wbf_levels_enabled', (bool) $data['levels']['enabled'] ); } $log[] = '✅ Level-System importiert.'; } // ── 4. Kategorien ───────────────────────────────────── if ( ! empty( $data['categories'] ) ) { $force = ! empty( $post['import_force_cats'] ); $existing = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}forum_categories" ); if ( $existing > 0 && ! $force ) { $log[] = '⏭️ Kategorien übersprungen (bereits vorhanden — „Überschreiben" aktivieren).'; } else { if ( $force ) { $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}forum_categories" ); } $count = 0; foreach ( $data['categories'] as $cat ) { // Original-ID beibehalten — Threads referenzieren diese IDs direkt $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", // INSERT-Werte (int) ($cat['id'] ?? 0), (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), // ON DUPLICATE KEY UPDATE-Werte (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), ) ); $cat_map[ (int)($cat['id'] ?? 0) ] = (int)($cat['id'] ?? 0); $count++; } $log[] = "✅ Kategorien ($count) importiert."; } } // ── 5. Benutzer + User-Meta ─────────────────────────── if ( ! empty( $data['users'] ) ) { $force = ! empty( $post['import_force_users'] ); $count_new = 0; $count_upd = 0; foreach ( $data['users'] as $u ) { $old_id = (int) $u['id']; // Superadmin-Schutz: importierte Benutzer dürfen nie Superadmin werden if ( isset( $u['role'] ) && $u['role'] === 'superadmin' ) { $u['role'] = 'member'; } // Sicherheitsfelder bereinigen unset( $u['reset_token'], $u['reset_token_expires'] ); $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_users WHERE username=%s OR email=%s", $u['username'] ?? '', $u['email'] ?? '' ) ); if ( $exists ) { $new_id = (int) $exists; $id_map[$old_id] = $new_id; if ( $force ) { $update = $u; unset( $update['id'] ); $wpdb->update( "{$wpdb->prefix}forum_users", $update, [ 'id' => $new_id ] ); $count_upd++; } continue; } // Neuer Benutzer → INSERT $insert = $u; unset( $insert['id'] ); $wpdb->insert( "{$wpdb->prefix}forum_users", $insert ); $new_id = (int) $wpdb->insert_id; $id_map[$old_id] = $new_id; $count_new++; } $log[] = "✅ Benutzer: $count_new neu erstellt, $count_upd aktualisiert."; // User-Meta mit gemappten IDs if ( ! empty( $data['user_meta'] ) ) { if ( $force && ! empty( $id_map ) ) { $mapped_ids = implode( ',', array_map( 'intval', array_values( $id_map ) ) ); $wpdb->query( "DELETE FROM {$wpdb->prefix}forum_user_meta WHERE user_id IN ($mapped_ids)" ); } $meta_count = 0; foreach ( $data['user_meta'] as $row ) { $new_uid = $id_map[ (int)( $row['user_id'] ?? 0 ) ] ?? null; if ( ! $new_uid ) continue; unset( $row['id'] ); $row['user_id'] = $new_uid; $wpdb->replace( "{$wpdb->prefix}forum_user_meta", $row ); $meta_count++; } if ( $meta_count ) $log[] = "✅ Profilfeld-Werte ($meta_count) importiert."; } } // ── 6. Threads, Posts, Tags, Abonnements ───────────── if ( ! empty( $data['threads'] ) ) { $force = ! empty( $post['import_force_threads'] ); if ( $force ) { // Nur die wirklich thread-abhängigen Tabellen leeren $wpdb->query( 'SET FOREIGN_KEY_CHECKS=0' ); foreach ( [ 'forum_posts', 'forum_threads', 'forum_thread_tags', 'forum_tags', 'forum_subscriptions', 'forum_likes', 'forum_reactions', 'forum_notifications', ] as $_tbl ) { if ( self::table_exists( $wpdb->prefix . $_tbl ) ) { $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}{$_tbl}" ); } } $wpdb->query( 'SET FOREIGN_KEY_CHECKS=1' ); } $t_count = 0; foreach ( $data['threads'] as $t ) { // user_id remappen falls der Benutzer eine neue ID bekommen hat if ( isset( $t['user_id'] ) && isset( $id_map[ (int)$t['user_id'] ] ) ) { $t['user_id'] = $id_map[ (int)$t['user_id'] ]; } $wpdb->query( self::build_upsert( "{$wpdb->prefix}forum_threads", $t, 'id' ) ); $t_count++; } $p_count = 0; foreach ( $data['posts'] ?? [] as $p ) { if ( isset( $p['user_id'] ) && isset( $id_map[ (int)$p['user_id'] ] ) ) { $p['user_id'] = $id_map[ (int)$p['user_id'] ]; } $wpdb->query( self::build_upsert( "{$wpdb->prefix}forum_posts", $p, 'id' ) ); $p_count++; } // Tags re-importieren if ( ! empty( $data['thread_tags'] ) ) { $tag_map = []; foreach ( $data['thread_tags'] as $tt ) { $slug = $tt['slug'] ?? ''; if ( ! isset( $tag_map[ $slug ] ) ) { $etag = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_tags WHERE slug=%s", $slug ) ); if ( $etag ) { $tag_map[ $slug ] = (int) $etag; } else { $wpdb->insert( "{$wpdb->prefix}forum_tags", [ 'name' => $tt['name'] ?? $slug, 'slug' => $slug, 'use_count' => (int)($tt['use_count'] ?? 0), ] ); $tag_map[ $slug ] = (int) $wpdb->insert_id; } } $wpdb->replace( "{$wpdb->prefix}forum_thread_tags", [ 'thread_id' => (int)($tt['thread_id'] ?? 0), 'tag_id' => $tag_map[ $slug ], ] ); } $log[] = '✅ Tags (' . count( $data['thread_tags'] ) . ') importiert.'; } // Abonnements (user_id remappen) if ( ! empty( $data['subscriptions'] ) ) { $sc = 0; foreach ( $data['subscriptions'] as $row ) { unset( $row['id'] ); if ( isset( $row['user_id'] ) && isset( $id_map[ (int)$row['user_id'] ] ) ) { $row['user_id'] = $id_map[ (int)$row['user_id'] ]; } $wpdb->replace( "{$wpdb->prefix}forum_subscriptions", $row ); $sc++; } if ( $sc ) $log[] = "✅ Abonnements ($sc) importiert."; } $log[] = "✅ Threads ($t_count) + Posts ($p_count) importiert."; } // ── 7. Thread-Präfixe ───────────────────────────────── if ( ! empty( $data['prefixes'] ) ) { $force = ! empty( $post['import_force_prefixes'] ); if ( $force && self::table_exists( $wpdb->prefix . 'forum_prefixes' ) ) { $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'] ?? 0), $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 ) $log[] = "✅ Thread-Präfixe ($pc) importiert."; } // ── 8. Umfragen + Abstimmungen ──────────────────────── if ( ! empty( $data['polls'] ) ) { $force = ! empty( $post['import_force_polls'] ); if ( $force ) { if ( self::table_exists( $wpdb->prefix . 'forum_poll_votes' ) ) $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}forum_poll_votes" ); if ( self::table_exists( $wpdb->prefix . 'forum_polls' ) ) $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'] ?? 0), (int) ($row['thread_id'] ?? 0), $row['question'] ?? '', $row['options'] ?? '[]', (int) ($row['multi'] ?? 0), $row['ends_at'] ?? null, $row['created_at'] ?? current_time('mysql'), (int) ($row['thread_id'] ?? 0), $row['question'] ?? '', $row['options'] ?? '[]', (int) ($row['multi'] ?? 0), $row['ends_at'] ?? null, ) ); $pc++; } $vc = 0; foreach ( $data['poll_votes'] ?? [] as $row ) { unset( $row['id'] ); if ( isset( $row['user_id'] ) && isset( $id_map[ (int)$row['user_id'] ] ) ) { $row['user_id'] = $id_map[ (int)$row['user_id'] ]; } $wpdb->replace( "{$wpdb->prefix}forum_poll_votes", $row ); $vc++; } $log[] = "✅ Umfragen ($pc) + Abstimmungen ($vc) importiert."; } // ── 9. Lesezeichen ──────────────────────────────────── if ( ! empty( $data['bookmarks'] ) ) { $force = ! empty( $post['import_force_bookmarks'] ); if ( $force && self::table_exists( $wpdb->prefix . 'forum_bookmarks' ) ) { $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}forum_bookmarks" ); } $bc = 0; foreach ( $data['bookmarks'] as $row ) { unset( $row['id'] ); if ( isset( $row['user_id'] ) && isset( $id_map[ (int)$row['user_id'] ] ) ) { $row['user_id'] = $id_map[ (int)$row['user_id'] ]; } $wpdb->replace( "{$wpdb->prefix}forum_bookmarks", $row ); $bc++; } if ( $bc ) $log[] = "✅ Lesezeichen ($bc) importiert."; } // ── 10. Likes (user_id + object_id remapping) ───────── if ( ! empty( $data['likes'] ) ) { $force = ! empty( $post['import_force_threads'] ); if ( $force ) { $wpdb->query( "DELETE FROM {$wpdb->prefix}forum_likes" ); } $count = 0; foreach ( $data['likes'] as $row ) { unset( $row['id'] ); if ( isset( $row['user_id'] ) && isset( $id_map[ (int)$row['user_id'] ] ) ) { $row['user_id'] = $id_map[ (int)$row['user_id'] ]; } $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_likes WHERE user_id=%d AND object_id=%d AND object_type=%s", (int)($row['user_id'] ?? 0), (int)($row['object_id'] ?? 0), ($row['object_type'] ?? '') ) ); if ( ! $exists ) { $wpdb->insert( "{$wpdb->prefix}forum_likes", $row ); $count++; } } $log[] = "✅ Likes ($count) importiert."; } // ── 11. Reaktionen ──────────────────────────────────── if ( ! empty( $data['reactions'] ) ) { $force = ! empty( $post['import_force_threads'] ); if ( $force ) { $wpdb->query( "DELETE FROM {$wpdb->prefix}forum_reactions" ); } $count = 0; foreach ( $data['reactions'] as $row ) { unset( $row['id'] ); if ( isset( $row['user_id'] ) && isset( $id_map[ (int)$row['user_id'] ] ) ) { $row['user_id'] = $id_map[ (int)$row['user_id'] ]; } $wpdb->replace( "{$wpdb->prefix}forum_reactions", $row ); $count++; } $log[] = "✅ Reaktionen ($count) importiert."; } // ── 12. Benachrichtigungen ──────────────────────────── if ( ! empty( $data['notifications'] ) ) { $force = ! empty( $post['import_force_threads'] ); if ( $force ) { $wpdb->query( "DELETE FROM {$wpdb->prefix}forum_notifications" ); } $count = 0; foreach ( $data['notifications'] as $row ) { unset( $row['id'] ); // Sowohl user_id als auch actor_id remappen foreach ( [ 'user_id', 'actor_id' ] as $field ) { if ( isset( $row[$field] ) && isset( $id_map[ (int)$row[$field] ] ) ) { $row[$field] = $id_map[ (int)$row[$field] ]; } } $wpdb->insert( "{$wpdb->prefix}forum_notifications", $row ); $count++; } $log[] = "✅ Benachrichtigungen ($count) importiert."; } // ── 13. Privatnachrichten ───────────────────────────── if ( ! empty( $data['messages'] ) ) { $force = ! empty( $post['import_force_messages'] ); if ( $force ) { $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}forum_messages" ); } $count = 0; foreach ( $data['messages'] as $row ) { unset( $row['id'] ); foreach ( [ 'from_id', 'to_id' ] as $field ) { if ( isset( $row[$field] ) && isset( $id_map[ (int)$row[$field] ] ) ) { $row[$field] = $id_map[ (int)$row[$field] ]; } } $wpdb->insert( "{$wpdb->prefix}forum_messages", $row ); $count++; } $log[] = "✅ Privatnachrichten ($count) importiert."; } // ── 14. Meldungen ───────────────────────────────────── if ( ! empty( $data['reports'] ) ) { $force = ! empty( $post['import_force_reports'] ); if ( $force ) { $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}forum_reports" ); } $count = 0; foreach ( $data['reports'] as $row ) { unset( $row['id'] ); if ( isset( $row['reporter_id'] ) && isset( $id_map[ (int)$row['reporter_id'] ] ) ) { $row['reporter_id'] = $id_map[ (int)$row['reporter_id'] ]; } $wpdb->insert( "{$wpdb->prefix}forum_reports", $row ); $count++; } $log[] = "✅ Meldungen ($count) importiert."; } // ── 15. Einladungen ─────────────────────────────────── if ( ! empty( $data['invites'] ) ) { $force = ! empty( $post['import_force_invites'] ); if ( $force && self::table_exists( $wpdb->prefix . 'forum_invites' ) ) { $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}forum_invites" ); } $count = 0; foreach ( $data['invites'] as $row ) { unset( $row['id'] ); if ( isset( $row['created_by'] ) && isset( $id_map[ (int)$row['created_by'] ] ) ) { $row['created_by'] = $id_map[ (int)$row['created_by'] ]; } // Duplikat-Codes überspringen if ( self::table_exists( $wpdb->prefix . 'forum_invites' ) ) { $dup = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_invites WHERE code=%s", $row['code'] ?? '' ) ); if ( $dup ) continue; } $wpdb->insert( "{$wpdb->prefix}forum_invites", $row ); $count++; } if ( $count ) $log[] = "✅ Einladungen ($count) importiert."; } // ── 16. Ignore-Liste ────────────────────────────────── if ( ! empty( $data['ignore_list'] ) ) { $force = ! empty( $post['import_force_ignore'] ); if ( $force && self::table_exists( $wpdb->prefix . 'forum_ignore_list' ) ) { $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}forum_ignore_list" ); } $count = 0; foreach ( $data['ignore_list'] as $row ) { unset( $row['id'] ); foreach ( [ 'user_id', 'ignored_id' ] as $field ) { if ( isset( $row[$field] ) && isset( $id_map[ (int)$row[$field] ] ) ) { $row[$field] = $id_map[ (int)$row[$field] ]; } } $wpdb->replace( "{$wpdb->prefix}forum_ignore_list", $row ); $count++; } if ( $count ) $log[] = "✅ Ignore-Einträge ($count) importiert."; } } catch ( \Exception $e ) { $has_error = true; $log[] = '❌ Fehler: ' . $e->getMessage(); } $wpdb->suppress_errors( $suppress ); // ── Statistiken neu berechnen ───────────────────────────── if ( ! $has_error ) { self::recalculate_stats(); $log[] = '🔄 Zähler (post_count, thread_count) neu berechnet.'; } if ( $has_error ) { return self::result( 'error', 'Import teilweise fehlgeschlagen.', $log, $data['_meta'] ?? [] ); } if ( empty( $log ) ) { return self::result( 'warning', 'Nichts importiert — Datei enthielt keine gültigen Abschnitte.', [], $data['_meta'] ?? [] ); } $prefix = $ver_warning ? $ver_warning . ' — ' : ''; return self::result( 'success', $prefix . 'Import abgeschlossen.', $log, $data['_meta'] ?? [] ); } // ═══════════════════════════════════════════════════════════════ // ADMIN-SEITE // ═══════════════════════════════════════════════════════════════ /** Rendert die Export/Import-Admin-Seite (ersetzt wbf_admin_export() in forum-admin.php) */ public static function admin_page() { if ( ! current_user_can( 'manage_options' ) ) return; // URL-Fehler aus dem Export-Redirect $url_err = sanitize_key( $_GET['wbf_err'] ?? '' ); // Import verarbeiten $notice = null; if ( isset( $_POST['wbf_do_import'] ) && check_admin_referer( 'wbf_import_nonce' ) ) { $result = self::handle_import( $_POST, $_FILES ); $notice = $result; } if ( $url_err === 'no_sections' ) { $notice = self::result( 'error', 'Bitte mindestens eine Sektion für den Export auswählen.' ); } elseif ( $url_err === 'json_fail' ) { $notice = self::result( 'error', 'JSON-Kodierung fehlgeschlagen — möglicherweise ungültige Zeichen in den Daten.' ); } ?>

Export & Import

Erstelle ein vollständiges Backup aller Forum-Daten oder stelle ein Backup wieder her.

Backup erstellt:  ·  Quelle:

Import-Protokoll ( Einträge)
Export

Wähle die Bereiche, die exportiert werden sollen. Die Datei wird als .json sofort heruntergeladen.

[ $icon, $label, $desc ] ) : ?>
Sofortiger Download
Import
⚠️ Vor dem Import: Erstelle unbedingt zuerst einen Export als Backup. Benutzer-Import enthält Passwort-Hashes — teile die Datei nicht öffentlich.

Maximum:

Überschreiben-Optionen (ohne Häkchen werden Duplikate übersprungen)

[ '📂', 'Kategorien überschreiben', 'Bestehende Kategorien werden gelöscht und neu importiert' ], 'import_force_users' => [ '👥', 'Benutzer aktualisieren', 'Gleiche Username/E-Mail → Daten werden überschrieben' ], 'import_force_threads' => [ '💬', 'Threads & Posts überschreiben', 'Löscht Threads, Posts, Likes, Reaktionen, Benachrichtigungen' ], 'import_force_polls' => [ '📊', 'Umfragen überschreiben', 'Alle Umfragen + Abstimmungen werden neu importiert' ], 'import_force_bookmarks' => [ '🔖', 'Lesezeichen überschreiben', 'Alle Lesezeichen werden neu importiert' ], 'import_force_prefixes' => [ '🏷️', 'Thread-Präfixe überschreiben', 'Alle Präfixe werden neu importiert' ], 'import_force_ignore' => [ '🚫', 'Ignore-Liste überschreiben', 'Alle Blockierungen werden neu importiert' ], 'import_force_messages' => [ '✉️', 'Privatnachrichten überschreiben', 'Alle DMs werden neu importiert' ], 'import_force_reports' => [ '🚩', 'Meldungen überschreiben', 'Alle Meldungen werden neu importiert' ], 'import_force_invites' => [ '📨', 'Einladungen überschreiben', 'Alle Einladungscodes werden neu importiert' ], ]; foreach ( $overwrite as $name => [ $icon, $label, $hint ] ) : ?>
Nur kompatible WBF-Backups (.json)

Exportierte Inhalte im Überblick

💡 Hinweise: Bei sehr großen Backups (10 MB+) das PHP-Limit upload_max_filesize prüfen. Nach dem Import werden Beitrags- und Thread-Zähler automatisch neu berechnet. Superadmin-Konten können nicht per Import überschrieben werden.
query( "UPDATE {$wpdb->prefix}forum_users u SET u.post_count = ( SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts p WHERE p.user_id = u.id AND p.deleted_at IS NULL )" ); // thread_count + post_count pro Kategorie $wpdb->query( "UPDATE {$wpdb->prefix}forum_categories c SET c.thread_count = ( SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads t WHERE t.category_id = c.id AND t.deleted_at IS NULL ), c.post_count = ( SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts p JOIN {$wpdb->prefix}forum_threads t ON t.id = p.thread_id WHERE t.category_id = c.id AND p.deleted_at IS NULL )" ); // reply_count + like_count pro Thread $wpdb->query( "UPDATE {$wpdb->prefix}forum_threads t SET t.reply_count = ( SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts p WHERE p.thread_id = t.id AND p.deleted_at IS NULL ), t.like_count = ( SELECT COUNT(*) FROM {$wpdb->prefix}forum_likes l WHERE l.object_id = t.id AND l.object_type = 'thread' )" ); // like_count pro Post $wpdb->query( "UPDATE {$wpdb->prefix}forum_posts p SET p.like_count = ( SELECT COUNT(*) FROM {$wpdb->prefix}forum_likes l WHERE l.object_id = p.id AND l.object_type = 'post' )" ); // Tag use_count $wpdb->query( "UPDATE {$wpdb->prefix}forum_tags tg SET tg.use_count = ( SELECT COUNT(*) FROM {$wpdb->prefix}forum_thread_tags tt WHERE tt.tag_id = tg.id )" ); } /** * Erstellt ein INSERT … ON DUPLICATE KEY UPDATE Statement. * Benutzt prepare() für alle Werte. */ private static function build_upsert( string $table, array $row, string $pk = 'id' ): string { global $wpdb; $cols = array_keys( $row ); $values = array_values( $row ); $col_list = implode( ', ', array_map( fn($c) => "`$c`", $cols ) ); $placeholders = implode( ', ', array_map( fn($v) => is_null($v) ? 'NULL' : ( is_int($v) || is_float($v) ? '%f' : '%s' ), $values ) ); $update_parts = []; foreach ( $cols as $i => $col ) { if ( $col === $pk ) continue; // PK nicht updaten $update_parts[] = "`{$col}` = VALUES(`{$col}`)"; } $sql = "INSERT INTO `{$table}` ({$col_list}) VALUES "; // Typen-sichere Formatierung $formatted_vals = []; foreach ( $values as $v ) { if ( is_null( $v ) ) { $formatted_vals[] = 'NULL'; } elseif ( is_int( $v ) || ( is_string( $v ) && ctype_digit( $v ) ) ) { $formatted_vals[] = (int) $v; } else { $formatted_vals[] = "'" . esc_sql( $v ) . "'"; } } $sql .= '(' . implode( ', ', $formatted_vals ) . ')'; $sql .= ' ON DUPLICATE KEY UPDATE ' . implode( ', ', $update_parts ); return $sql; } /** Sicher SELECT ausführen — gibt leeres Array zurück wenn Tabelle nicht existiert */ private static function safe_get( string $sql ): array { global $wpdb; // Tabellenname aus der Query extrahieren (primitiv, aber reicht für unsere Queries) if ( preg_match( '/FROM\s+`?(\w+)`?/i', $sql, $m ) ) { if ( ! self::table_exists( $m[1] ) ) return []; } return $wpdb->get_results( $sql, ARRAY_A ) ?: []; } /** Prüft ob eine Tabelle existiert */ private static function table_exists( string $table ): bool { global $wpdb; return $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ) === $table; } /** Erstellt ein standardisiertes Ergebnis-Array */ private static function result( string $type, string $message, array $log = [], array $meta = [] ): array { return compact( 'type', 'message', 'log', 'meta' ); } /** Alle Export-Optionen (key => [icon, label, beschreibung]) */ private static function export_options(): array { return [ 'settings' => [ '⚙️', 'Einstellungen & Wortfilter', 'Forum-Texte, Labels, Regeln, Wortfilter, Profilfelder, Reaktionen' ], 'roles' => [ '🛡️', 'Rollen', 'Alle Rollen inkl. Berechtigungen & Farben' ], 'levels' => [ '⭐', 'Level-System', 'Level-Konfiguration & An/Aus-Status' ], 'categories' => [ '📂', 'Kategorien', 'Alle Kategorien inkl. Hierarchie & Zugriffsstufen' ], 'users' => [ '👥', 'Benutzer & Profilfelder', 'Accounts, Ban-Status, Profilfeld-Werte' ], 'threads' => [ '💬', 'Threads, Posts & Abonnements', 'Alle Inhalte, Tags & Thread-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, Emoji-Reaktionen, Benachrichtigungen' ], 'messages' => [ '✉️', 'Privatnachrichten', 'Alle DM-Konversationen' ], 'ignore_list' => [ '🚫', 'Ignore-Liste', 'Alle gegenseitigen Nutzer-Blockierungen' ], 'reports' => [ '🚩', 'Meldungen', 'Gemeldete Beiträge inkl. Status' ], 'invites' => [ '📨', 'Einladungen', 'Alle Einladungscodes inkl. Nutzungsstand' ], ]; } }