Files
WP-Multi-Wiki/includes/class-wmw-gitea-importer.php
2026-03-18 21:56:45 +01:00

352 lines
13 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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 ] : '';
}
}