352 lines
13 KiB
PHP
352 lines
13 KiB
PHP
<?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 ] : '';
|
||
}
|
||
}
|