new file: olm-login.php

This commit is contained in:
Sven Steinert
2026-05-27 14:17:22 +02:00
parent 1d4cf6e727
commit e99acdce47
25 changed files with 36226 additions and 630 deletions

View File

@@ -19,107 +19,153 @@ final class ProductsPage
?>
<div class="wrap">
<h1><?php esc_html_e('Documentation Products', 'kb-markdown-importer'); ?></h1>
<p><?php esc_html_e('Manage imported products, repair their URL slugs, or remove a broken import from the frontend.', 'kb-markdown-importer'); ?></p>
<p><?php esc_html_e('Manage imported products, group them into frontend products, and assign categories.', 'kb-markdown-importer'); ?></p>
<?php settings_errors('kb_markdown_products'); ?>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e('Product', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Slug', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Versions', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Pages', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Actions', 'kb-markdown-importer'); ?></th>
</tr>
</thead>
<tbody>
<?php if (! $products) : ?>
<tr><td colspan="5"><?php esc_html_e('No products have been imported yet.', 'kb-markdown-importer'); ?></td></tr>
<?php endif; ?>
<?php foreach ($products as $item) : ?>
<?php
$term = $item['term'];
$formId = 'kb-markdown-product-' . (int) $term->term_id;
$versions = array_map(static fn (\WP_Term $version): string => $version->name, (array) $item['versions']);
?>
<form method="post" action="">
<?php wp_nonce_field('kb_markdown_save_all_products'); ?>
<table class="widefat striped">
<thead>
<tr>
<td>
<input form="<?php echo esc_attr($formId); ?>" class="regular-text" type="text" name="product_name" value="<?php echo esc_attr($term->name); ?>" aria-label="<?php esc_attr_e('Product name', 'kb-markdown-importer'); ?>">
</td>
<td>
<input form="<?php echo esc_attr($formId); ?>" class="regular-text" type="text" name="product_slug" value="<?php echo esc_attr($term->slug); ?>" aria-label="<?php esc_attr_e('Product slug', 'kb-markdown-importer'); ?>">
</td>
<td><?php echo esc_html($versions ? implode(', ', $versions) : __('No versions', 'kb-markdown-importer')); ?></td>
<td><?php echo esc_html((string) $item['page_count']); ?></td>
<td>
<form id="<?php echo esc_attr($formId); ?>" method="post" action="">
<?php wp_nonce_field('kb_markdown_update_product_' . $term->term_id); ?>
<input type="hidden" name="kb_markdown_product_action" value="update">
<input type="hidden" name="term_id" value="<?php echo esc_attr((string) $term->term_id); ?>">
<?php submit_button(__('Save', 'kb-markdown-importer'), 'secondary small', 'submit', false); ?>
</form>
<form method="post" action="" style="margin-top:8px;" onsubmit="return window.confirm('<?php echo esc_js(__('Move this product and its imported pages to the trash?', 'kb-markdown-importer')); ?>');">
<?php wp_nonce_field('kb_markdown_delete_product_' . $term->term_id); ?>
<input type="hidden" name="kb_markdown_product_action" value="delete">
<input type="hidden" name="term_id" value="<?php echo esc_attr((string) $term->term_id); ?>">
<label>
<input type="checkbox" name="trash_pages" value="1" checked>
<?php esc_html_e('Trash imported pages', 'kb-markdown-importer'); ?>
</label>
<?php submit_button(__('Delete product', 'kb-markdown-importer'), 'delete small', 'submit', false); ?>
</form>
</td>
<th><?php esc_html_e('Imported product', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Frontend product', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Documentation part', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Category', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Versions', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Pages', 'kb-markdown-importer'); ?></th>
<th><?php esc_html_e('Delete', 'kb-markdown-importer'); ?></th>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</thead>
<tbody>
<?php if (! $products) : ?>
<tr><td colspan="7"><?php esc_html_e('No products have been imported yet.', 'kb-markdown-importer'); ?></td></tr>
<?php endif; ?>
<?php foreach ($products as $item) : ?>
<?php
$term = $item['term'];
$meta = (array) $item['meta'];
$versions = array_map(static fn (\WP_Term $version): string => $version->name, (array) $item['versions']);
?>
<tr>
<td>
<input type="hidden" name="products[<?php echo esc_attr((string) $term->term_id); ?>][term_id]" value="<?php echo esc_attr((string) $term->term_id); ?>">
<strong><?php echo esc_html($term->name); ?></strong>
<br>
<code><?php echo esc_html($term->slug); ?></code>
<input type="hidden" name="products[<?php echo esc_attr((string) $term->term_id); ?>][product_name]" value="<?php echo esc_attr($term->name); ?>">
<input type="hidden" name="products[<?php echo esc_attr((string) $term->term_id); ?>][product_slug]" value="<?php echo esc_attr($term->slug); ?>">
</td>
<td>
<label class="screen-reader-text" for="group_name_<?php echo esc_attr((string) $term->term_id); ?>"><?php esc_html_e('Frontend product name', 'kb-markdown-importer'); ?></label>
<input id="group_name_<?php echo esc_attr((string) $term->term_id); ?>" class="regular-text" type="text" name="products[<?php echo esc_attr((string) $term->term_id); ?>][group_name]" value="<?php echo esc_attr((string) $meta['group_name']); ?>" placeholder="<?php echo esc_attr($term->name); ?>">
<br>
<label class="screen-reader-text" for="group_slug_<?php echo esc_attr((string) $term->term_id); ?>"><?php esc_html_e('Frontend product slug', 'kb-markdown-importer'); ?></label>
<input id="group_slug_<?php echo esc_attr((string) $term->term_id); ?>" class="regular-text" type="text" name="products[<?php echo esc_attr((string) $term->term_id); ?>][group_slug]" value="<?php echo esc_attr((string) $meta['group_slug']); ?>" placeholder="<?php echo esc_attr($term->slug); ?>">
</td>
<td>
<input class="regular-text" type="text" name="products[<?php echo esc_attr((string) $term->term_id); ?>][part_label]" value="<?php echo esc_attr((string) $meta['part_label']); ?>" placeholder="<?php esc_attr_e('App, Modul, Exporter', 'kb-markdown-importer'); ?>">
</td>
<td>
<input class="regular-text" type="text" name="products[<?php echo esc_attr((string) $term->term_id); ?>][category]" value="<?php echo esc_attr((string) $meta['category']); ?>" placeholder="<?php esc_attr_e('CRM, Telefonie, Integration', 'kb-markdown-importer'); ?>">
</td>
<td><?php echo esc_html($versions ? implode(', ', $versions) : __('No versions', 'kb-markdown-importer')); ?></td>
<td><?php echo esc_html((string) $item['page_count']); ?></td>
<td>
<label>
<input type="checkbox" name="delete_terms[]" value="<?php echo esc_attr((string) $term->term_id); ?>">
<?php esc_html_e('Trash pages and remove product', 'kb-markdown-importer'); ?>
</label>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php submit_button(__('Save all products', 'kb-markdown-importer'), 'primary', 'kb_markdown_save_all_products', false); ?>
<?php submit_button(__('Delete selected', 'kb-markdown-importer'), 'delete', 'kb_markdown_delete_selected_products', false, [
'onclick' => "return window.confirm('" . esc_js(__('Move selected products and their imported pages to the trash?', 'kb-markdown-importer')) . "');",
]); ?>
</form>
</div>
<?php
}
private static function handleActions(ProductRepository $repository): void
{
$action = sanitize_key(wp_unslash((string) ($_POST['kb_markdown_product_action'] ?? '')));
$action = '';
if (isset($_POST['kb_markdown_save_all_products'])) {
$action = 'save_all';
} elseif (isset($_POST['kb_markdown_delete_selected_products'])) {
$action = 'delete_selected';
} else {
$action = sanitize_key(wp_unslash((string) ($_POST['kb_markdown_product_action'] ?? '')));
}
if (! $action) {
return;
}
$termId = absint($_POST['term_id'] ?? 0);
if ('save_all' === $action) {
check_admin_referer('kb_markdown_save_all_products');
$products = (array) ($_POST['products'] ?? []);
$saved = 0;
if (! $termId) {
add_settings_error('kb_markdown_products', 'missing_term', __('Missing product ID.', 'kb-markdown-importer'), 'error');
foreach ($products as $rawTermId => $rawProduct) {
$termId = absint($rawTermId);
$rawProduct = (array) $rawProduct;
if (! $termId) {
continue;
}
$result = $repository->update(
$termId,
sanitize_text_field(wp_unslash((string) ($rawProduct['product_name'] ?? ''))),
sanitize_title(wp_unslash((string) ($rawProduct['product_slug'] ?? ''))),
[
'group_name' => sanitize_text_field(wp_unslash((string) ($rawProduct['group_name'] ?? ''))),
'group_slug' => sanitize_title(wp_unslash((string) ($rawProduct['group_slug'] ?? ''))),
'part_label' => sanitize_text_field(wp_unslash((string) ($rawProduct['part_label'] ?? ''))),
'category' => sanitize_text_field(wp_unslash((string) ($rawProduct['category'] ?? ''))),
]
);
if (is_wp_error($result)) {
add_settings_error('kb_markdown_products', 'update_failed_' . $termId, $result->get_error_message(), 'error');
continue;
}
$saved++;
}
add_settings_error('kb_markdown_products', 'updated', sprintf(
/* translators: %d: number of saved products. */
__('Saved %d products.', 'kb-markdown-importer'),
$saved
), 'success');
return;
}
if ('update' === $action) {
check_admin_referer('kb_markdown_update_product_' . $termId);
$result = $repository->update(
$termId,
sanitize_text_field(wp_unslash((string) ($_POST['product_name'] ?? ''))),
sanitize_title(wp_unslash((string) ($_POST['product_slug'] ?? '')))
);
if ('delete_selected' === $action) {
check_admin_referer('kb_markdown_save_all_products');
$termIds = array_map('absint', (array) ($_POST['delete_terms'] ?? []));
$deleted = 0;
if (is_wp_error($result)) {
add_settings_error('kb_markdown_products', 'update_failed', $result->get_error_message(), 'error');
return;
foreach (array_filter($termIds) as $termId) {
$result = $repository->deleteProduct($termId, true);
if (is_wp_error($result)) {
add_settings_error('kb_markdown_products', 'delete_failed_' . $termId, $result->get_error_message(), 'error');
continue;
}
$deleted++;
}
add_settings_error('kb_markdown_products', 'updated', __('Product saved.', 'kb-markdown-importer'), 'success');
return;
}
if ('delete' === $action) {
check_admin_referer('kb_markdown_delete_product_' . $termId);
$trashPages = ! empty($_POST['trash_pages']);
$result = $repository->deleteProduct($termId, $trashPages);
if (is_wp_error($result)) {
add_settings_error('kb_markdown_products', 'delete_failed', $result->get_error_message(), 'error');
return;
}
add_settings_error('kb_markdown_products', 'deleted', __('Product deleted.', 'kb-markdown-importer'), 'success');
add_settings_error('kb_markdown_products', 'deleted', sprintf(
/* translators: %d: number of deleted products. */
__('Deleted %d products.', 'kb-markdown-importer'),
$deleted
), 'success');
}
}
}

View File

@@ -5,6 +5,7 @@ namespace KbMarkdownImporter\Admin;
use KbMarkdownImporter\GitLab\GitLabClient;
use KbMarkdownImporter\Import\ImportLogger;
use KbMarkdownImporter\Olm\ChangelogSync;
use KbMarkdownImporter\Plugin;
use KbMarkdownImporter\Settings;
@@ -40,20 +41,12 @@ final class SettingsPage
$settings['custom_theme_css_url'] = esc_url_raw((string) ($input['custom_theme_css_url'] ?? ''));
$settings['docs_home_intro_title'] = sanitize_text_field((string) ($input['docs_home_intro_title'] ?? $settings['docs_home_intro_title']));
$settings['docs_home_intro_content'] = wp_kses_post((string) ($input['docs_home_intro_content'] ?? $settings['docs_home_intro_content']));
$settings['product_updates_source'] = in_array(($input['product_updates_source'] ?? 'rss'), ['rss', 'rest'], true) ? (string) $input['product_updates_source'] : 'rss';
$settings['product_updates_feed_url'] = esc_url_raw((string) ($input['product_updates_feed_url'] ?? ''));
$settings['product_updates_feed_limit'] = (string) max(1, min(20, (int) ($input['product_updates_feed_limit'] ?? 5)));
$settings['product_updates_feed_item_path'] = self::sanitizeXmlPath((string) ($input['product_updates_feed_item_path'] ?? 'channel/item'), 'channel/item');
$settings['product_updates_feed_product_field'] = self::sanitizeXmlPath((string) ($input['product_updates_feed_product_field'] ?? 'title'), 'title');
$settings['product_updates_feed_version_field'] = self::sanitizeXmlPath((string) ($input['product_updates_feed_version_field'] ?? 'category'), 'category');
$settings['product_updates_feed_date_field'] = self::sanitizeXmlPath((string) ($input['product_updates_feed_date_field'] ?? 'pubDate'), 'pubDate');
$settings['product_updates_feed_changelog_field'] = self::sanitizeXmlPath((string) ($input['product_updates_feed_changelog_field'] ?? 'description'), 'description');
$settings['product_updates_rest_url'] = esc_url_raw((string) ($input['product_updates_rest_url'] ?? ''));
$settings['product_updates_rest_list_path'] = self::sanitizePathList((string) ($input['product_updates_rest_list_path'] ?? 'content,data,items'), 'content,data,items');
$settings['product_updates_rest_product_field'] = self::sanitizePathList((string) ($input['product_updates_rest_product_field'] ?? 'product.name,productName,name'), 'product.name,productName,name');
$settings['product_updates_rest_version_field'] = self::sanitizePathList((string) ($input['product_updates_rest_version_field'] ?? 'version,versionName,name'), 'version,versionName,name');
$settings['product_updates_rest_date_field'] = self::sanitizePathList((string) ($input['product_updates_rest_date_field'] ?? 'releaseDate,date,updatedAt,createdAt'), 'releaseDate,date,updatedAt,createdAt');
$settings['product_updates_rest_changelog_field'] = self::sanitizePathList((string) ($input['product_updates_rest_changelog_field'] ?? 'changelog,changeLog,description,changes'), 'changelog,changeLog,description,changes');
$settings['product_updates_source'] = 'olm_changelog';
$settings['product_updates_olm_months'] = (string) max(1, min(24, (int) ($input['product_updates_olm_months'] ?? 4)));
$settings['product_updates_olm_ignore_numbers'] = self::sanitizeOlmNumberList((string) ($input['product_updates_olm_ignore_numbers'] ?? 'olm-10109,olm-10110'));
$settings['olm_base_url'] = esc_url_raw(ChangelogSync::normalizeBaseUrl((string) ($input['olm_base_url'] ?? '')));
$settings['olm_username'] = sanitize_text_field((string) ($input['olm_username'] ?? ''));
$settings['olm_password'] = trim((string) ($input['olm_password'] ?? '')) ?: (string) $old['olm_password'];
Plugin::syncCronSchedule($settings);
if (($old['docs_base_slug'] ?? 'docs') !== $settings['docs_base_slug']) {
@@ -155,93 +148,33 @@ final class SettingsPage
</td>
</tr>
<tr>
<th scope="row"><label for="product_updates_source"><?php esc_html_e('Update-Quelle', 'kb-markdown-importer'); ?></label></th>
<th scope="row"><label for="product_updates_olm_months"><?php esc_html_e('OLM Zeitraum', 'kb-markdown-importer'); ?></label></th>
<td>
<select id="product_updates_source" name="kb_markdown_importer_settings[product_updates_source]">
<option value="rss" <?php selected($settings['product_updates_source'], 'rss'); ?>><?php esc_html_e('RSS/XML', 'kb-markdown-importer'); ?></option>
<option value="rest" <?php selected($settings['product_updates_source'], 'rest'); ?>><?php esc_html_e('REST/JSON', 'kb-markdown-importer'); ?></option>
</select>
<input id="product_updates_olm_months" name="kb_markdown_importer_settings[product_updates_olm_months]" type="number" min="1" max="24" value="<?php echo esc_attr($settings['product_updates_olm_months']); ?>"> <?php esc_html_e('Monate zurück ab Monatsanfang', 'kb-markdown-importer'); ?>
<p class="description"><?php esc_html_e('Entspricht dem Python-Script: aktueller Monat plus die angegebene Anzahl vorheriger Monate.', 'kb-markdown-importer'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="product_updates_feed_url"><?php esc_html_e('RSS/XML-Feed URL', 'kb-markdown-importer'); ?></label></th>
<th scope="row"><label for="product_updates_olm_ignore_numbers"><?php esc_html_e('OLM Nummern ignorieren', 'kb-markdown-importer'); ?></label></th>
<td>
<input class="regular-text" id="product_updates_feed_url" name="kb_markdown_importer_settings[product_updates_feed_url]" type="url" value="<?php echo esc_attr($settings['product_updates_feed_url']); ?>" placeholder="https://example.com/updates.xml">
<p class="description"><?php esc_html_e('RSS- oder XML-Feed mit den neuesten Produktupdates. Wird nur genutzt, wenn RSS/XML als Quelle ausgewählt ist.', 'kb-markdown-importer'); ?></p>
<input class="regular-text" id="product_updates_olm_ignore_numbers" name="kb_markdown_importer_settings[product_updates_olm_ignore_numbers]" type="text" value="<?php echo esc_attr($settings['product_updates_olm_ignore_numbers']); ?>" placeholder="olm-10109,olm-10110">
<p class="description"><?php esc_html_e('Kommagetrennte productNo-Liste, die nicht im Changelog erscheinen soll.', 'kb-markdown-importer'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="product_updates_rest_url"><?php esc_html_e('REST/JSON URL', 'kb-markdown-importer'); ?></label></th>
<th scope="row"><label for="olm_base_url"><?php esc_html_e('OLM Basis-URL', 'kb-markdown-importer'); ?></label></th>
<td>
<input class="regular-text" id="product_updates_rest_url" name="kb_markdown_importer_settings[product_updates_rest_url]" type="url" value="<?php echo esc_attr($settings['product_updates_rest_url']); ?>" placeholder="https://example.com/api/product-versions">
<p class="description"><?php esc_html_e('REST-Endpunkt mit JSON-Antwort. Wird nur genutzt, wenn REST/JSON als Quelle ausgewählt ist.', 'kb-markdown-importer'); ?></p>
<input class="regular-text" id="olm_base_url" name="kb_markdown_importer_settings[olm_base_url]" type="url" value="<?php echo esc_attr($settings['olm_base_url']); ?>" placeholder="https://olm.o-byte.com">
<p class="description"><?php esc_html_e('Wird für den OLM-Changelog-Sync nach dem Python-Script verwendet.', 'kb-markdown-importer'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="product_updates_feed_limit"><?php esc_html_e('Anzahl Updates', 'kb-markdown-importer'); ?></label></th>
<td><input id="product_updates_feed_limit" name="kb_markdown_importer_settings[product_updates_feed_limit]" type="number" min="1" max="20" value="<?php echo esc_attr($settings['product_updates_feed_limit']); ?>"></td>
<th scope="row"><label for="olm_username"><?php esc_html_e('OLM Benutzername', 'kb-markdown-importer'); ?></label></th>
<td><input class="regular-text" id="olm_username" name="kb_markdown_importer_settings[olm_username]" type="text" value="<?php echo esc_attr($settings['olm_username']); ?>"></td>
</tr>
<tr>
<th scope="row"><label for="product_updates_feed_item_path"><?php esc_html_e('Eintrag-Pfad', 'kb-markdown-importer'); ?></label></th>
<td>
<input class="regular-text" id="product_updates_feed_item_path" name="kb_markdown_importer_settings[product_updates_feed_item_path]" type="text" value="<?php echo esc_attr($settings['product_updates_feed_item_path']); ?>" placeholder="channel/item">
<p class="description"><?php esc_html_e('Pfad zum wiederholten Feed-Eintrag, zum Beispiel channel/item.', 'kb-markdown-importer'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="product_updates_rest_list_path"><?php esc_html_e('REST Listenpfad', 'kb-markdown-importer'); ?></label></th>
<td>
<input class="regular-text" id="product_updates_rest_list_path" name="kb_markdown_importer_settings[product_updates_rest_list_path]" type="text" value="<?php echo esc_attr($settings['product_updates_rest_list_path']); ?>" placeholder="content,data,items">
<p class="description"><?php esc_html_e('Pfad zur Liste in der JSON-Antwort. Mehrere Alternativen mit Komma trennen. Leer lassen, wenn die Antwort direkt ein Array ist.', 'kb-markdown-importer'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e('XML-Felder', 'kb-markdown-importer'); ?></th>
<td>
<fieldset class="kb-feed-fields">
<p>
<label for="product_updates_feed_product_field"><?php esc_html_e('Produktname', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_feed_product_field" name="kb_markdown_importer_settings[product_updates_feed_product_field]" type="text" value="<?php echo esc_attr($settings['product_updates_feed_product_field']); ?>" placeholder="title">
</p>
<p>
<label for="product_updates_feed_version_field"><?php esc_html_e('Version', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_feed_version_field" name="kb_markdown_importer_settings[product_updates_feed_version_field]" type="text" value="<?php echo esc_attr($settings['product_updates_feed_version_field']); ?>" placeholder="category">
</p>
<p>
<label for="product_updates_feed_date_field"><?php esc_html_e('Datum', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_feed_date_field" name="kb_markdown_importer_settings[product_updates_feed_date_field]" type="text" value="<?php echo esc_attr($settings['product_updates_feed_date_field']); ?>" placeholder="pubDate">
</p>
<p>
<label for="product_updates_feed_changelog_field"><?php esc_html_e('Changelog', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_feed_changelog_field" name="kb_markdown_importer_settings[product_updates_feed_changelog_field]" type="text" value="<?php echo esc_attr($settings['product_updates_feed_changelog_field']); ?>" placeholder="description">
</p>
</fieldset>
<p class="description"><?php esc_html_e('Feldpfade relativ zum Eintrag, zum Beispiel product/name, version oder changelog. Namespaces wie dc:date werden unterstützt.', 'kb-markdown-importer'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e('REST-Felder', 'kb-markdown-importer'); ?></th>
<td>
<fieldset class="kb-rest-fields">
<p>
<label for="product_updates_rest_product_field"><?php esc_html_e('Produktname', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_rest_product_field" name="kb_markdown_importer_settings[product_updates_rest_product_field]" type="text" value="<?php echo esc_attr($settings['product_updates_rest_product_field']); ?>" placeholder="product.name,productName,name">
</p>
<p>
<label for="product_updates_rest_version_field"><?php esc_html_e('Version', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_rest_version_field" name="kb_markdown_importer_settings[product_updates_rest_version_field]" type="text" value="<?php echo esc_attr($settings['product_updates_rest_version_field']); ?>" placeholder="version,versionName,name">
</p>
<p>
<label for="product_updates_rest_date_field"><?php esc_html_e('Datum', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_rest_date_field" name="kb_markdown_importer_settings[product_updates_rest_date_field]" type="text" value="<?php echo esc_attr($settings['product_updates_rest_date_field']); ?>" placeholder="releaseDate,date,updatedAt,createdAt">
</p>
<p>
<label for="product_updates_rest_changelog_field"><?php esc_html_e('Changelog', 'kb-markdown-importer'); ?></label><br>
<input class="regular-text" id="product_updates_rest_changelog_field" name="kb_markdown_importer_settings[product_updates_rest_changelog_field]" type="text" value="<?php echo esc_attr($settings['product_updates_rest_changelog_field']); ?>" placeholder="changelog,changeLog,description,changes">
</p>
</fieldset>
<p class="description"><?php esc_html_e('JSON-Feldpfade relativ zu einem Eintrag. Verschachtelte Felder mit Punkt oder Slash angeben, Alternativen mit Komma trennen.', 'kb-markdown-importer'); ?></p>
</td>
<th scope="row"><label for="olm_password"><?php esc_html_e('OLM Passwort', 'kb-markdown-importer'); ?></label></th>
<td><input class="regular-text" id="olm_password" name="kb_markdown_importer_settings[olm_password]" type="password" value="" placeholder="<?php echo $settings['olm_password'] ? esc_attr__('Passwort ist gespeichert; leer lassen zum Beibehalten', 'kb-markdown-importer') : ''; ?>"></td>
</tr>
</table>
<h2><?php esc_html_e('Frontend Design', 'kb-markdown-importer'); ?></h2>
@@ -287,8 +220,8 @@ final class SettingsPage
</form>
<form method="post">
<?php wp_nonce_field('kb_markdown_test_product_updates'); ?>
<?php submit_button(__('Produktupdate-Quelle testen', 'kb-markdown-importer'), 'secondary', 'kb_markdown_test_product_updates'); ?>
<p class="description"><?php esc_html_e('Der Test nutzt die gespeicherten Einstellungen der ausgewählten Update-Quelle. Bitte Änderungen vorher speichern.', 'kb-markdown-importer'); ?></p>
<?php submit_button(__('OLM Changelog synchronisieren', 'kb-markdown-importer'), 'secondary', 'kb_markdown_test_product_updates'); ?>
<p class="description"><?php esc_html_e('Nutzt die gespeicherten OLM-Einstellungen. Bitte Änderungen vorher speichern.', 'kb-markdown-importer'); ?></p>
</form>
<?php if (is_array($updatesTest)) : ?>
<div class="notice notice-<?php echo $updatesTest['ok'] ? 'success' : 'error'; ?>">
@@ -296,7 +229,7 @@ final class SettingsPage
<p><?php echo esc_html($updatesTest['message']); ?></p>
</div>
<?php if ('' !== $updatesTest['body']) : ?>
<h2><?php esc_html_e('Antwort der Produktupdate-Quelle', 'kb-markdown-importer'); ?></h2>
<h2><?php esc_html_e('Gespeicherte Changelog-Vorschau', 'kb-markdown-importer'); ?></h2>
<textarea class="large-text code" rows="16" readonly><?php echo esc_textarea($updatesTest['body']); ?></textarea>
<?php endif; ?>
<?php endif; ?>
@@ -324,73 +257,23 @@ final class SettingsPage
private static function handleProductUpdatesTest(): array
{
$settings = Plugin::settings();
$source = (string) ($settings['product_updates_source'] ?? 'rss');
$url = esc_url_raw((string) ('rest' === $source ? ($settings['product_updates_rest_url'] ?? '') : ($settings['product_updates_feed_url'] ?? '')));
if ('' === $url) {
return [
'ok' => false,
'title' => __('Keine Produktupdate-Quelle konfiguriert.', 'kb-markdown-importer'),
'message' => __('Bitte zuerst eine RSS/XML- oder REST/JSON-URL speichern.', 'kb-markdown-importer'),
'body' => '',
];
}
$response = wp_remote_get($url, [
'timeout' => 12,
'redirection' => 3,
'user-agent' => 'KB Markdown Importer/' . KB_MARKDOWN_IMPORTER_VERSION,
]);
if (is_wp_error($response)) {
return [
'ok' => false,
'title' => __('Produktupdate-Quelle nicht erreichbar.', 'kb-markdown-importer'),
'message' => $response->get_error_message(),
'body' => '',
];
}
$status = (int) wp_remote_retrieve_response_code($response);
$contentType = (string) wp_remote_retrieve_header($response, 'content-type');
$body = (string) wp_remote_retrieve_body($response);
$excerpt = substr($body, 0, 12000);
$validPayload = true;
$payloadNote = '';
if ('rest' === $source) {
json_decode($body, true);
$validPayload = JSON_ERROR_NONE === json_last_error();
if (! $validPayload) {
$payloadNote = ' ' . sprintf(
/* translators: %s: JSON parser error message. */
__('Die Antwort ist kein gültiges JSON: %s', 'kb-markdown-importer'),
json_last_error_msg()
);
}
}
$message = sprintf(
/* translators: 1: source type, 2: HTTP status code, 3: content type. */
__('Quelle: %1$s | HTTP-Status: %2$d | Content-Type: %3$s', 'kb-markdown-importer'),
'rest' === $source ? 'REST/JSON' : 'RSS/XML',
$status,
$contentType ?: '-'
);
$message .= $payloadNote;
if (strlen($body) > strlen($excerpt)) {
$message .= ' ' . __('Die Antwort wurde auf 12000 Zeichen gekürzt.', 'kb-markdown-importer');
}
$ok = $status >= 200 && $status < 300 && $validPayload;
$response = (new ChangelogSync())->sync();
$data = (array) $response->get_data();
$ok = true === ($data['success'] ?? false);
$items = ChangelogSync::items();
$preview = wp_json_encode(array_slice($items, 0, 5), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return [
'ok' => $ok,
'title' => $ok ? __('Produktupdate-Quelle erreichbar.', 'kb-markdown-importer') : __('Produktupdate-Quelle nicht nutzbar.', 'kb-markdown-importer'),
'message' => $message,
'body' => $excerpt,
'title' => $ok ? __('OLM Changelog synchronisiert.', 'kb-markdown-importer') : __('OLM Changelog konnte nicht synchronisiert werden.', 'kb-markdown-importer'),
'message' => $ok
? sprintf(
/* translators: %d: number of parsed changelog items. */
__('Gefundene Changelog-Einträge im Zeitraum: %d', 'kb-markdown-importer'),
count($items)
)
: (string) ($data['message'] ?? __('Unbekannter Fehler.', 'kb-markdown-importer')),
'body' => $preview ?: '',
];
}
@@ -425,19 +308,12 @@ final class SettingsPage
return preg_match('/^#[0-9a-fA-F]{6}$/', $value) ? strtoupper($value) : $fallback;
}
private static function sanitizeXmlPath(string $value, string $fallback): string
private static function sanitizeOlmNumberList(string $value): string
{
$value = trim($value);
$value = preg_replace('/[^A-Za-z0-9_:@.\/-]/', '', $value) ?: '';
$items = array_filter(array_map(static function (string $item): string {
return strtolower(preg_replace('/[^a-zA-Z0-9_-]/', '', trim($item)) ?: '');
}, explode(',', $value)), static fn (string $item): bool => '' !== $item);
return '' !== $value ? $value : $fallback;
}
private static function sanitizePathList(string $value, string $fallback): string
{
$value = trim($value);
$value = preg_replace('/[^A-Za-z0-9_:@.\/,-]/', '', $value) ?: '';
return '' !== $value ? $value : $fallback;
return implode(',', array_values(array_unique($items)));
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace KbMarkdownImporter\Admin;
use KbMarkdownImporter\Import\ImportLogger;
use KbMarkdownImporter\Olm\ChangelogSync;
use KbMarkdownImporter\Plugin;
final class StatusPage
@@ -26,6 +27,8 @@ final class StatusPage
<div class="kb-admin-card"><strong>Versions</strong><span><?php echo esc_html((string) $counts['versions']); ?></span></div>
<div class="kb-admin-card"><strong>Pages</strong><span><?php echo esc_html((string) $counts['pages']); ?></span></div>
<div class="kb-admin-card"><strong>Last sync</strong><span><?php echo esc_html((string) get_option('kb_markdown_importer_last_sync', __('Never', 'kb-markdown-importer'))); ?></span></div>
<div class="kb-admin-card"><strong>OLM updates</strong><span><?php echo esc_html((string) count(ChangelogSync::items())); ?></span></div>
<div class="kb-admin-card"><strong>Last OLM sync</strong><span><?php echo esc_html(ChangelogSync::lastSync() ?: __('Never', 'kb-markdown-importer')); ?></span></div>
<div class="kb-admin-card"><strong>Format</strong><span>Markdown</span></div>
</div>
<h2><?php esc_html_e('Recent Import Logs', 'kb-markdown-importer'); ?></h2>
@@ -40,6 +43,7 @@ final class StatusPage
'settings_complete' => (bool) (Plugin::settings()['gitlab_base_url'] && Plugin::settings()['gitlab_token']),
'counts' => self::counts(),
'last_sync' => get_option('kb_markdown_importer_last_sync', ''),
'last_changelog_sync' => ChangelogSync::lastSync(),
'last_error' => get_option('kb_markdown_importer_last_error', ''),
]);
}

View File

@@ -6,6 +6,7 @@ namespace KbMarkdownImporter\Admin;
use KbMarkdownImporter\GitLab\GitLabClient;
use KbMarkdownImporter\Import\ImportLogger;
use KbMarkdownImporter\Import\ImportManager;
use KbMarkdownImporter\Olm\ChangelogSync;
use KbMarkdownImporter\Plugin;
final class SyncPage
@@ -24,6 +25,7 @@ final class SyncPage
<form method="post" class="kb-sync-actions">
<?php wp_nonce_field('kb_markdown_sync'); ?>
<?php submit_button(__('Sync All', 'kb-markdown-importer'), 'primary', 'kb_markdown_sync_all', false); ?>
<?php submit_button(__('Sync OLM Changelog', 'kb-markdown-importer'), 'secondary', 'kb_markdown_sync_changelog', false); ?>
<?php submit_button(__('Dry Run', 'kb-markdown-importer'), 'secondary', 'kb_markdown_dry_run', false); ?>
</form>
@@ -63,9 +65,20 @@ final class SyncPage
{
if (isset($_POST['kb_markdown_sync_all']) && check_admin_referer('kb_markdown_sync')) {
(new ImportManager())->syncAll(false);
(new ChangelogSync())->sync();
echo '<div class="notice notice-success"><p>' . esc_html__('Synchronization finished.', 'kb-markdown-importer') . '</p></div>';
}
if (isset($_POST['kb_markdown_sync_changelog']) && check_admin_referer('kb_markdown_sync')) {
$response = (new ChangelogSync())->sync();
$data = (array) $response->get_data();
$success = true === ($data['success'] ?? false);
$message = $success
? __('OLM changelog synchronization finished.', 'kb-markdown-importer')
: (string) ($data['message'] ?? __('OLM changelog synchronization failed.', 'kb-markdown-importer'));
echo '<div class="notice notice-' . ($success ? 'success' : 'error') . '"><p>' . esc_html($message) . '</p></div>';
}
if (isset($_POST['kb_markdown_dry_run']) && check_admin_referer('kb_markdown_sync')) {
(new ImportManager())->syncAll(true);
echo '<div class="notice notice-info"><p>' . esc_html__('Dry run finished.', 'kb-markdown-importer') . '</p></div>';