From ee592829c01b06afd4efe935f3d6a321eaf2a31d Mon Sep 17 00:00:00 2001 From: M_Viper Date: Wed, 18 Mar 2026 21:56:45 +0100 Subject: [PATCH] Update from Git Manager GUI --- includes/class-wmw-activator.php | 88 +++++++ includes/class-wmw-admin.php | 303 ++++++++++++++++++++++ includes/class-wmw-ajax.php | 208 +++++++++++++++ includes/class-wmw-core.php | 116 +++++++++ includes/class-wmw-cpt.php | 114 +++++++++ includes/class-wmw-frontend.php | 189 ++++++++++++++ includes/class-wmw-gitea-importer.php | 351 ++++++++++++++++++++++++++ includes/class-wmw-metaboxes.php | 165 ++++++++++++ includes/class-wmw-post-types.php | 95 +++++++ includes/class-wmw-search.php | 169 +++++++++++++ includes/class-wmw-shortcodes.php | 88 +++++++ includes/class-wmw-toc.php | 68 +++++ 12 files changed, 1954 insertions(+) create mode 100644 includes/class-wmw-activator.php create mode 100644 includes/class-wmw-admin.php create mode 100644 includes/class-wmw-ajax.php create mode 100644 includes/class-wmw-core.php create mode 100644 includes/class-wmw-cpt.php create mode 100644 includes/class-wmw-frontend.php create mode 100644 includes/class-wmw-gitea-importer.php create mode 100644 includes/class-wmw-metaboxes.php create mode 100644 includes/class-wmw-post-types.php create mode 100644 includes/class-wmw-search.php create mode 100644 includes/class-wmw-shortcodes.php create mode 100644 includes/class-wmw-toc.php diff --git a/includes/class-wmw-activator.php b/includes/class-wmw-activator.php new file mode 100644 index 0000000..bc45328 --- /dev/null +++ b/includes/class-wmw-activator.php @@ -0,0 +1,88 @@ +get_charset_collate(); + + // Views table (track article views) + $views_table = $wpdb->prefix . 'wmw_views'; + $sql_views = "CREATE TABLE IF NOT EXISTS $views_table ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + article_id BIGINT(20) UNSIGNED NOT NULL, + view_count BIGINT(20) UNSIGNED NOT NULL DEFAULT 1, + PRIMARY KEY (id), + UNIQUE KEY article_id (article_id) + ) $charset;"; + + // Search index table + $search_table = $wpdb->prefix . 'wmw_search_index'; + $sql_search = "CREATE TABLE IF NOT EXISTS $search_table ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + article_id BIGINT(20) UNSIGNED NOT NULL, + wiki_id BIGINT(20) UNSIGNED NOT NULL, + keywords LONGTEXT NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY article_id (article_id), + KEY wiki_id (wiki_id) + ) $charset;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql_views ); + dbDelta( $sql_search ); + + // Store DB version + update_option( 'wmw_db_version', '1.0.0' ); + + // Default settings + if ( ! get_option( 'wmw_settings' ) ) { + update_option( 'wmw_settings', array( + 'show_toc' => 1, + 'show_breadcrumbs' => 1, + 'show_related' => 1, + 'show_search' => 1, + 'articles_per_page' => 20, + 'sidebar_position' => 'left', + 'color_scheme' => 'auto', + ) ); + } + + // Flag to flush rewrite rules + update_option( 'wmw_flush_rewrite', 1 ); + + // Register post types early so permalinks work + $pt = new WMW_Post_Types(); + $pt->register(); + flush_rewrite_rules(); + } + + public static function deactivate() { + flush_rewrite_rules(); + } + + public static function uninstall() { + global $wpdb; + + // Remove custom tables + $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}wmw_views" ); + $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}wmw_search_index" ); + + // Remove options + delete_option( 'wmw_settings' ); + delete_option( 'wmw_db_version' ); + + // Remove all wiki and article posts + $posts = get_posts( array( + 'post_type' => array( 'wmw_wiki', 'wmw_article' ), + 'post_status' => 'any', + 'posts_per_page' => -1, + 'fields' => 'ids', + ) ); + foreach ( $posts as $post_id ) { + wp_delete_post( $post_id, true ); + } + } +} diff --git a/includes/class-wmw-admin.php b/includes/class-wmw-admin.php new file mode 100644 index 0000000..40504bb --- /dev/null +++ b/includes/class-wmw-admin.php @@ -0,0 +1,303 @@ + admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wmw_admin_nonce' ), + 'confirm_delete' => __( 'Wirklich löschen?', 'wp-multi-wiki' ), + ) ); + + wp_enqueue_editor(); + } + + // ── Meta Boxes ──────────────────────────────────────────────────────────── + + public function add_meta_boxes() { + add_meta_box( 'wmw_wiki_options', 'Wiki-Optionen', array( $this, 'mb_wiki_options' ), 'wmw_wiki', 'side' ); + add_meta_box( 'wmw_article_meta', 'Artikel-Einstellungen', array( $this, 'mb_article_meta' ), 'wmw_article', 'side' ); + } + + public function mb_wiki_options( $post ) { + wp_nonce_field( 'wmw_save_wiki', 'wmw_wiki_nonce' ); + $icon = get_post_meta( $post->ID, '_wmw_icon', true ) ?: '📖'; + $color = get_post_meta( $post->ID, '_wmw_color', true ) ?: '#2271b1'; + $ver = get_post_meta( $post->ID, '_wmw_version', true ) ?: '1.0.0'; + ?> +

+
+ +

+

+
+ +

+

+
+ +

+ ID, '_wmw_wiki_id', true ); + $order = (int) get_post_meta( $post->ID, '_wmw_order', true ); + $wikis = wmw_get_wikis(); + ?> +

+
+ +

+

+
+ +

+ update_search_index( $post_id, $wiki_id ); + } + } + + private function update_search_index( $article_id, $wiki_id ) { + global $wpdb; + $post = get_post( $article_id ); + $keywords = wp_strip_all_tags( $post->post_title . ' ' . $post->post_content . ' ' . $post->post_excerpt ); + $table = $wpdb->prefix . 'wmw_search_index'; + $wpdb->replace( $table, array( + 'article_id' => $article_id, + 'wiki_id' => $wiki_id, + 'keywords' => $keywords, + ) ); + } + + // ── Column Overrides ───────────────────────────────────────────────────── + + public function article_columns( $columns ) { + unset( $columns['date'] ); + $columns['wmw_wiki'] = 'Wiki'; + $columns['wmw_category'] = 'Kategorie'; + $columns['date'] = 'Datum'; + return $columns; + } + + public function article_column_data( $column, $post_id ) { + if ( 'wmw_wiki' === $column ) { + $wiki = wmw_get_article_wiki( $post_id ); + echo $wiki ? '' . esc_html( $wiki->post_title ) . '' : '—'; + } + if ( 'wmw_category' === $column ) { + $terms = get_the_terms( $post_id, 'wmw_category' ); + echo $terms ? implode( ', ', wp_list_pluck( $terms, 'name' ) ) : '—'; + } + } + + public function wiki_columns( $columns ) { + unset( $columns['date'] ); + $columns['wmw_articles'] = 'Artikel'; + $columns['wmw_icon'] = 'Icon'; + $columns['wmw_version'] = 'Version'; + $columns['date'] = 'Datum'; + return $columns; + } + + public function wiki_column_data( $column, $post_id ) { + if ( 'wmw_articles' === $column ) { + $count = count( wmw_get_articles( $post_id ) ); + echo '' . $count . ''; + } + if ( 'wmw_icon' === $column ) { + echo '' . wmw_get_wiki_icon( $post_id ) . ''; + } + if ( 'wmw_version' === $column ) { + echo esc_html( get_post_meta( $post_id, '_wmw_version', true ) ?: '1.0.0' ); + } + } + + // ── Admin Notices ───────────────────────────────────────────────────────── + + public function admin_notices() { + if ( isset( $_GET['wmw_saved'] ) ) { + echo '

✅ Gespeichert!

'; + } + if ( isset( $_GET['wmw_deleted'] ) ) { + echo '

🗑️ Gelöscht!

'; + } + if ( isset( $_GET['wmw_error'] ) ) { + echo '

❌ Fehler: ' . esc_html( $_GET['wmw_error'] ) . '

'; + } + } + + // ── Pages ───────────────────────────────────────────────────────────────── + + public function page_dashboard() { + $wikis = get_posts( array( 'post_type' => 'wmw_wiki', 'post_status' => 'any', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC' ) ); + $total_articles = count( get_posts( array( 'post_type' => 'wmw_article', 'post_status' => 'any', 'posts_per_page' => -1 ) ) ); + include WMW_PLUGIN_DIR . 'templates/admin/dashboard.php'; + } + + public function page_new_wiki() { + include WMW_PLUGIN_DIR . 'templates/admin/wiki-form.php'; + } + + public function page_edit_wiki() { + $wiki_id = absint( $_GET['id'] ?? 0 ); + $wiki = $wiki_id ? get_post( $wiki_id ) : null; + if ( ! $wiki || $wiki->post_type !== 'wmw_wiki' ) { + wp_die( 'Wiki nicht gefunden.' ); + } + include WMW_PLUGIN_DIR . 'templates/admin/wiki-form.php'; + } + + public function page_articles() { + $wiki_filter = absint( $_GET['wiki_id'] ?? 0 ); + $args = array( 'post_type' => 'wmw_article', 'post_status' => 'any', 'posts_per_page' => 50, 'orderby' => 'title', 'order' => 'ASC' ); + if ( $wiki_filter ) { + $args['meta_query'] = array( array( 'key' => '_wmw_wiki_id', 'value' => $wiki_filter ) ); + } + $articles = get_posts( $args ); + $wikis = wmw_get_wikis(); + include WMW_PLUGIN_DIR . 'templates/admin/article-list.php'; + } + + public function page_new_article() { + $wiki_id = absint( $_GET['wiki_id'] ?? 0 ); + $wikis = wmw_get_wikis(); + include WMW_PLUGIN_DIR . 'templates/admin/article-form.php'; + } + + public function page_edit_article() { + $article_id = absint( $_GET['id'] ?? 0 ); + $article = $article_id ? get_post( $article_id ) : null; + if ( ! $article || $article->post_type !== 'wmw_article' ) wp_die( 'Artikel nicht gefunden.' ); + $wikis = wmw_get_wikis(); + include WMW_PLUGIN_DIR . 'templates/admin/article-form.php'; + } + + public function page_categories() { + include WMW_PLUGIN_DIR . 'templates/admin/categories.php'; + } + + public function page_gitea_import() { + include WMW_PLUGIN_DIR . 'templates/admin/gitea-importer.php'; + } + + public function page_settings() { + + $saved = false; + + // ── Speichern ──────────────────────────────────────────────────── + if ( isset( $_POST['wmw_settings_nonce'] ) + && wp_verify_nonce( $_POST['wmw_settings_nonce'], 'wmw_save_settings' ) + && current_user_can( 'manage_options' ) ) { + + $settings = array( + 'show_toc' => isset( $_POST['show_toc'] ) ? 1 : 0, + 'show_breadcrumbs' => isset( $_POST['show_breadcrumbs'] ) ? 1 : 0, + 'show_related' => isset( $_POST['show_related'] ) ? 1 : 0, + 'show_search' => isset( $_POST['show_search'] ) ? 1 : 0, + 'articles_per_page' => absint( $_POST['articles_per_page'] ?? 20 ), + 'sidebar_position' => sanitize_key( $_POST['sidebar_position'] ?? 'left' ), + 'color_scheme' => sanitize_key( $_POST['color_scheme'] ?? 'auto' ), + 'custom_css' => wp_strip_all_tags( $_POST['custom_css'] ?? '' ), + ); + + update_option( 'wmw_settings', $settings ); + $saved = true; + } + + // ── Seite anzeigen (nach Speichern ODER normalem Aufruf) ───────── + $settings = get_option( 'wmw_settings', array() ); + + // Erfolgsmeldung direkt hier ausgeben, kein Redirect nötig + if ( $saved ) { + echo '

✅ Einstellungen gespeichert!

'; + } + + include WMW_PLUGIN_DIR . 'templates/admin/settings.php'; + } +} \ No newline at end of file diff --git a/includes/class-wmw-ajax.php b/includes/class-wmw-ajax.php new file mode 100644 index 0000000..22543af --- /dev/null +++ b/includes/class-wmw-ajax.php @@ -0,0 +1,208 @@ + 'Ungültige Anfrage.' ) ); + } + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => 'Keine Berechtigung.' ) ); + } + } + + public function save_wiki() { + $this->verify_nonce(); + + $id = absint( $_POST['id'] ?? 0 ); + $title = sanitize_text_field( $_POST['title'] ?? '' ); + $desc = wp_kses_post( $_POST['description'] ?? '' ); + $icon = sanitize_text_field( $_POST['icon'] ?? '📖' ); + $color = sanitize_hex_color( $_POST['color'] ?? '#2271b1' ); + $ver = sanitize_text_field( $_POST['version'] ?? '1.0.0' ); + + if ( empty( $title ) ) { + wp_send_json_error( array( 'message' => 'Titel darf nicht leer sein.' ) ); + } + + $data = array( + 'post_type' => 'wmw_wiki', + 'post_title' => $title, + 'post_content' => $desc, + 'post_status' => 'publish', + ); + + if ( $id ) { + $data['ID'] = $id; + $result = wp_update_post( $data, true ); + } else { + $result = wp_insert_post( $data, true ); + } + + if ( is_wp_error( $result ) ) { + wp_send_json_error( array( 'message' => $result->get_error_message() ) ); + } + + update_post_meta( $result, '_wmw_icon', $icon ); + update_post_meta( $result, '_wmw_color', $color ); + update_post_meta( $result, '_wmw_version', $ver ); + + wp_send_json_success( array( + 'id' => $result, + 'title' => $title, + 'url' => get_permalink( $result ), + ) ); + } + + public function delete_wiki() { + $this->verify_nonce(); + $id = absint( $_POST['id'] ?? 0 ); + if ( ! $id ) wp_send_json_error(); + + // Delete all articles in this wiki + $articles = wmw_get_articles( $id ); + foreach ( $articles as $article ) { + wp_delete_post( $article->ID, true ); + } + + wp_delete_post( $id, true ); + wp_send_json_success(); + } + + public function save_article() { + $this->verify_nonce(); + + $id = absint( $_POST['id'] ?? 0 ); + $title = sanitize_text_field( $_POST['title'] ?? '' ); + $content = wp_kses_post( $_POST['content'] ?? '' ); + $excerpt = sanitize_textarea_field( $_POST['excerpt'] ?? '' ); + $wiki_id = absint( $_POST['wiki_id'] ?? 0 ); + $order = absint( $_POST['order'] ?? 0 ); + $cats = array_map( 'absint', (array) ( $_POST['categories'] ?? array() ) ); + $tags = sanitize_text_field( $_POST['tags'] ?? '' ); + $status = in_array( $_POST['status'] ?? 'publish', array( 'publish', 'draft' ) ) ? $_POST['status'] : 'publish'; + + if ( empty( $title ) ) { + wp_send_json_error( array( 'message' => 'Titel darf nicht leer sein.' ) ); + } + + $data = array( + 'post_type' => 'wmw_article', + 'post_title' => $title, + 'post_content' => $content, + 'post_excerpt' => $excerpt, + 'post_status' => $status, + ); + + if ( $id ) { + $data['ID'] = $id; + $result = wp_update_post( $data, true ); + } else { + $result = wp_insert_post( $data, true ); + } + + if ( is_wp_error( $result ) ) { + wp_send_json_error( array( 'message' => $result->get_error_message() ) ); + } + + update_post_meta( $result, '_wmw_wiki_id', $wiki_id ); + update_post_meta( $result, '_wmw_order', $order ); + + if ( ! empty( $cats ) ) { + wp_set_object_terms( $result, $cats, 'wmw_category' ); + } + if ( ! empty( $tags ) ) { + $tag_arr = array_map( 'trim', explode( ',', $tags ) ); + wp_set_object_terms( $result, $tag_arr, 'wmw_tag' ); + } + + wp_send_json_success( array( + 'id' => $result, + 'title' => $title, + 'url' => get_permalink( $result ), + ) ); + } + + public function delete_article() { + $this->verify_nonce(); + $id = absint( $_POST['id'] ?? 0 ); + if ( ! $id ) wp_send_json_error(); + wp_delete_post( $id, true ); + wp_send_json_success(); + } + + public function reorder_articles() { + $this->verify_nonce(); + $order = (array) ( $_POST['order'] ?? array() ); + foreach ( $order as $position => $article_id ) { + update_post_meta( absint( $article_id ), '_wmw_order', absint( $position ) ); + } + wp_send_json_success(); + } + + public function ajax_search() { + $query = sanitize_text_field( $_POST['query'] ?? $_GET['query'] ?? '' ); + $wiki_id = absint( $_POST['wiki_id'] ?? $_GET['wiki_id'] ?? 0 ); + + if ( strlen( $query ) < 2 ) { + wp_send_json_success( array( 'results' => array(), 'count' => 0 ) ); + } + + $results = WMW_Search::search( $query, $wiki_id ); + $output = array(); + + foreach ( $results as $post ) { + $wiki = wmw_get_article_wiki( $post->ID ); + $output[] = array( + 'id' => $post->ID, + 'title' => $post->wmw_title, + 'excerpt' => $post->wmw_excerpt, + 'url' => get_permalink( $post->ID ), + 'wiki' => $wiki ? $wiki->post_title : '', + 'icon' => $wiki ? wmw_get_wiki_icon( $wiki->ID ) : '📄', + ); + } + + wp_send_json_success( array( 'results' => $output, 'count' => count( $output ) ) ); + } + + public function reindex() { + $this->verify_nonce(); + $count = WMW_Search::reindex_all(); + wp_send_json_success( array( 'count' => $count, 'message' => $count . ' Artikel neu indiziert.' ) ); + } + + public function track_view() { + global $wpdb; + $article_id = absint( $_POST['article_id'] ?? 0 ); + if ( ! $article_id ) wp_send_json_error(); + + $table = $wpdb->prefix . 'wmw_views'; + $existing = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table WHERE article_id = %d", $article_id ) ); + + if ( $existing ) { + $wpdb->query( $wpdb->prepare( "UPDATE $table SET view_count = view_count + 1 WHERE article_id = %d", $article_id ) ); + } else { + $wpdb->insert( $table, array( 'article_id' => $article_id, 'view_count' => 1 ), array( '%d', '%d' ) ); + } + + wp_send_json_success(); + } +} \ No newline at end of file diff --git a/includes/class-wmw-core.php b/includes/class-wmw-core.php new file mode 100644 index 0000000..32b34a5 --- /dev/null +++ b/includes/class-wmw-core.php @@ -0,0 +1,116 @@ +is_wiki_context() ) return; + + // Dashicons on frontend + wp_enqueue_style( 'dashicons' ); + + wp_enqueue_style( + 'wmw-frontend', + WMW_URL . 'assets/css/frontend.css', + [ 'dashicons' ], + WMW_VERSION + ); + + wp_enqueue_script( + 'wmw-frontend', + WMW_URL . 'assets/js/frontend.js', + [ 'jquery' ], + WMW_VERSION, + true + ); + + wp_localize_script( 'wmw-frontend', 'WMW', [ + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wmw_search' ), + 'l10n' => [ + 'no_results' => __( 'Keine Ergebnisse gefunden.', 'wp-multi-wiki' ), + 'searching' => __( 'Suche …', 'wp-multi-wiki' ), + 'navigation' => __( 'Navigation', 'wp-multi-wiki' ), + ], + ] ); + } + + /* ── Admin Assets ───────────────────────────────────────────── */ + + public function admin_assets( string $hook ): void { + $screen = get_current_screen(); + if ( ! $screen ) return; + + $is_wmw = str_contains( $hook, 'wp-multi-wiki' ) + || str_contains( $hook, 'wmw-settings' ) + || in_array( $screen->post_type, [ 'wmw_wiki', 'wmw_article' ], true ) + || in_array( $screen->taxonomy ?? '', [ 'wmw_category', 'wmw_tag' ], true ); + + if ( ! $is_wmw ) return; + + wp_enqueue_style( + 'wmw-admin', + WMW_URL . 'assets/css/admin.css', + [], + WMW_VERSION + ); + + wp_enqueue_script( + 'wmw-admin', + WMW_URL . 'assets/js/admin.js', + [ 'jquery' ], + WMW_VERSION, + true + ); + } + + /* ── Helpers ────────────────────────────────────────────────── */ + + private function is_wiki_context(): bool { + return is_singular( [ 'wmw_wiki', 'wmw_article' ] ) + || is_post_type_archive( 'wmw_wiki' ) + || is_tax( [ 'wmw_category', 'wmw_tag' ] ); + } + + public function action_links( array $links ): array { + array_unshift( + $links, + '' . __( 'Dashboard', 'wp-multi-wiki' ) . '' + ); + return $links; + } +} diff --git a/includes/class-wmw-cpt.php b/includes/class-wmw-cpt.php new file mode 100644 index 0000000..14dde68 --- /dev/null +++ b/includes/class-wmw-cpt.php @@ -0,0 +1,114 @@ + [ + 'name' => _x( 'Wikis', 'post type general name', 'wp-multi-wiki' ), + 'singular_name' => _x( 'Wiki', 'post type singular name', 'wp-multi-wiki' ), + 'add_new' => __( 'Neues Wiki', 'wp-multi-wiki' ), + 'add_new_item' => __( 'Neues Wiki erstellen', 'wp-multi-wiki' ), + 'edit_item' => __( 'Wiki bearbeiten', 'wp-multi-wiki' ), + 'view_item' => __( 'Wiki ansehen', 'wp-multi-wiki' ), + 'all_items' => __( 'Alle Wikis', 'wp-multi-wiki' ), + 'search_items' => __( 'Wikis suchen', 'wp-multi-wiki' ), + 'not_found' => __( 'Keine Wikis gefunden.', 'wp-multi-wiki' ), + 'not_found_in_trash' => __( 'Keine Wikis im Papierkorb.', 'wp-multi-wiki' ), + ], + 'public' => true, + 'show_ui' => true, + 'show_in_menu' => 'wp-multi-wiki', + 'show_in_rest' => true, + 'has_archive' => 'wikis', + 'rewrite' => [ 'slug' => 'wikis', 'with_front' => false ], + 'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt' ], + 'menu_icon' => 'dashicons-book-alt', + 'capability_type' => 'post', + ] ); + } + + /* ── Wiki Article ───────────────────────────────────────────── */ + + private static function register_article(): void { + register_post_type( 'wmw_article', [ + 'labels' => [ + 'name' => _x( 'Wiki Artikel', 'post type general name', 'wp-multi-wiki' ), + 'singular_name' => _x( 'Wiki Artikel', 'post type singular name', 'wp-multi-wiki' ), + 'add_new' => __( 'Neuer Artikel', 'wp-multi-wiki' ), + 'add_new_item' => __( 'Neuen Artikel erstellen', 'wp-multi-wiki' ), + 'edit_item' => __( 'Artikel bearbeiten', 'wp-multi-wiki' ), + 'view_item' => __( 'Artikel ansehen', 'wp-multi-wiki' ), + 'all_items' => __( 'Alle Artikel', 'wp-multi-wiki' ), + 'search_items' => __( 'Artikel suchen', 'wp-multi-wiki' ), + 'not_found' => __( 'Keine Artikel gefunden.', 'wp-multi-wiki' ), + 'parent_item_colon' => __( 'Übergeordneter Artikel:', 'wp-multi-wiki' ), + ], + 'public' => true, + 'show_ui' => true, + 'show_in_menu' => 'wp-multi-wiki', + 'show_in_rest' => true, + 'has_archive' => false, + 'hierarchical' => true, // Enables Chapter → Subchapter structure + 'rewrite' => [ 'slug' => 'wiki-artikel', 'with_front' => false ], + 'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'page-attributes', 'revisions', 'custom-fields' ], + 'capability_type' => 'post', + ] ); + } + + /* ── Taxonomies ─────────────────────────────────────────────── */ + + private static function register_category(): void { + register_taxonomy( 'wmw_category', 'wmw_article', [ + 'labels' => [ + 'name' => _x( 'Kategorien', 'taxonomy general name', 'wp-multi-wiki' ), + 'singular_name' => _x( 'Kategorie', 'taxonomy singular name', 'wp-multi-wiki' ), + 'add_new_item' => __( 'Neue Kategorie erstellen', 'wp-multi-wiki' ), + 'edit_item' => __( 'Kategorie bearbeiten', 'wp-multi-wiki' ), + 'all_items' => __( 'Alle Kategorien', 'wp-multi-wiki' ), + 'parent_item' => __( 'Übergeordnete Kategorie', 'wp-multi-wiki' ), + 'parent_item_colon' => __( 'Übergeordnete Kategorie:', 'wp-multi-wiki' ), + ], + 'hierarchical' => true, + 'public' => true, + 'show_ui' => true, + 'show_admin_column' => true, + 'show_in_rest' => true, + 'rewrite' => [ 'slug' => 'wiki-kategorie', 'with_front' => false ], + ] ); + } + + private static function register_tag(): void { + register_taxonomy( 'wmw_tag', 'wmw_article', [ + 'labels' => [ + 'name' => _x( 'Tags', 'taxonomy general name', 'wp-multi-wiki' ), + 'singular_name' => _x( 'Tag', 'taxonomy singular name', 'wp-multi-wiki' ), + 'add_new_item' => __( 'Neuen Tag erstellen', 'wp-multi-wiki' ), + 'all_items' => __( 'Alle Tags', 'wp-multi-wiki' ), + ], + 'hierarchical' => false, + 'public' => true, + 'show_ui' => true, + 'show_admin_column' => true, + 'show_in_rest' => true, + 'rewrite' => [ 'slug' => 'wiki-tag', 'with_front' => false ], + ] ); + } +} diff --git a/includes/class-wmw-frontend.php b/includes/class-wmw-frontend.php new file mode 100644 index 0000000..d0035f1 --- /dev/null +++ b/includes/class-wmw-frontend.php @@ -0,0 +1,189 @@ + admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wmw_public_nonce' ), + 'settings' => array( + 'show_toc' => $settings['show_toc'] ?? 1, + ), + ) ); + } + + public function inject_css_vars() { + global $post; + if ( ! $post ) return; + + $wiki = null; + if ( $post->post_type === 'wmw_wiki' ) { + $wiki = $post; + } elseif ( $post->post_type === 'wmw_article' ) { + $wiki = wmw_get_article_wiki( $post->ID ); + } + + if ( $wiki ) { + $color = wmw_get_wiki_color( $wiki->ID ); + echo "\n"; + } + } + + public function filter_content( $content ) { + global $post; + if ( ! $post || ! is_singular() ) return $content; + + $settings = get_option( 'wmw_settings', array() ); + + if ( $post->post_type === 'wmw_article' && ( $settings['show_toc'] ?? 1 ) ) { + $processed = WMW_TOC::process( $content ); + if ( ! empty( $processed['toc'] ) ) { + $content = $processed['toc'] . $processed['content']; + } + } + + return $content; + } + + public function filter_title( $title, $id = null ) { + return $title; + } + + public function load_template( $template ) { + global $post; + if ( ! $post ) return $template; + + if ( $post->post_type === 'wmw_wiki' ) { + $custom = WMW_PLUGIN_DIR . 'templates/single-wmw-wiki.php'; + if ( file_exists( $custom ) ) return $custom; + } + if ( $post->post_type === 'wmw_article' ) { + $custom = WMW_PLUGIN_DIR . 'templates/single-wmw-article.php'; + if ( file_exists( $custom ) ) return $custom; + } + + return $template; + } + + // ── Static Render Methods ───────────────────────────────────────────────── + + public static function render_wiki_index( $wiki, $layout = 'grid' ) { + $settings = get_option( 'wmw_settings', array() ); + $articles = wmw_get_articles( $wiki->ID ); + $categories = get_terms( array( 'taxonomy' => 'wmw_category', 'hide_empty' => true ) ); + $color = wmw_get_wiki_color( $wiki->ID ); + $icon = wmw_get_wiki_icon( $wiki->ID ); + + // Group by category + $grouped = array( 'uncategorized' => array() ); + $cat_map = array(); + foreach ( (array) $categories as $cat ) { + $grouped[ $cat->term_id ] = array(); + $cat_map[ $cat->term_id ] = $cat; + } + + foreach ( $articles as $article ) { + $cats = get_the_terms( $article->ID, 'wmw_category' ); + if ( $cats && ! is_wp_error( $cats ) ) { + foreach ( $cats as $cat ) { + $grouped[ $cat->term_id ][] = $article; + } + } else { + $grouped['uncategorized'][] = $article; + } + } + + include WMW_PLUGIN_DIR . 'templates/wiki-index.php'; + } + + public static function render_article( $article ) { + $wiki = wmw_get_article_wiki( $article->ID ); + $settings = get_option( 'wmw_settings', array() ); + $content = apply_filters( 'the_content', $article->post_content ); + + // Related articles + $related = array(); + if ( $settings['show_related'] ?? 1 ) { + $cats = get_the_terms( $article->ID, 'wmw_category' ); + if ( $cats && ! is_wp_error( $cats ) ) { + $cat_ids = wp_list_pluck( $cats, 'term_id' ); + $related = get_posts( array( + 'post_type' => 'wmw_article', + 'post_status' => 'publish', + 'posts_per_page' => 5, + 'post__not_in' => array( $article->ID ), + 'tax_query' => array( array( 'taxonomy' => 'wmw_category', 'field' => 'term_id', 'terms' => $cat_ids ) ), + ) ); + } + } + + include WMW_PLUGIN_DIR . 'templates/article-single.php'; + } + + public static function render_search_box( $wiki_id = 0, $placeholder = '' ) { + $placeholder = $placeholder ?: 'Wiki durchsuchen…'; + include WMW_PLUGIN_DIR . 'templates/search-box.php'; + } + + public static function render_wiki_list( $wikis, $columns = 3 ) { + include WMW_PLUGIN_DIR . 'templates/wiki-list.php'; + } + + public static function render_breadcrumb( $post ) { + $items = array(); + $items[] = array( 'label' => 'Home', 'url' => home_url() ); + + if ( $post->post_type === 'wmw_article' ) { + $wiki = wmw_get_article_wiki( $post->ID ); + if ( $wiki ) { + $items[] = array( 'label' => 'Wiki', 'url' => get_post_type_archive_link( 'wmw_wiki' ) ); + $items[] = array( 'label' => $wiki->post_title, 'url' => get_permalink( $wiki->ID ) ); + } + $items[] = array( 'label' => $post->post_title, 'url' => '' ); + } elseif ( $post->post_type === 'wmw_wiki' ) { + $items[] = array( 'label' => 'Wiki', 'url' => get_post_type_archive_link( 'wmw_wiki' ) ); + $items[] = array( 'label' => $post->post_title, 'url' => '' ); + } + + include WMW_PLUGIN_DIR . 'templates/breadcrumb.php'; + } + + public static function get_wiki_sidebar( $wiki ) { + $articles = wmw_get_articles( $wiki->ID ); + $categories = get_terms( array( 'taxonomy' => 'wmw_category', 'hide_empty' => false ) ); + ob_start(); + include WMW_PLUGIN_DIR . 'templates/sidebar.php'; + return ob_get_clean(); + } +} diff --git a/includes/class-wmw-gitea-importer.php b/includes/class-wmw-gitea-importer.php new file mode 100644 index 0000000..7a2d852 --- /dev/null +++ b/includes/class-wmw-gitea-importer.php @@ -0,0 +1,351 @@ +api_base = $base . '/api/v1'; + $this->owner = $owner; + $this->repo = $repo; + $this->token = $token; + } + + // ── API ─────────────────────────────────────────────────────────────────── + + private function api_get( $endpoint ) { + $url = $this->api_base . $endpoint; + $args = array( + 'timeout' => 20, + 'headers' => array( 'Accept' => 'application/json' ), + ); + if ( $this->token ) { + $args['headers']['Authorization'] = 'token ' . $this->token; + } + $resp = wp_remote_get( $url, $args ); + if ( is_wp_error( $resp ) ) { + return $resp; + } + $code = wp_remote_retrieve_response_code( $resp ); + if ( 200 !== $code ) { + return new WP_Error( 'api_error', 'Gitea antwortet mit Status ' . $code . ' – URL: ' . $url ); + } + $body = json_decode( wp_remote_retrieve_body( $resp ), true ); + if ( JSON_ERROR_NONE !== json_last_error() ) { + return new WP_Error( 'json_error', 'Ungültige JSON-Antwort von: ' . $url ); + } + return $body; + } + + // ── Öffentliche Methoden ────────────────────────────────────────────────── + + public function list_pages() { + $endpoint = '/repos/' . rawurlencode( $this->owner ) . '/' . rawurlencode( $this->repo ) . '/wiki/pages?limit=50'; + $result = $this->api_get( $endpoint ); + if ( is_wp_error( $result ) ) { + return $result; + } + if ( ! is_array( $result ) ) { + return new WP_Error( 'invalid_response', 'Ungültige API-Antwort.' ); + } + $pages = array(); + foreach ( $result as $page ) { + if ( ! is_array( $page ) ) { + continue; + } + $last = ''; + if ( isset( $page['last_commit']['created'] ) ) { + $last = $page['last_commit']['created']; + } + $pages[] = array( + 'title' => isset( $page['title'] ) ? $page['title'] : '', + 'last_updated' => $last, + ); + } + return $pages; + } + + public function get_page( $page_name ) { + $endpoint = '/repos/' . rawurlencode( $this->owner ) . '/' . rawurlencode( $this->repo ) . '/wiki/page/' . rawurlencode( $page_name ); + $result = $this->api_get( $endpoint ); + if ( is_wp_error( $result ) ) { + return $result; + } + if ( ! is_array( $result ) ) { + return new WP_Error( 'invalid_response', 'Ungültige Seitenantwort für: ' . $page_name ); + } + $markdown = ''; + if ( ! empty( $result['content_base64'] ) ) { + $markdown = base64_decode( $result['content_base64'] ); // phpcs:ignore + } + if ( empty( $markdown ) && ! empty( $result['content'] ) ) { + $markdown = $result['content']; + } + $title = isset( $result['title'] ) ? $result['title'] : $page_name; + return array( + 'title' => $title, + 'content' => self::markdown_to_html( $markdown ), + 'raw' => $markdown, + ); + } + + public function import_all( $wiki_id, $skip_pages = array( 'Home', '_Sidebar' ), $update_existing = false ) { + $log = array( 'imported' => 0, 'updated' => 0, 'skipped' => 0, 'errors' => array() ); + + $pages = $this->list_pages(); + if ( is_wp_error( $pages ) ) { + $log['errors'][] = $pages->get_error_message(); + return $log; + } + + foreach ( $pages as $p ) { + $title = $p['title']; + + if ( in_array( $title, $skip_pages, true ) ) { + $log['skipped']++; + continue; + } + + $existing = get_posts( array( + 'post_type' => 'wmw_article', + 'post_status' => 'any', + 'title' => $title, + 'posts_per_page' => 1, + 'meta_query' => array( + array( 'key' => '_wmw_wiki_id', 'value' => $wiki_id ), + ), + ) ); + + if ( $existing && ! $update_existing ) { + $log['skipped']++; + continue; + } + + $page_data = $this->get_page( $title ); + if ( is_wp_error( $page_data ) ) { + $log['errors'][] = 'Fehler bei "' . $title . '": ' . $page_data->get_error_message(); + continue; + } + + $post_data = array( + 'post_type' => 'wmw_article', + 'post_title' => $page_data['title'], + 'post_content' => $page_data['content'], + 'post_status' => 'publish', + ); + + if ( $existing && $update_existing ) { + $post_data['ID'] = $existing[0]->ID; + $result = wp_update_post( $post_data, true ); + if ( ! is_wp_error( $result ) ) { + $log['updated']++; + } + } else { + $result = wp_insert_post( $post_data, true ); + if ( ! is_wp_error( $result ) ) { + $log['imported']++; + } + } + + if ( is_wp_error( $result ) ) { + $log['errors'][] = 'WP-Fehler bei "' . $title . '": ' . $result->get_error_message(); + continue; + } + + update_post_meta( $result, '_wmw_wiki_id', absint( $wiki_id ) ); + update_post_meta( + $result, + '_wmw_gitea_source', + $this->api_base . '/repos/' . rawurlencode( $this->owner ) . '/' . rawurlencode( $this->repo ) . '/wiki/page/' . rawurlencode( $title ) + ); + + $auto_cat = self::guess_category( $title ); + if ( $auto_cat ) { + $term = term_exists( $auto_cat, 'wmw_category' ); + if ( ! $term ) { + $term = wp_insert_term( $auto_cat, 'wmw_category' ); + } + if ( ! is_wp_error( $term ) ) { + $term_id = is_array( $term ) ? (int) $term['term_id'] : (int) $term; + wp_set_object_terms( $result, $term_id, 'wmw_category', true ); + } + } + } + + return $log; + } + + // ── Markdown → HTML ─────────────────────────────────────────────────────── + + public static function markdown_to_html( $md ) { + if ( empty( $md ) ) { + return ''; + } + + // Gitea [[Wiki-Links]] + $md = preg_replace( '/\[\[([^\]|]+)\|([^\]]+)\]\]/', '[$2]($1)', $md ); + $md = preg_replace( '/\[\[([^\]]+)\]\]/', '[$1]($1)', $md ); + + // Code-Blöcke sichern + $code_blocks = array(); + $md = preg_replace_callback( + '/```(\w*)\n(.*?)```/s', + function ( $m ) use ( &$code_blocks ) { + $lang = ''; + if ( ! empty( $m[1] ) ) { + $lang = ' class="language-' . esc_attr( $m[1] ) . '"'; + } + $token = '%%CB_' . count( $code_blocks ) . '%%'; + $code_blocks[ $token ] = '
' . esc_html( $m[2] ) . '
'; + return $token; + }, + $md + ); + + // Inline Code + $md = preg_replace( '/`([^`\n]+)`/', '$1', $md ); + + // Überschriften + $md = preg_replace( '/^######\s+(.+)$/m', '
$1
', $md ); + $md = preg_replace( '/^#####\s+(.+)$/m', '
$1
', $md ); + $md = preg_replace( '/^####\s+(.+)$/m', '

$1

', $md ); + $md = preg_replace( '/^###\s+(.+)$/m', '

$1

', $md ); + $md = preg_replace( '/^##\s+(.+)$/m', '

$1

', $md ); + $md = preg_replace( '/^#\s+(.+)$/m', '

$1

', $md ); + + // Formatierung + $md = preg_replace( '/\*\*\*(.+?)\*\*\*/s', '$1', $md ); + $md = preg_replace( '/\*\*(.+?)\*\*/s', '$1', $md ); + $md = preg_replace( '/\*(.+?)\*/s', '$1', $md ); + $md = preg_replace( '/__(.+?)__/s', '$1', $md ); + $md = preg_replace( '/_([^_\s][^_]*)_/', '$1', $md ); + $md = preg_replace( '/~~(.+?)~~/s', '$1', $md ); + + // Bilder & Links + $md = preg_replace( '/!\[([^\]]*)\]\(([^)]+)\)/', '$1', $md ); + $md = preg_replace( '/\[([^\]]+)\]\(([^)]+)\)/', '$1', $md ); + + // Blockquote & HR + $md = preg_replace( '/^>\s+(.+)$/m', '
$1
', $md ); + $md = preg_replace( '/^(\-{3,}|\*{3,}|_{3,})$/m', '
', $md ); + + // Tabellen + $md = preg_replace_callback( + '/^(\|.+\|\n)+/m', + array( 'WMW_Gitea_Importer', 'cb_table' ), + $md + ); + + // Ungeordnete Listen + $md = preg_replace_callback( + '/^([ \t]*[\*\-\+] .+\n?)+/m', + array( 'WMW_Gitea_Importer', 'cb_ul' ), + $md + ); + + // Geordnete Listen + $md = preg_replace_callback( + '/^([ \t]*\d+\. .+\n?)+/m', + array( 'WMW_Gitea_Importer', 'cb_ol' ), + $md + ); + + // Absätze + $blocks = preg_split( '/\n{2,}/', $md ); + $html = ''; + foreach ( $blocks as $block ) { + $block = trim( $block ); + if ( empty( $block ) ) { + continue; + } + if ( preg_match( '/^<(h[1-6]|ul|ol|blockquote|pre|table|hr|img|%%CB_)/i', $block ) ) { + $html .= $block . "\n"; + } else { + $html .= '

' . nl2br( $block ) . "

\n"; + } + } + + // Code-Blöcke zurücksetzen + foreach ( $code_blocks as $token => $code_html ) { + $html = str_replace( $token, $code_html, $html ); + } + + return $html; + } + + /** @param array $m */ + private static function cb_table( $m ) { + $rows = array_filter( explode( "\n", trim( $m[0] ) ) ); + $html = ''; + $first = true; + foreach ( $rows as $row ) { + $row = trim( $row ); + if ( empty( $row ) ) { + continue; + } + if ( preg_match( '/^\|[-|:\s]+\|$/', $row ) ) { + continue; // Trennzeile + } + $cells = array_map( 'trim', explode( '|', trim( $row, '|' ) ) ); + $tag = $first ? 'th' : 'td'; + $html .= ''; + foreach ( $cells as $cell ) { + $html .= '<' . $tag . '>' . $cell . ''; + } + $html .= ''; + $first = false; + } + return $html . '
'; + } + + /** @param array $m */ + private static function cb_ul( $m ) { + $lines = array_filter( explode( "\n", trim( $m[0] ) ) ); + $html = ''; + } + + /** @param array $m */ + private static function cb_ol( $m ) { + $lines = array_filter( explode( "\n", trim( $m[0] ) ) ); + $html = '
    '; + foreach ( $lines as $line ) { + $text = preg_replace( '/^[ \t]*\d+\. /', '', $line ); + $html .= '
  1. ' . $text . '
  2. '; + } + return $html . '
'; + } + + /** @param string $title */ + private static function guess_category( $title ) { + $map = array( + 'Installation' => 'Grundlagen', + 'Befehle' => 'Grundlagen', + 'Berechtigungen' => 'Grundlagen', + 'config' => 'Konfiguration', + 'settings' => 'Konfiguration', + 'visuals' => 'Konfiguration', + 'PlaceholderAPI' => 'Extras', + 'Sicherheit' => 'Extras', + ); + return isset( $map[ $title ] ) ? $map[ $title ] : ''; + } +} diff --git a/includes/class-wmw-metaboxes.php b/includes/class-wmw-metaboxes.php new file mode 100644 index 0000000..3a2b15d --- /dev/null +++ b/includes/class-wmw-metaboxes.php @@ -0,0 +1,165 @@ +ID, '_wmw_wiki_id', true ); + $order = get_post_meta( $post->ID, '_wmw_order', true ) ?: 0; + $hide_toc = get_post_meta( $post->ID, '_wmw_hide_toc', true ); + + // Alle Wikis laden + $wikis = get_posts( [ + 'post_type' => 'wmw_wiki', + 'numberposts' => -1, + 'orderby' => 'title', + 'order' => 'ASC', + ] ); + ?> +

+
+ +

+

+
+ + Niedrigere Zahl = weiter oben +

+

+ +

+ ID, '_wmw_icon_url', true ); + $icon_class = get_post_meta( $post->ID, '_wmw_icon_class', true ) ?: 'dashicons-book-alt'; + $color = get_post_meta( $post->ID, '_wmw_color', true ) ?: '#0073aa'; + $version = get_post_meta( $post->ID, '_wmw_version', true ); + ?> + + + + + + + + + + + + + + + + + +
+ +
+ + + Dashicons anzeigen + +
+ +
+ + Optionales eigenes Icon (ueberschreibt Dashicon) +
+ 0 ) { + update_post_meta( $post_id, '_wmw_wiki_id', $wiki_id ); + } else { + delete_post_meta( $post_id, '_wmw_wiki_id' ); + } + } + + if ( isset( $_POST['wmw_order'] ) ) { + update_post_meta( $post_id, '_wmw_order', intval( $_POST['wmw_order'] ) ); + } + + if ( isset( $_POST['wmw_hide_toc'] ) ) { + update_post_meta( $post_id, '_wmw_hide_toc', '1' ); + } else { + delete_post_meta( $post_id, '_wmw_hide_toc' ); + } + } + + // ── Speichern: Wiki ───────────────────────────────────────────────────── + public static function save_wiki( $post_id, $post ) { + if ( ! isset( $_POST['wmw_wiki_nonce'] ) ) return; + if ( ! wp_verify_nonce( $_POST['wmw_wiki_nonce'], 'wmw_save_wiki' ) ) return; + if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) return; + if ( ! current_user_can( 'manage_options' ) ) return; + + $fields = [ 'wmw_icon_url', 'wmw_icon_class', 'wmw_color', 'wmw_version' ]; + foreach ( $fields as $field ) { + if ( isset( $_POST[ $field ] ) ) { + update_post_meta( $post_id, "_$field", sanitize_text_field( $_POST[ $field ] ) ); + } + } + } +} diff --git a/includes/class-wmw-post-types.php b/includes/class-wmw-post-types.php new file mode 100644 index 0000000..c386635 --- /dev/null +++ b/includes/class-wmw-post-types.php @@ -0,0 +1,95 @@ + array( + 'name' => 'Wikis', + 'singular_name' => 'Wiki', + 'add_new' => 'Neues Wiki', + 'add_new_item' => 'Neues Wiki erstellen', + 'edit_item' => 'Wiki bearbeiten', + 'view_item' => 'Wiki ansehen', + 'search_items' => 'Wikis durchsuchen', + 'not_found' => 'Keine Wikis gefunden', + 'menu_name' => 'WP Multi Wiki', + 'all_items' => 'Alle Wikis', + ), + 'public' => true, + 'has_archive' => true, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ), + 'rewrite' => array( 'slug' => 'wiki', 'with_front' => false ), + 'menu_icon' => 'dashicons-book-alt', + 'capability_type' => 'post', + ) ); + + // ── Wiki Article ─────────────────────────────────────────────────── + register_post_type( 'wmw_article', array( + 'labels' => array( + 'name' => 'Wiki-Artikel', + 'singular_name' => 'Wiki-Artikel', + 'add_new' => 'Neuer Artikel', + 'add_new_item' => 'Neuen Artikel erstellen', + 'edit_item' => 'Artikel bearbeiten', + 'view_item' => 'Artikel ansehen', + 'search_items' => 'Artikel durchsuchen', + 'not_found' => 'Keine Artikel gefunden', + 'all_items' => 'Alle Artikel', + ), + 'public' => true, + 'has_archive' => false, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'revisions' ), + 'rewrite' => array( 'slug' => 'wiki-artikel', 'with_front' => false ), + 'menu_icon' => 'dashicons-media-document', + 'capability_type' => 'post', + ) ); + } + + public function register_taxonomies() { + + // ── Wiki Category ────────────────────────────────────────────────── + register_taxonomy( 'wmw_category', 'wmw_article', array( + 'labels' => array( + 'name' => 'Wiki-Kategorien', + 'singular_name' => 'Kategorie', + 'add_new_item' => 'Neue Kategorie', + 'edit_item' => 'Kategorie bearbeiten', + 'all_items' => 'Alle Kategorien', + ), + 'public' => true, + 'hierarchical' => true, + 'show_in_rest' => true, + 'show_admin_column' => true, + 'rewrite' => array( 'slug' => 'wiki-kategorie' ), + ) ); + + // ── Wiki Tag ─────────────────────────────────────────────────────── + register_taxonomy( 'wmw_tag', 'wmw_article', array( + 'labels' => array( + 'name' => 'Wiki-Tags', + 'singular_name' => 'Tag', + 'add_new_item' => 'Neues Tag', + 'edit_item' => 'Tag bearbeiten', + 'all_items' => 'Alle Tags', + ), + 'public' => true, + 'hierarchical' => false, + 'show_in_rest' => true, + 'show_admin_column' => true, + 'rewrite' => array( 'slug' => 'wiki-tag' ), + ) ); + } +} diff --git a/includes/class-wmw-search.php b/includes/class-wmw-search.php new file mode 100644 index 0000000..0e340b6 --- /dev/null +++ b/includes/class-wmw-search.php @@ -0,0 +1,169 @@ +post_status !== 'publish' ) return; + + $wiki_id = (int) get_post_meta( $post_id, '_wmw_wiki_id', true ); + $keywords = wp_strip_all_tags( $post->post_title . ' ' . $post->post_content . ' ' . $post->post_excerpt ); + + $wpdb->replace( + $wpdb->prefix . 'wmw_search_index', + array( + 'article_id' => $post_id, + 'wiki_id' => $wiki_id, + 'keywords' => $keywords, + ), + array( '%d', '%d', '%s' ) + ); + } + + /** + * Artikel aus dem Index entfernen. + */ + public function remove_from_index( $post_id ) { + global $wpdb; + $wpdb->delete( $wpdb->prefix . 'wmw_search_index', array( 'article_id' => $post_id ), array( '%d' ) ); + } + + /** + * Alle vorhandenen Artikel in den Index aufnehmen. + * Wird aufgerufen wenn der Index leer ist (z.B. nach Gitea-Import). + */ + public static function reindex_all() { + global $wpdb; + + $articles = get_posts( array( + 'post_type' => 'wmw_article', + 'post_status' => 'publish', + 'posts_per_page' => -1, + ) ); + + foreach ( $articles as $post ) { + $wiki_id = (int) get_post_meta( $post->ID, '_wmw_wiki_id', true ); + $keywords = wp_strip_all_tags( $post->post_title . ' ' . $post->post_content . ' ' . $post->post_excerpt ); + + $wpdb->replace( + $wpdb->prefix . 'wmw_search_index', + array( + 'article_id' => $post->ID, + 'wiki_id' => $wiki_id, + 'keywords' => $keywords, + ), + array( '%d', '%d', '%s' ) + ); + } + + return count( $articles ); + } + + /** + * Artikel suchen. + * 1. Versucht den schnellen Custom-Index. + * 2. Fällt auf WP_Query zurück, wenn der Index leer ist (z.B. nach Gitea-Import). + * 3. Baut den Index automatisch auf, wenn er leer war. + */ + public static function search( $query, $wiki_id = 0, $limit = 20 ) { + global $wpdb; + + $table = $wpdb->prefix . 'wmw_search_index'; + $like = '%' . $wpdb->esc_like( $query ) . '%'; + + // ── Prüfen ob der Index überhaupt Einträge hat ──────────────────── + $index_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table" ); + + $ids = array(); + + if ( $index_count > 0 ) { + // ── Custom-Index nutzen ─────────────────────────────────────── + if ( $wiki_id ) { + $rows = $wpdb->get_results( $wpdb->prepare( + "SELECT article_id FROM $table WHERE wiki_id = %d AND keywords LIKE %s LIMIT %d", + $wiki_id, $like, $limit + ) ); + } else { + $rows = $wpdb->get_results( $wpdb->prepare( + "SELECT article_id FROM $table WHERE keywords LIKE %s LIMIT %d", + $like, $limit + ) ); + } + $ids = wp_list_pluck( $rows, 'article_id' ); + } + + // ── Fallback: WP_Query (wenn Index leer oder keine Treffer) ─────── + if ( empty( $ids ) ) { + $args = array( + 'post_type' => 'wmw_article', + 'post_status' => 'publish', + 'posts_per_page' => $limit, + 's' => $query, + 'orderby' => 'relevance', + ); + + if ( $wiki_id ) { + $args['meta_query'] = array( + array( + 'key' => '_wmw_wiki_id', + 'value' => $wiki_id, + ), + ); + } + + $fallback = get_posts( $args ); + + // Index war leer → jetzt automatisch aufbauen (im Hintergrund) + if ( $index_count === 0 ) { + self::reindex_all(); + } + + foreach ( $fallback as &$post ) { + $excerpt = $post->post_excerpt ?: wp_trim_words( $post->post_content, 25 ); + $post->wmw_excerpt = self::highlight( $excerpt, $query ); + $post->wmw_title = self::highlight( $post->post_title, $query ); + } + + return $fallback; + } + + // ── Artikel-Objekte aus IDs laden ───────────────────────────────── + $posts = get_posts( array( + 'post_type' => 'wmw_article', + 'post_status' => 'publish', + 'post__in' => $ids, + 'posts_per_page' => $limit, + 'orderby' => 'post_title', + 'order' => 'ASC', + ) ); + + foreach ( $posts as &$post ) { + $excerpt = $post->post_excerpt ?: wp_trim_words( $post->post_content, 25 ); + $post->wmw_excerpt = self::highlight( $excerpt, $query ); + $post->wmw_title = self::highlight( $post->post_title, $query ); + } + + return $posts; + } + + /** + * Suchtreffer hervorheben. + */ + public static function highlight( $text, $query ) { + $words = explode( ' ', preg_quote( trim( $query ), '/' ) ); + foreach ( $words as $word ) { + if ( strlen( $word ) < 2 ) continue; + $text = preg_replace( '/(' . $word . ')/iu', '$1', $text ); + } + return $text; + } +} \ No newline at end of file diff --git a/includes/class-wmw-shortcodes.php b/includes/class-wmw-shortcodes.php new file mode 100644 index 0000000..1eac0c9 --- /dev/null +++ b/includes/class-wmw-shortcodes.php @@ -0,0 +1,88 @@ + 0, + 'slug' => '', + 'layout' => 'grid', // grid | list + ), $atts, 'wmw_wiki' ); + + $wiki = null; + if ( $atts['id'] ) { + $wiki = get_post( absint( $atts['id'] ) ); + } elseif ( $atts['slug'] ) { + $wiki = get_page_by_path( $atts['slug'], OBJECT, 'wmw_wiki' ); + } + + if ( ! $wiki || $wiki->post_type !== 'wmw_wiki' ) return ''; + + ob_start(); + WMW_Frontend::render_wiki_index( $wiki, $atts['layout'] ); + return ob_get_clean(); + } + + /** + * [wmw_article id="5"] + */ + public function single_article( $atts ) { + $atts = shortcode_atts( array( 'id' => 0 ), $atts, 'wmw_article' ); + $article = get_post( absint( $atts['id'] ) ); + if ( ! $article || $article->post_type !== 'wmw_article' ) return ''; + + ob_start(); + WMW_Frontend::render_article( $article ); + return ob_get_clean(); + } + + /** + * [wmw_search wiki_id="1" placeholder="Suche..."] + */ + public function search_box( $atts ) { + $atts = shortcode_atts( array( + 'wiki_id' => 0, + 'placeholder' => 'Wiki durchsuchen…', + ), $atts, 'wmw_search' ); + + ob_start(); + WMW_Frontend::render_search_box( absint( $atts['wiki_id'] ), $atts['placeholder'] ); + return ob_get_clean(); + } + + /** + * [wmw_wiki_list columns="3"] + */ + public function wiki_list( $atts ) { + $atts = shortcode_atts( array( 'columns' => 3 ), $atts, 'wmw_wiki_list' ); + $wikis = wmw_get_wikis(); + if ( empty( $wikis ) ) return ''; + + ob_start(); + WMW_Frontend::render_wiki_list( $wikis, absint( $atts['columns'] ) ); + return ob_get_clean(); + } + + /** + * [wmw_breadcrumb] + */ + public function breadcrumb( $atts ) { + global $post; + if ( ! $post ) return ''; + ob_start(); + WMW_Frontend::render_breadcrumb( $post ); + return ob_get_clean(); + } +} diff --git a/includes/class-wmw-toc.php b/includes/class-wmw-toc.php new file mode 100644 index 0000000..b392aae --- /dev/null +++ b/includes/class-wmw-toc.php @@ -0,0 +1,68 @@ + ..., 'toc' => ...] + */ + public static function process( $content ) { + if ( empty( $content ) ) return array( 'content' => $content, 'toc' => '' ); + + $headings = array(); + $counter = array(); + + // Add IDs to h2, h3, h4 + $content = preg_replace_callback( + '/<(h[234])([^>]*)>(.*?)<\/h[234]>/si', + function( $matches ) use ( &$headings, &$counter ) { + $tag = $matches[1]; + $attrs = $matches[2]; + $text = wp_strip_all_tags( $matches[3] ); + $slug = sanitize_title( $text ); + + // Make unique + if ( isset( $counter[ $slug ] ) ) { + $counter[ $slug ]++; + $slug .= '-' . $counter[ $slug ]; + } else { + $counter[ $slug ] = 0; + } + + $headings[] = array( + 'level' => (int) substr( $tag, 1 ), + 'id' => $slug, + 'text' => $text, + ); + + return "<{$tag}{$attrs} id=\"{$slug}\">{$matches[3]}"; + }, + $content + ); + + if ( empty( $headings ) || count( $headings ) < 2 ) { + return array( 'content' => $content, 'toc' => '' ); + } + + // Build TOC HTML + $toc = '
'; + $toc .= '
' . __( 'Inhalt', 'wp-multi-wiki' ) . '
'; + $toc .= '
    '; + + $prev_level = 2; + foreach ( $headings as $h ) { + if ( $h['level'] > $prev_level ) { + $toc .= '
      '; + } elseif ( $h['level'] < $prev_level ) { + $toc .= '
    '; + } + $toc .= '
  1. ' . esc_html( $h['text'] ) . '
  2. '; + $prev_level = $h['level']; + } + + $toc .= '
'; + + return array( 'content' => $content, 'toc' => $toc ); + } +}