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

@@ -0,0 +1,397 @@
<?php
declare(strict_types=1);
namespace KbMarkdownImporter\Olm;
use KbMarkdownImporter\Import\ImportLogger;
use KbMarkdownImporter\Plugin;
final class ChangelogSync
{
private const OPTION_ITEMS = 'kb_markdown_importer_product_updates';
private const OPTION_LAST_SYNC = 'kb_markdown_importer_changelog_last_sync';
private array $settings;
private string $baseUrl;
private array $headers = [];
public function __construct(?array $settings = null)
{
$this->settings = $settings ?: Plugin::settings();
$this->baseUrl = self::normalizeBaseUrl((string) ($this->settings['olm_base_url'] ?? ''));
}
public static function items(): array
{
$items = get_option(self::OPTION_ITEMS, []);
return is_array($items) ? $items : [];
}
public static function lastSync(): string
{
return (string) get_option(self::OPTION_LAST_SYNC, '');
}
public static function normalizeBaseUrl(string $url): string
{
$url = trim($url);
if ('' === $url) {
return '';
}
if (! preg_match('#^https?://#i', $url)) {
$url = 'https://' . $url;
}
return rtrim($url, '/');
}
public function sync(): \WP_REST_Response
{
ImportLogger::info('OLM changelog synchronization started.');
$token = $this->login();
if (is_wp_error($token)) {
ImportLogger::error('OLM changelog login failed: ' . $token->get_error_message());
return new \WP_REST_Response(['success' => false, 'message' => $token->get_error_message()], 500);
}
$this->headers = [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token,
];
$downloadIds = $this->productDownloadIds();
if (is_wp_error($downloadIds)) {
ImportLogger::error('OLM product lookup failed: ' . $downloadIds->get_error_message());
return new \WP_REST_Response(['success' => false, 'message' => $downloadIds->get_error_message()], 500);
}
$items = [];
foreach ($downloadIds as $downloadId) {
$versions = $this->downloadFieldVersions($downloadId);
if (is_wp_error($versions)) {
ImportLogger::warning('OLM download field lookup failed for ' . $downloadId . ': ' . $versions->get_error_message());
continue;
}
foreach ($versions as $version) {
$item = $this->normalizeVersion($version);
if (null !== $item) {
$items[] = $item;
}
}
}
usort($items, static fn (array $a, array $b): int => ($b['_timestamp'] ?? 0) <=> ($a['_timestamp'] ?? 0));
$items = array_filter($items, fn (array $item): bool => $this->isInDateWindow($item));
$items = array_map(static function (array $item): array {
unset($item['_timestamp']);
return $item;
}, array_values($items));
update_option(self::OPTION_ITEMS, $items, false);
update_option(self::OPTION_LAST_SYNC, current_time('mysql'), false);
ImportLogger::info('OLM changelog synchronization completed. Entries: ' . count($items));
return new \WP_REST_Response([
'success' => true,
'stats' => [
'downloads' => count($downloadIds),
'updates' => count($items),
],
]);
}
private function login(): string|\WP_Error
{
if ('' === $this->baseUrl) {
return new \WP_Error('kb_olm_missing_base_url', __('OLM base URL missing.', 'kb-markdown-importer'));
}
$username = trim((string) ($this->settings['olm_username'] ?? ''));
$password = (string) ($this->settings['olm_password'] ?? '');
if ('' === $username || '' === $password) {
return new \WP_Error('kb_olm_missing_credentials', __('OLM credentials missing.', 'kb-markdown-importer'));
}
$response = wp_remote_post($this->baseUrl . '/login', [
'timeout' => 12,
'redirection' => 3,
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'body' => wp_json_encode([
'username' => $username,
'password' => $password,
]),
]);
if (is_wp_error($response)) {
return $response;
}
$status = (int) wp_remote_retrieve_response_code($response);
$body = (string) wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if ($status < 200 || $status >= 300 || ! is_array($data) || empty($data['bearerToken'])) {
return new \WP_Error(
'kb_olm_login_failed',
__('OLM login failed.', 'kb-markdown-importer'),
[
'status' => $status,
'response_excerpt' => substr(wp_strip_all_tags($body), 0, 1000),
]
);
}
return (string) $data['bearerToken'];
}
private function productDownloadIds(): array|\WP_Error
{
$ids = [];
$page = 1;
while (true) {
$data = $this->getJson($this->baseUrl . '/api/rest/v1/product?page=' . $page . '&size=1');
if (is_wp_error($data)) {
return $data;
}
$products = is_array($data['content'] ?? null) ? $data['content'] : [];
if (! $products) {
break;
}
foreach ($products as $product) {
if (! is_array($product)) {
continue;
}
foreach ((array) ($product['downloads'] ?? []) as $download) {
$id = is_array($download) ? (string) ($download['id'] ?? '') : '';
if ('' !== $id) {
$ids[$id] = $id;
}
}
}
$page++;
}
return array_values($ids);
}
private function downloadFieldVersions(string $downloadId): array|\WP_Error
{
$versions = [];
$page = 1;
while (true) {
$data = $this->getJson($this->baseUrl . '/api/rest/v1/download/field/' . rawurlencode($downloadId) . '?page=' . $page . '&size=1');
if (is_wp_error($data)) {
return $data;
}
$content = is_array($data['content'] ?? null) ? $data['content'] : [];
if (! $content) {
break;
}
foreach ($content as $version) {
if (is_array($version)) {
$versions[] = $version;
}
}
$page++;
}
return $versions;
}
private function getJson(string $url): array|\WP_Error
{
$response = wp_remote_get($url, [
'timeout' => 12,
'redirection' => 3,
'headers' => $this->headers,
'user-agent' => 'KB Markdown Importer/' . KB_MARKDOWN_IMPORTER_VERSION,
]);
if (is_wp_error($response)) {
return $response;
}
$status = (int) wp_remote_retrieve_response_code($response);
$body = (string) wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if ($status < 200 || $status >= 300 || ! is_array($data)) {
return new \WP_Error(
'kb_olm_request_failed',
__('OLM request failed.', 'kb-markdown-importer'),
[
'status' => $status,
'url' => $url,
'response_excerpt' => substr(wp_strip_all_tags($body), 0, 1000),
]
);
}
return $data;
}
private function normalizeVersion(array $version): ?array
{
$productVersion = is_array($version['productVersion'] ?? null) ? $version['productVersion'] : [];
$product = is_array($productVersion['product'] ?? null) ? $productVersion['product'] : [];
$downloadField = is_array($version['downloadField'] ?? null) ? $version['downloadField'] : [];
$productNo = strtolower((string) ($product['productNo'] ?? ''));
$publishedAt = (string) ($version['publishedAt'] ?? '');
if (true !== ($version['qa'] ?? false) || true !== ($product['published'] ?? false) || '' === $publishedAt) {
return null;
}
if (in_array($productNo, $this->ignoredOlmNumbers(), true)) {
return null;
}
$timestamp = $this->dateTimestamp($publishedAt);
$productName = $this->cleanText((string) ($product['name'] ?? ''));
$downloadName = $this->cleanText((string) ($downloadField['name'] ?? ''));
if (true !== ($downloadField['starfaceModule'] ?? false) && '' !== $downloadName) {
$productName = trim($productName . ' - ' . $downloadName);
}
$changelogLines = $this->changelogLines((string) ($version['changelog'] ?? ''));
return [
'product' => $productName,
'version' => $this->moduleVersion($productVersion, $version),
'date' => $this->formatDate($publishedAt),
'month_label' => $timestamp > 0 ? wp_date('F Y', $timestamp) : '',
'changelog' => implode(' ', $changelogLines),
'changelog_lines' => $changelogLines,
'starface_min' => $this->starfaceVersion(is_array($productVersion['minStarfaceVersion'] ?? null) ? $productVersion['minStarfaceVersion'] : []),
'starface_max' => $this->starfaceVersion(is_array($productVersion['maxStarfaceVersion'] ?? null) ? $productVersion['maxStarfaceVersion'] : []),
'link' => $this->validUrl((string) ($product['productPageURI'] ?? '')),
'download_link' => '' !== $productNo ? 'https://get.o-byte.com?olm=' . rawurlencode($productNo) : '',
'product_no' => $productNo,
'published_at' => $timestamp > 0 ? wp_date('Y-m-d', $timestamp) : '',
'_timestamp' => $timestamp,
];
}
private function isInDateWindow(array $item): bool
{
$timestamp = (int) ($item['_timestamp'] ?? 0);
if ($timestamp <= 0) {
return false;
}
$months = max(1, min(24, (int) ($this->settings['product_updates_olm_months'] ?? 4)));
$timezone = wp_timezone();
$date = (new \DateTimeImmutable('@' . $timestamp))->setTimezone($timezone);
$now = new \DateTimeImmutable('now', $timezone);
$start = $now->modify('first day of this month')->modify('-' . $months . ' months')->setTime(0, 0, 0);
return $date >= $start && $date <= $now;
}
private function ignoredOlmNumbers(): array
{
$value = strtolower((string) ($this->settings['product_updates_olm_ignore_numbers'] ?? ''));
return array_values(array_filter(array_map('trim', explode(',', $value)), static fn (string $item): bool => '' !== $item));
}
private function moduleVersion(array $productVersion, array $downloadVersion): string
{
return implode('.', [
(string) ($productVersion['major'] ?? ''),
(string) ($productVersion['minor'] ?? ''),
(string) ($downloadVersion['bugfixVersion'] ?? ''),
]);
}
private function starfaceVersion(array $version): string
{
return implode('.', [
(string) ($version['major'] ?? ''),
(string) ($version['minor'] ?? ''),
(string) ($version['build'] ?? ''),
(string) ($version['revision'] ?? ''),
]);
}
private function changelogLines(string $value): array
{
$value = str_replace(["\r\n", "\r"], "\n", $value);
$lines = [];
foreach (explode("\n", $value) as $line) {
$line = $this->cleanText($line);
if ('' !== $line) {
$lines[] = $line;
}
}
return $lines;
}
private function cleanText(string $value): string
{
$value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, get_bloginfo('charset'));
$value = preg_replace('/<br\s*\/?>/i', ' ', $value) ?? $value;
$value = wp_strip_all_tags($value);
$value = preg_replace('/\s+/', ' ', $value) ?? $value;
return trim($value);
}
private function validUrl(string $value): string
{
$value = trim($value);
if ('' === $value || in_array($value, ['.', '-'], true)) {
return '';
}
return filter_var($value, FILTER_VALIDATE_URL) ? $value : '';
}
private function dateTimestamp(string $date): int
{
if ('' === $date) {
return 0;
}
$timestamp = strtotime($date);
return $timestamp ?: 0;
}
private function formatDate(string $date): string
{
$timestamp = $this->dateTimestamp($date);
return $timestamp > 0 ? wp_date((string) get_option('date_format'), $timestamp) : $date;
}
}