Update from Git Manager GUI
This commit is contained in:
88
includes/class-wmw-activator.php
Normal file
88
includes/class-wmw-activator.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_Activator {
|
||||
|
||||
public static function activate() {
|
||||
global $wpdb;
|
||||
|
||||
$charset = $wpdb->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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
303
includes/class-wmw-admin.php
Normal file
303
includes/class-wmw-admin.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_Admin {
|
||||
|
||||
public function init() {
|
||||
add_action( 'admin_menu', array( $this, 'add_menus' ) );
|
||||
add_action( 'admin_enqueue_scripts',array( $this, 'enqueue_scripts' ) );
|
||||
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
|
||||
add_action( 'save_post', array( $this, 'save_meta_boxes' ) );
|
||||
add_filter( 'manage_wmw_article_posts_columns', array( $this, 'article_columns' ) );
|
||||
add_action( 'manage_wmw_article_posts_custom_column', array( $this, 'article_column_data' ), 10, 2 );
|
||||
add_filter( 'manage_wmw_wiki_posts_columns', array( $this, 'wiki_columns' ) );
|
||||
add_action( 'manage_wmw_wiki_posts_custom_column', array( $this, 'wiki_column_data' ), 10, 2 );
|
||||
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
|
||||
}
|
||||
|
||||
// ── Admin Menu ────────────────────────────────────────────────────────────
|
||||
|
||||
public function add_menus() {
|
||||
add_menu_page(
|
||||
'WP Multi Wiki',
|
||||
'WP Multi Wiki',
|
||||
'manage_options',
|
||||
'wp-multi-wiki',
|
||||
array( $this, 'page_dashboard' ),
|
||||
'dashicons-book-alt',
|
||||
30
|
||||
);
|
||||
|
||||
add_submenu_page( 'wp-multi-wiki', 'Alle Wikis', 'Alle Wikis', 'manage_options', 'wp-multi-wiki', array( $this, 'page_dashboard' ) );
|
||||
add_submenu_page( 'wp-multi-wiki', 'Neues Wiki', 'Neues Wiki', 'manage_options', 'wmw-new-wiki', array( $this, 'page_new_wiki' ) );
|
||||
add_submenu_page( 'wp-multi-wiki', 'Alle Artikel', 'Alle Artikel', 'manage_options', 'wmw-articles', array( $this, 'page_articles' ) );
|
||||
add_submenu_page( 'wp-multi-wiki', 'Neuer Artikel', 'Neuer Artikel', 'manage_options', 'wmw-new-article', array( $this, 'page_new_article' ) );
|
||||
add_submenu_page( 'wp-multi-wiki', 'Kategorien', 'Kategorien', 'manage_options', 'wmw-categories', array( $this, 'page_categories' ) );
|
||||
add_submenu_page( 'wp-multi-wiki', 'Einstellungen', 'Einstellungen', 'manage_options', 'wmw-settings', array( $this, 'page_settings' ) );
|
||||
add_submenu_page( 'wp-multi-wiki', '🐙 Gitea Import', '🐙 Gitea Import', 'manage_options', 'wmw-gitea-import', array( $this, 'page_gitea_import' ) );
|
||||
|
||||
// Hidden edit pages
|
||||
add_submenu_page( 'wp-multi-wiki', 'Wiki bearbeiten', '', 'manage_options', 'wmw-edit-wiki', array( $this, 'page_edit_wiki' ) );
|
||||
add_submenu_page( 'wp-multi-wiki', 'Artikel bearbeiten','', 'manage_options', 'wmw-edit-article', array( $this, 'page_edit_article' ) );
|
||||
}
|
||||
|
||||
// ── Scripts & Styles ──────────────────────────────────────────────────────
|
||||
|
||||
public function enqueue_scripts( $hook ) {
|
||||
if ( strpos( $hook, 'wp-multi-wiki' ) === false && strpos( $hook, 'wmw-' ) === false ) return;
|
||||
|
||||
wp_enqueue_style(
|
||||
'wmw-admin',
|
||||
WMW_PLUGIN_URL . 'admin/css/wmw-admin.css',
|
||||
array(),
|
||||
WMW_VERSION
|
||||
);
|
||||
wp_enqueue_style( 'wp-color-picker' );
|
||||
wp_enqueue_script(
|
||||
'wmw-admin',
|
||||
WMW_PLUGIN_URL . 'admin/js/wmw-admin.js',
|
||||
array( 'jquery', 'wp-color-picker' ),
|
||||
WMW_VERSION,
|
||||
true
|
||||
);
|
||||
wp_localize_script( 'wmw-admin', 'wmwAdmin', array(
|
||||
'ajaxUrl' => 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';
|
||||
?>
|
||||
<p>
|
||||
<label><strong>Icon (Emoji)</strong></label><br>
|
||||
<input type="text" name="wmw_icon" value="<?php echo esc_attr( $icon ); ?>" style="width:100%;font-size:20px;">
|
||||
</p>
|
||||
<p>
|
||||
<label><strong>Akzentfarbe</strong></label><br>
|
||||
<input type="text" name="wmw_color" value="<?php echo esc_attr( $color ); ?>" class="wmw-color-picker">
|
||||
</p>
|
||||
<p>
|
||||
<label><strong>Plugin-Version</strong></label><br>
|
||||
<input type="text" name="wmw_version" value="<?php echo esc_attr( $ver ); ?>" style="width:100%;">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function mb_article_meta( $post ) {
|
||||
wp_nonce_field( 'wmw_save_article', 'wmw_article_nonce' );
|
||||
$wiki_id = (int) get_post_meta( $post->ID, '_wmw_wiki_id', true );
|
||||
$order = (int) get_post_meta( $post->ID, '_wmw_order', true );
|
||||
$wikis = wmw_get_wikis();
|
||||
?>
|
||||
<p>
|
||||
<label><strong>Gehört zu Wiki</strong></label><br>
|
||||
<select name="wmw_wiki_id" style="width:100%">
|
||||
<option value="">— Kein Wiki —</option>
|
||||
<?php foreach ( $wikis as $wiki ) : ?>
|
||||
<option value="<?php echo $wiki->ID; ?>" <?php selected( $wiki_id, $wiki->ID ); ?>>
|
||||
<?php echo esc_html( $wiki->post_title ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label><strong>Sortierreihenfolge</strong></label><br>
|
||||
<input type="number" name="wmw_order" value="<?php echo esc_attr( $order ); ?>" style="width:100%;" min="0">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function save_meta_boxes( $post_id ) {
|
||||
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
|
||||
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
|
||||
|
||||
// Save wiki options
|
||||
if ( isset( $_POST['wmw_wiki_nonce'] ) && wp_verify_nonce( $_POST['wmw_wiki_nonce'], 'wmw_save_wiki' ) ) {
|
||||
update_post_meta( $post_id, '_wmw_icon', sanitize_text_field( $_POST['wmw_icon'] ?? '📖' ) );
|
||||
update_post_meta( $post_id, '_wmw_color', sanitize_hex_color( $_POST['wmw_color'] ?? '#2271b1' ) );
|
||||
update_post_meta( $post_id, '_wmw_version', sanitize_text_field( $_POST['wmw_version'] ?? '1.0.0' ) );
|
||||
}
|
||||
|
||||
// Save article meta
|
||||
if ( isset( $_POST['wmw_article_nonce'] ) && wp_verify_nonce( $_POST['wmw_article_nonce'], 'wmw_save_article' ) ) {
|
||||
$wiki_id = absint( $_POST['wmw_wiki_id'] ?? 0 );
|
||||
update_post_meta( $post_id, '_wmw_wiki_id', $wiki_id );
|
||||
update_post_meta( $post_id, '_wmw_order', absint( $_POST['wmw_order'] ?? 0 ) );
|
||||
|
||||
// Update search index
|
||||
$this->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 ? '<a href="' . admin_url( 'admin.php?page=wmw-edit-wiki&id=' . $wiki->ID ) . '">' . esc_html( $wiki->post_title ) . '</a>' : '—';
|
||||
}
|
||||
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 '<strong>' . $count . '</strong>';
|
||||
}
|
||||
if ( 'wmw_icon' === $column ) {
|
||||
echo '<span style="font-size:20px">' . wmw_get_wiki_icon( $post_id ) . '</span>';
|
||||
}
|
||||
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 '<div class="notice notice-success is-dismissible"><p>✅ Gespeichert!</p></div>';
|
||||
}
|
||||
if ( isset( $_GET['wmw_deleted'] ) ) {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>🗑️ Gelöscht!</p></div>';
|
||||
}
|
||||
if ( isset( $_GET['wmw_error'] ) ) {
|
||||
echo '<div class="notice notice-error is-dismissible"><p>❌ Fehler: ' . esc_html( $_GET['wmw_error'] ) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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 '<div class="notice notice-success is-dismissible"><p>✅ Einstellungen gespeichert!</p></div>';
|
||||
}
|
||||
|
||||
include WMW_PLUGIN_DIR . 'templates/admin/settings.php';
|
||||
}
|
||||
}
|
||||
208
includes/class-wmw-ajax.php
Normal file
208
includes/class-wmw-ajax.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_Ajax {
|
||||
|
||||
public function init() {
|
||||
// Admin AJAX
|
||||
add_action( 'wp_ajax_wmw_save_wiki', array( $this, 'save_wiki' ) );
|
||||
add_action( 'wp_ajax_wmw_delete_wiki', array( $this, 'delete_wiki' ) );
|
||||
add_action( 'wp_ajax_wmw_save_article', array( $this, 'save_article' ) );
|
||||
add_action( 'wp_ajax_wmw_delete_article', array( $this, 'delete_article' ) );
|
||||
add_action( 'wp_ajax_wmw_reindex', array( $this, 'reindex' ) );
|
||||
add_action( 'wp_ajax_wmw_reorder', array( $this, 'reorder_articles' ) );
|
||||
|
||||
// Public AJAX
|
||||
add_action( 'wp_ajax_wmw_search', array( $this, 'ajax_search' ) );
|
||||
add_action( 'wp_ajax_nopriv_wmw_search', array( $this, 'ajax_search' ) );
|
||||
add_action( 'wp_ajax_wmw_track_view', array( $this, 'track_view' ) );
|
||||
add_action( 'wp_ajax_nopriv_wmw_track_view', array( $this, 'track_view' ) );
|
||||
}
|
||||
|
||||
private function verify_nonce( $action = 'wmw_admin_nonce' ) {
|
||||
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], $action ) ) {
|
||||
wp_send_json_error( array( 'message' => '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();
|
||||
}
|
||||
}
|
||||
116
includes/class-wmw-core.php
Normal file
116
includes/class-wmw-core.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* WMW_Core – Bootstrap singleton.
|
||||
* Wires up all sub-systems and handles asset enqueueing.
|
||||
*/
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
final class WMW_Core {
|
||||
|
||||
private static ?WMW_Core $instance = null;
|
||||
|
||||
private function __construct() {
|
||||
new WMW_CPT();
|
||||
new WMW_Admin();
|
||||
new WMW_Frontend();
|
||||
new WMW_Search();
|
||||
new WMW_Shortcodes();
|
||||
|
||||
add_action( 'wp_enqueue_scripts', [ $this, 'frontend_assets' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'admin_assets' ] );
|
||||
add_filter( 'plugin_action_links_' . WMW_BASENAME, [ $this, 'action_links' ] );
|
||||
add_action( 'init', [ $this, 'load_textdomain' ] );
|
||||
}
|
||||
|
||||
public static function get_instance(): WMW_Core {
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/* ── Textdomain ─────────────────────────────────────────────── */
|
||||
|
||||
public function load_textdomain(): void {
|
||||
load_plugin_textdomain( 'wp-multi-wiki', false, dirname( WMW_BASENAME ) . '/languages' );
|
||||
}
|
||||
|
||||
/* ── Frontend Assets ────────────────────────────────────────── */
|
||||
|
||||
public function frontend_assets(): void {
|
||||
if ( ! $this->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,
|
||||
'<a href="' . admin_url( 'admin.php?page=wp-multi-wiki' ) . '">' . __( 'Dashboard', 'wp-multi-wiki' ) . '</a>'
|
||||
);
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
114
includes/class-wmw-cpt.php
Normal file
114
includes/class-wmw-cpt.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* WMW_CPT – Registers Custom Post Types and Taxonomies.
|
||||
*/
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class WMW_CPT {
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'init', [ __CLASS__, 'register' ], 0 );
|
||||
}
|
||||
|
||||
public static function register(): void {
|
||||
self::register_wiki();
|
||||
self::register_article();
|
||||
self::register_category();
|
||||
self::register_tag();
|
||||
}
|
||||
|
||||
/* ── Wiki Container ─────────────────────────────────────────── */
|
||||
|
||||
private static function register_wiki(): void {
|
||||
register_post_type( 'wmw_wiki', [
|
||||
'labels' => [
|
||||
'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 ],
|
||||
] );
|
||||
}
|
||||
}
|
||||
189
includes/class-wmw-frontend.php
Normal file
189
includes/class-wmw-frontend.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_Frontend {
|
||||
|
||||
public function init() {
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) );
|
||||
add_filter( 'the_content', array( $this, 'filter_content' ) );
|
||||
add_filter( 'the_title', array( $this, 'filter_title' ), 10, 2 );
|
||||
add_action( 'wp_head', array( $this, 'inject_css_vars' ) );
|
||||
add_filter( 'template_include', array( $this, 'load_template' ) );
|
||||
}
|
||||
|
||||
public function enqueue() {
|
||||
$settings = get_option( 'wmw_settings', array() );
|
||||
|
||||
wp_enqueue_style(
|
||||
'wmw-public',
|
||||
WMW_PLUGIN_URL . 'public/css/wmw-public.css',
|
||||
array(),
|
||||
WMW_VERSION
|
||||
);
|
||||
|
||||
// Custom CSS from settings
|
||||
if ( ! empty( $settings['custom_css'] ) ) {
|
||||
wp_add_inline_style( 'wmw-public', $settings['custom_css'] );
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'wmw-public',
|
||||
WMW_PLUGIN_URL . 'public/js/wmw-public.js',
|
||||
array( 'jquery' ),
|
||||
WMW_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script( 'wmw-public', 'wmwPublic', array(
|
||||
'ajaxUrl' => 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 "<style>:root { --wmw-accent: {$color}; --wmw-accent-light: {$color}22; }</style>\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();
|
||||
}
|
||||
}
|
||||
351
includes/class-wmw-gitea-importer.php
Normal file
351
includes/class-wmw-gitea-importer.php
Normal file
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
/**
|
||||
* WMW Gitea Importer – PHP 7.4 kompatibel
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WMW_Gitea_Importer {
|
||||
|
||||
/** @var string */
|
||||
private $api_base;
|
||||
/** @var string */
|
||||
private $owner;
|
||||
/** @var string */
|
||||
private $repo;
|
||||
/** @var string */
|
||||
private $token;
|
||||
|
||||
public function __construct( $gitea_url, $owner, $repo, $token = '' ) {
|
||||
$base = rtrim( preg_replace( '#/(wiki.*)?$#', '', $gitea_url ), '/' );
|
||||
$this->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 ] = '<pre><code' . $lang . '>' . esc_html( $m[2] ) . '</code></pre>';
|
||||
return $token;
|
||||
},
|
||||
$md
|
||||
);
|
||||
|
||||
// Inline Code
|
||||
$md = preg_replace( '/`([^`\n]+)`/', '<code>$1</code>', $md );
|
||||
|
||||
// Überschriften
|
||||
$md = preg_replace( '/^######\s+(.+)$/m', '<h6>$1</h6>', $md );
|
||||
$md = preg_replace( '/^#####\s+(.+)$/m', '<h5>$1</h5>', $md );
|
||||
$md = preg_replace( '/^####\s+(.+)$/m', '<h4>$1</h4>', $md );
|
||||
$md = preg_replace( '/^###\s+(.+)$/m', '<h3>$1</h3>', $md );
|
||||
$md = preg_replace( '/^##\s+(.+)$/m', '<h2>$1</h2>', $md );
|
||||
$md = preg_replace( '/^#\s+(.+)$/m', '<h1>$1</h1>', $md );
|
||||
|
||||
// Formatierung
|
||||
$md = preg_replace( '/\*\*\*(.+?)\*\*\*/s', '<strong><em>$1</em></strong>', $md );
|
||||
$md = preg_replace( '/\*\*(.+?)\*\*/s', '<strong>$1</strong>', $md );
|
||||
$md = preg_replace( '/\*(.+?)\*/s', '<em>$1</em>', $md );
|
||||
$md = preg_replace( '/__(.+?)__/s', '<strong>$1</strong>', $md );
|
||||
$md = preg_replace( '/_([^_\s][^_]*)_/', '<em>$1</em>', $md );
|
||||
$md = preg_replace( '/~~(.+?)~~/s', '<del>$1</del>', $md );
|
||||
|
||||
// Bilder & Links
|
||||
$md = preg_replace( '/!\[([^\]]*)\]\(([^)]+)\)/', '<img src="$2" alt="$1" style="max-width:100%">', $md );
|
||||
$md = preg_replace( '/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $md );
|
||||
|
||||
// Blockquote & HR
|
||||
$md = preg_replace( '/^>\s+(.+)$/m', '<blockquote>$1</blockquote>', $md );
|
||||
$md = preg_replace( '/^(\-{3,}|\*{3,}|_{3,})$/m', '<hr>', $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 .= '<p>' . nl2br( $block ) . "</p>\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 = '<table>';
|
||||
$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 .= '<tr>';
|
||||
foreach ( $cells as $cell ) {
|
||||
$html .= '<' . $tag . '>' . $cell . '</' . $tag . '>';
|
||||
}
|
||||
$html .= '</tr>';
|
||||
$first = false;
|
||||
}
|
||||
return $html . '</table>';
|
||||
}
|
||||
|
||||
/** @param array $m */
|
||||
private static function cb_ul( $m ) {
|
||||
$lines = array_filter( explode( "\n", trim( $m[0] ) ) );
|
||||
$html = '<ul>';
|
||||
foreach ( $lines as $line ) {
|
||||
$text = preg_replace( '/^[ \t]*[\*\-\+] /', '', $line );
|
||||
$html .= '<li>' . $text . '</li>';
|
||||
}
|
||||
return $html . '</ul>';
|
||||
}
|
||||
|
||||
/** @param array $m */
|
||||
private static function cb_ol( $m ) {
|
||||
$lines = array_filter( explode( "\n", trim( $m[0] ) ) );
|
||||
$html = '<ol>';
|
||||
foreach ( $lines as $line ) {
|
||||
$text = preg_replace( '/^[ \t]*\d+\. /', '', $line );
|
||||
$html .= '<li>' . $text . '</li>';
|
||||
}
|
||||
return $html . '</ol>';
|
||||
}
|
||||
|
||||
/** @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 ] : '';
|
||||
}
|
||||
}
|
||||
165
includes/class-wmw-metaboxes.php
Normal file
165
includes/class-wmw-metaboxes.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) { exit; }
|
||||
|
||||
class WMW_Metaboxes {
|
||||
|
||||
public static function init() {
|
||||
add_action( 'add_meta_boxes', [ __CLASS__, 'register' ] );
|
||||
add_action( 'save_post_wmw_article', [ __CLASS__, 'save_article' ], 10, 2 );
|
||||
add_action( 'save_post_wmw_wiki', [ __CLASS__, 'save_wiki' ], 10, 2 );
|
||||
}
|
||||
|
||||
public static function register() {
|
||||
add_meta_box(
|
||||
'wmw_article_settings',
|
||||
'Wiki-Einstellungen',
|
||||
[ __CLASS__, 'article_settings_html' ],
|
||||
'wmw_article',
|
||||
'side',
|
||||
'high'
|
||||
);
|
||||
add_meta_box(
|
||||
'wmw_wiki_settings',
|
||||
'Wiki-Einstellungen',
|
||||
[ __CLASS__, 'wiki_settings_html' ],
|
||||
'wmw_wiki',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
|
||||
// ── Artikel Meta-Box ────────────────────────────────────────────────────
|
||||
public static function article_settings_html( $post ) {
|
||||
wp_nonce_field( 'wmw_save_article', 'wmw_article_nonce' );
|
||||
|
||||
$wiki_id = get_post_meta( $post->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',
|
||||
] );
|
||||
?>
|
||||
<p>
|
||||
<label for="wmw_wiki_id"><strong>Zugehoeriges Wiki</strong></label><br>
|
||||
<select name="wmw_wiki_id" id="wmw_wiki_id" style="width:100%;">
|
||||
<option value="">-- Wiki waehlen --</option>
|
||||
<?php foreach ( $wikis as $wiki ) : ?>
|
||||
<option value="<?php echo esc_attr( $wiki->ID ); ?>"
|
||||
<?php selected( $wiki_id, $wiki->ID ); ?>>
|
||||
<?php echo esc_html( $wiki->post_title ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="wmw_order"><strong>Reihenfolge</strong></label><br>
|
||||
<input type="number" name="wmw_order" id="wmw_order"
|
||||
value="<?php echo esc_attr( $order ); ?>"
|
||||
style="width:100%;" min="0" step="1">
|
||||
<em style="font-size:11px;">Niedrigere Zahl = weiter oben</em>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="wmw_hide_toc" value="1" <?php checked( $hide_toc, '1' ); ?>>
|
||||
Inhaltsverzeichnis ausblenden
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
// ── Wiki Meta-Box ───────────────────────────────────────────────────────
|
||||
public static function wiki_settings_html( $post ) {
|
||||
wp_nonce_field( 'wmw_save_wiki', 'wmw_wiki_nonce' );
|
||||
|
||||
$icon_url = get_post_meta( $post->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 );
|
||||
?>
|
||||
<table class="form-table" style="margin:0;">
|
||||
<tr>
|
||||
<th><label for="wmw_color">Farbe</label></th>
|
||||
<td>
|
||||
<input type="color" name="wmw_color" id="wmw_color"
|
||||
value="<?php echo esc_attr( $color ); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="wmw_icon_class">Dashicon</label></th>
|
||||
<td>
|
||||
<input type="text" name="wmw_icon_class" id="wmw_icon_class"
|
||||
value="<?php echo esc_attr( $icon_class ); ?>"
|
||||
placeholder="dashicons-book-alt" style="width:100%;">
|
||||
<em style="font-size:11px;">
|
||||
<a href="https://developer.wordpress.org/resource/dashicons/" target="_blank">Dashicons anzeigen</a>
|
||||
</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="wmw_version">Version</label></th>
|
||||
<td>
|
||||
<input type="text" name="wmw_version" id="wmw_version"
|
||||
value="<?php echo esc_attr( $version ); ?>"
|
||||
placeholder="z.B. 2.3.1" style="width:100%;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="wmw_icon_url">Icon URL</label></th>
|
||||
<td>
|
||||
<input type="url" name="wmw_icon_url" id="wmw_icon_url"
|
||||
value="<?php echo esc_attr( $icon_url ); ?>"
|
||||
placeholder="https://..." style="width:100%;">
|
||||
<em style="font-size:11px;">Optionales eigenes Icon (ueberschreibt Dashicon)</em>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
// ── Speichern: Artikel ──────────────────────────────────────────────────
|
||||
public static function save_article( $post_id, $post ) {
|
||||
if ( ! isset( $_POST['wmw_article_nonce'] ) ) return;
|
||||
if ( ! wp_verify_nonce( $_POST['wmw_article_nonce'], 'wmw_save_article' ) ) return;
|
||||
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) return;
|
||||
if ( ! current_user_can( 'manage_options' ) ) return;
|
||||
|
||||
if ( isset( $_POST['wmw_wiki_id'] ) ) {
|
||||
$wiki_id = intval( $_POST['wmw_wiki_id'] );
|
||||
if ( $wiki_id > 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 ] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
includes/class-wmw-post-types.php
Normal file
95
includes/class-wmw-post-types.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_Post_Types {
|
||||
|
||||
public function register() {
|
||||
add_action( 'init', array( $this, 'register_post_types' ) );
|
||||
add_action( 'init', array( $this, 'register_taxonomies' ) );
|
||||
}
|
||||
|
||||
public function register_post_types() {
|
||||
|
||||
// ── Wiki (Container) ───────────────────────────────────────────────
|
||||
register_post_type( 'wmw_wiki', array(
|
||||
'labels' => 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' ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
169
includes/class-wmw-search.php
Normal file
169
includes/class-wmw-search.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_Search {
|
||||
|
||||
public function init() {
|
||||
add_action( 'save_post_wmw_article', array( $this, 'index_article' ), 10, 2 );
|
||||
add_action( 'delete_post', array( $this, 'remove_from_index' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Einen Artikel in den Suchindex schreiben.
|
||||
*/
|
||||
public function index_article( $post_id, $post ) {
|
||||
global $wpdb;
|
||||
if ( $post->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', '<mark class="wmw-highlight">$1</mark>', $text );
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
88
includes/class-wmw-shortcodes.php
Normal file
88
includes/class-wmw-shortcodes.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_Shortcodes {
|
||||
|
||||
public function register() {
|
||||
add_shortcode( 'wmw_wiki', array( $this, 'wiki_index' ) );
|
||||
add_shortcode( 'wmw_article', array( $this, 'single_article' ) );
|
||||
add_shortcode( 'wmw_search', array( $this, 'search_box' ) );
|
||||
add_shortcode( 'wmw_wiki_list', array( $this, 'wiki_list' ) );
|
||||
add_shortcode( 'wmw_breadcrumb', array( $this, 'breadcrumb' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* [wmw_wiki id="1"] or [wmw_wiki slug="my-plugin"]
|
||||
*/
|
||||
public function wiki_index( $atts ) {
|
||||
$atts = shortcode_atts( array(
|
||||
'id' => 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();
|
||||
}
|
||||
}
|
||||
68
includes/class-wmw-toc.php
Normal file
68
includes/class-wmw-toc.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
class WMW_TOC {
|
||||
|
||||
/**
|
||||
* Generate TOC and add anchor IDs to headings in content.
|
||||
* Returns array: ['content' => ..., '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]}</{$tag}>";
|
||||
},
|
||||
$content
|
||||
);
|
||||
|
||||
if ( empty( $headings ) || count( $headings ) < 2 ) {
|
||||
return array( 'content' => $content, 'toc' => '' );
|
||||
}
|
||||
|
||||
// Build TOC HTML
|
||||
$toc = '<div class="wmw-toc">';
|
||||
$toc .= '<div class="wmw-toc__title"><span>' . __( 'Inhalt', 'wp-multi-wiki' ) . '</span><button class="wmw-toc__toggle" aria-label="TOC ausklappen">▼</button></div>';
|
||||
$toc .= '<ol class="wmw-toc__list">';
|
||||
|
||||
$prev_level = 2;
|
||||
foreach ( $headings as $h ) {
|
||||
if ( $h['level'] > $prev_level ) {
|
||||
$toc .= '<ol class="wmw-toc__sub">';
|
||||
} elseif ( $h['level'] < $prev_level ) {
|
||||
$toc .= '</ol>';
|
||||
}
|
||||
$toc .= '<li><a href="#' . esc_attr( $h['id'] ) . '">' . esc_html( $h['text'] ) . '</a></li>';
|
||||
$prev_level = $h['level'];
|
||||
}
|
||||
|
||||
$toc .= '</ol></div>';
|
||||
|
||||
return array( 'content' => $content, 'toc' => $toc );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user