new file: olm-login.php
This commit is contained in:
@@ -3,264 +3,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace KbMarkdownImporter\Frontend;
|
||||
|
||||
use KbMarkdownImporter\Plugin;
|
||||
use KbMarkdownImporter\Olm\ChangelogSync;
|
||||
|
||||
final class ProductUpdatesFeed
|
||||
{
|
||||
public static function items(array $settings = []): array
|
||||
{
|
||||
$settings = $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 [];
|
||||
}
|
||||
|
||||
$cacheKey = 'kb_product_updates_' . md5($source . $url . wp_json_encode([
|
||||
$settings['product_updates_feed_item_path'] ?? '',
|
||||
$settings['product_updates_feed_product_field'] ?? '',
|
||||
$settings['product_updates_feed_version_field'] ?? '',
|
||||
$settings['product_updates_feed_date_field'] ?? '',
|
||||
$settings['product_updates_feed_changelog_field'] ?? '',
|
||||
$settings['product_updates_rest_list_path'] ?? '',
|
||||
$settings['product_updates_rest_product_field'] ?? '',
|
||||
$settings['product_updates_rest_version_field'] ?? '',
|
||||
$settings['product_updates_rest_date_field'] ?? '',
|
||||
$settings['product_updates_rest_changelog_field'] ?? '',
|
||||
]));
|
||||
$cached = get_transient($cacheKey);
|
||||
|
||||
if (is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 8,
|
||||
'redirection' => 3,
|
||||
'user-agent' => 'KB Markdown Importer/' . KB_MARKDOWN_IMPORTER_VERSION,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response) || 200 !== (int) wp_remote_retrieve_response_code($response)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = (string) wp_remote_retrieve_body($response);
|
||||
$items = 'rest' === $source ? self::parseJson($body, $settings) : self::parseXml($body, $settings);
|
||||
set_transient($cacheKey, $items, 15 * MINUTE_IN_SECONDS);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private static function parseXml(string $xml, array $settings): array
|
||||
{
|
||||
if ('' === trim($xml) || ! class_exists(\DOMDocument::class)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$previous = libxml_use_internal_errors(true);
|
||||
$document = new \DOMDocument();
|
||||
$loaded = $document->loadXML($xml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($previous);
|
||||
|
||||
if (! $loaded) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$xpath = new \DOMXPath($document);
|
||||
$itemNodes = $xpath->query(self::itemPath((string) ($settings['product_updates_feed_item_path'] ?? 'channel/item')));
|
||||
|
||||
if (! $itemNodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$limit = max(1, min(20, (int) ($settings['product_updates_feed_limit'] ?? 5)));
|
||||
$items = [];
|
||||
|
||||
foreach ($itemNodes as $itemNode) {
|
||||
if (count($items) >= $limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
$date = self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_date_field'] ?? 'pubDate'));
|
||||
$items[] = [
|
||||
'product' => self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_product_field'] ?? 'title')),
|
||||
'version' => self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_version_field'] ?? 'category')),
|
||||
'date' => self::formatDate($date),
|
||||
'changelog' => self::fieldValue($xpath, $itemNode, (string) ($settings['product_updates_feed_changelog_field'] ?? 'description')),
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private static function parseJson(string $json, array $settings): array
|
||||
{
|
||||
$data = json_decode($json, true);
|
||||
|
||||
if (! is_array($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$nodes = self::jsonList($data, (string) ($settings['product_updates_rest_list_path'] ?? 'content,data,items'));
|
||||
|
||||
if (! $nodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$limit = max(1, min(20, (int) ($settings['product_updates_feed_limit'] ?? 5)));
|
||||
$items = [];
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if (count($items) >= $limit || ! is_array($node)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$date = self::jsonField($node, (string) ($settings['product_updates_rest_date_field'] ?? 'releaseDate,date,updatedAt,createdAt'));
|
||||
$items[] = [
|
||||
'product' => self::jsonField($node, (string) ($settings['product_updates_rest_product_field'] ?? 'product.name,productName,name')),
|
||||
'version' => self::jsonField($node, (string) ($settings['product_updates_rest_version_field'] ?? 'version,versionName,name')),
|
||||
'date' => self::formatDate($date),
|
||||
'changelog' => self::jsonField($node, (string) ($settings['product_updates_rest_changelog_field'] ?? 'changelog,changeLog,description,changes')),
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private static function jsonList(array $data, string $paths): array
|
||||
{
|
||||
foreach (self::pathAlternatives($paths) as $path) {
|
||||
$value = '' === $path ? $data : self::jsonValue($data, $path);
|
||||
|
||||
if (is_array($value) && array_is_list($value)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return array_is_list($data) ? $data : [];
|
||||
}
|
||||
|
||||
private static function jsonField(array $data, string $paths): string
|
||||
{
|
||||
foreach (self::pathAlternatives($paths) as $path) {
|
||||
$value = self::jsonValue($data, $path);
|
||||
|
||||
if (is_scalar($value)) {
|
||||
return trim(wp_strip_all_tags((string) $value));
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$text = implode(', ', array_filter(array_map(static fn ($item): string => is_scalar($item) ? (string) $item : '', $value)));
|
||||
if ('' !== $text) {
|
||||
return trim(wp_strip_all_tags($text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function jsonValue(array $data, string $path): mixed
|
||||
{
|
||||
$value = $data;
|
||||
|
||||
foreach (self::pathSegments($path) as $segment) {
|
||||
if (! is_array($value) || ! array_key_exists($segment, $value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = $value[$segment];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private static function pathAlternatives(string $paths): array
|
||||
{
|
||||
return array_values(array_map('trim', explode(',', $paths)));
|
||||
}
|
||||
|
||||
private static function itemPath(string $path): string
|
||||
{
|
||||
$segments = self::pathSegments($path ?: 'channel/item');
|
||||
$first = (string) array_shift($segments);
|
||||
$query = '//*[local-name()="' . self::localName($first ?: 'item') . '"]';
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
$query .= '/*[local-name()="' . self::localName((string) $segment) . '"]';
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private static function fieldValue(\DOMXPath $xpath, \DOMNode $node, string $path): string
|
||||
{
|
||||
$segments = self::pathSegments($path);
|
||||
|
||||
if (! $segments) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attribute = null;
|
||||
$last = (string) end($segments);
|
||||
|
||||
if (str_starts_with($last, '@')) {
|
||||
$attribute = substr($last, 1);
|
||||
array_pop($segments);
|
||||
}
|
||||
|
||||
$query = '.';
|
||||
foreach ($segments as $segment) {
|
||||
$query .= '/*[local-name()="' . self::localName((string) $segment) . '"]';
|
||||
}
|
||||
|
||||
$result = $xpath->query($query, $node);
|
||||
$target = $result && $result->length > 0 ? $result->item(0) : null;
|
||||
|
||||
if (! $target) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($attribute && $target instanceof \DOMElement) {
|
||||
return trim(wp_strip_all_tags(html_entity_decode($target->getAttribute($attribute), ENT_QUOTES | ENT_XML1, get_bloginfo('charset'))));
|
||||
}
|
||||
|
||||
return trim(wp_strip_all_tags(html_entity_decode($target->textContent, ENT_QUOTES | ENT_XML1, get_bloginfo('charset'))));
|
||||
}
|
||||
|
||||
private static function pathSegments(string $path): array
|
||||
{
|
||||
$path = trim(str_replace('.', '/', $path), '/ ');
|
||||
|
||||
if ('' === $path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map('trim', explode('/', $path)), static fn (string $segment): bool => '' !== $segment));
|
||||
}
|
||||
|
||||
private static function localName(string $segment): string
|
||||
{
|
||||
$segment = trim($segment);
|
||||
$parts = explode(':', $segment);
|
||||
|
||||
return preg_replace('/[^A-Za-z0-9_-]/', '', (string) end($parts)) ?: '';
|
||||
}
|
||||
|
||||
private static function formatDate(string $date): string
|
||||
{
|
||||
if ('' === $date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$timestamp = strtotime($date);
|
||||
|
||||
if (! $timestamp) {
|
||||
return $date;
|
||||
}
|
||||
|
||||
return wp_date((string) get_option('date_format'), $timestamp);
|
||||
return ChangelogSync::items();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace KbMarkdownImporter\Frontend;
|
||||
use KbMarkdownImporter\Access\AccessController;
|
||||
use KbMarkdownImporter\Plugin;
|
||||
use KbMarkdownImporter\Repository\PageRepository;
|
||||
use KbMarkdownImporter\Repository\ProductRepository;
|
||||
|
||||
final class Router
|
||||
{
|
||||
@@ -212,15 +213,18 @@ final class Router
|
||||
|
||||
private function captureProduct(string $productSlug): string
|
||||
{
|
||||
$product = get_term_by('slug', $productSlug, 'kb_product');
|
||||
$productItem = self::frontendProduct($productSlug);
|
||||
$product = $productItem['term'] ?? null;
|
||||
if (! $product) {
|
||||
return $this->capture404();
|
||||
}
|
||||
$productSlug = (string) $product->slug;
|
||||
|
||||
$versions = $this->versionsForProduct($productSlug);
|
||||
|
||||
return (new TemplateLoader())->capture('product', [
|
||||
'product' => $product,
|
||||
'product_item' => $productItem,
|
||||
'versions' => $versions,
|
||||
'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
|
||||
'url_builder' => UrlBuilder::class,
|
||||
@@ -234,23 +238,30 @@ final class Router
|
||||
|
||||
private function captureVersion(string $productSlug, string $versionSlug): string
|
||||
{
|
||||
$product = get_term_by('slug', $productSlug, 'kb_product');
|
||||
$productItem = self::frontendProduct($productSlug);
|
||||
$product = $productItem['term'] ?? null;
|
||||
$version = get_term_by('slug', $versionSlug, 'kb_version');
|
||||
|
||||
if (! $product || ! $version) {
|
||||
return $this->capture404();
|
||||
}
|
||||
$productSlug = (string) $product->slug;
|
||||
|
||||
$landing = $this->landingPageForVersion($productSlug, $versionSlug);
|
||||
if ($landing) {
|
||||
return $this->captureDocPage($landing, $productSlug, $versionSlug);
|
||||
$landingSlugs = $this->pageLinkSlugs([$landing], $productSlug);
|
||||
return $this->captureDocPage($landing, $productSlug, $versionSlug, $productItem, (string) ($landingSlugs[$landing->ID] ?? ''));
|
||||
}
|
||||
|
||||
$pages = $this->pagesForVersion($productSlug, $versionSlug);
|
||||
|
||||
return (new TemplateLoader())->capture('version', [
|
||||
'product' => $product,
|
||||
'product_item' => $productItem,
|
||||
'version' => $version,
|
||||
'versions' => $this->versionsForProduct($productSlug),
|
||||
'pages' => $this->pagesForVersion($productSlug, $versionSlug),
|
||||
'pages' => $pages,
|
||||
'page_link_slugs' => $this->pageLinkSlugs($pages, $productSlug),
|
||||
'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
|
||||
'url_builder' => UrlBuilder::class,
|
||||
]);
|
||||
@@ -263,17 +274,35 @@ final class Router
|
||||
|
||||
private function capturePage(string $productSlug, string $versionSlug, string $pageSlug): string
|
||||
{
|
||||
$post = (new PageRepository())->findFrontendPage($productSlug, $versionSlug, $pageSlug);
|
||||
$productItem = self::frontendProduct($productSlug);
|
||||
if ($productItem && isset($productItem['term']->slug)) {
|
||||
$productSlug = (string) $productItem['term']->slug;
|
||||
}
|
||||
$termSlugs = $this->sourceProductSlugs($productSlug);
|
||||
$sourceSlug = '';
|
||||
$realPageSlug = $pageSlug;
|
||||
|
||||
if (! $post && '' === $pageSlug) {
|
||||
$post = (new PageRepository())->findFrontendPage($productSlug, $versionSlug, 'index');
|
||||
if (str_contains($pageSlug, '--')) {
|
||||
[$sourceSlug, $realPageSlug] = array_pad(explode('--', $pageSlug, 2), 2, '');
|
||||
if (in_array($sourceSlug, $termSlugs, true)) {
|
||||
$termSlugs = [$sourceSlug];
|
||||
}
|
||||
if ('index' === $realPageSlug) {
|
||||
$realPageSlug = '';
|
||||
}
|
||||
}
|
||||
|
||||
$post = (new PageRepository())->findFrontendPageInProducts($termSlugs, $versionSlug, $realPageSlug);
|
||||
|
||||
if (! $post && '' === $realPageSlug) {
|
||||
$post = (new PageRepository())->findFrontendPageInProducts($termSlugs, $versionSlug, 'index');
|
||||
}
|
||||
|
||||
if (! $post) {
|
||||
return $this->capture404();
|
||||
}
|
||||
|
||||
return $this->captureDocPage($post, $productSlug, $versionSlug);
|
||||
return $this->captureDocPage($post, $productSlug, $versionSlug, $productItem, $pageSlug);
|
||||
}
|
||||
|
||||
private function renderDocPage(\WP_Post $post, string $productSlug, string $versionSlug): void
|
||||
@@ -281,21 +310,26 @@ final class Router
|
||||
echo $this->captureDocPage($post, $productSlug, $versionSlug);
|
||||
}
|
||||
|
||||
private function captureDocPage(\WP_Post $post, string $productSlug, string $versionSlug): string
|
||||
private function captureDocPage(\WP_Post $post, string $productSlug, string $versionSlug, ?array $productItem = null, string $activePageSlug = ''): string
|
||||
{
|
||||
$product = get_term_by('slug', $productSlug, 'kb_product');
|
||||
$productItem = $productItem ?: self::frontendProduct($productSlug);
|
||||
$product = $productItem['term'] ?? null;
|
||||
$version = get_term_by('slug', $versionSlug, 'kb_version');
|
||||
$navTree = json_decode((string) get_post_meta($post->ID, '_kb_nav_tree', true), true);
|
||||
$pages = $this->pagesForVersion($productSlug, $versionSlug);
|
||||
|
||||
return (new TemplateLoader())->capture('page', [
|
||||
'post' => $post,
|
||||
'product' => $product,
|
||||
'product_item' => $productItem,
|
||||
'version' => $version,
|
||||
'versions' => $this->versionsForProduct($productSlug),
|
||||
'nav_tree' => is_array($navTree) ? $navTree : [],
|
||||
'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
|
||||
'product_slug' => $productSlug,
|
||||
'version_slug' => $versionSlug,
|
||||
'active_page_slug' => $activePageSlug,
|
||||
'page_link_slugs' => $this->pageLinkSlugs($pages, $productSlug),
|
||||
'url_builder' => UrlBuilder::class,
|
||||
]);
|
||||
}
|
||||
@@ -316,18 +350,33 @@ final class Router
|
||||
private function captureShell(string $content, string $productSlug = '', string $versionSlug = '', string $pageSlug = ''): string
|
||||
{
|
||||
$activePages = [];
|
||||
$activeProduct = self::frontendProduct($productSlug);
|
||||
|
||||
if ($activeProduct && isset($activeProduct['term']->slug)) {
|
||||
$productSlug = (string) $activeProduct['term']->slug;
|
||||
}
|
||||
|
||||
if ($productSlug && $versionSlug) {
|
||||
$activePages = $this->pagesForVersion($productSlug, $versionSlug);
|
||||
|
||||
if ('' === $pageSlug) {
|
||||
$landing = $this->landingPageForVersion($productSlug, $versionSlug);
|
||||
if ($landing) {
|
||||
$landingSlugs = $this->pageLinkSlugs([$landing], $productSlug);
|
||||
$pageSlug = (string) ($landingSlugs[$landing->ID] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (new TemplateLoader())->capture('docs-app', [
|
||||
'content' => $content,
|
||||
'products' => self::productsWithVersions(),
|
||||
'active_product' => $activeProduct,
|
||||
'active_product_slug' => $productSlug,
|
||||
'active_version_slug' => $versionSlug,
|
||||
'active_page_slug' => $pageSlug,
|
||||
'active_pages' => $activePages,
|
||||
'active_page_link_slugs' => $this->pageLinkSlugs($activePages, $productSlug),
|
||||
'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
|
||||
'url_builder' => UrlBuilder::class,
|
||||
]);
|
||||
@@ -356,37 +405,93 @@ final class Router
|
||||
public static function productsWithVersions(): array
|
||||
{
|
||||
$products = get_terms(['taxonomy' => 'kb_product', 'hide_empty' => false]);
|
||||
$items = [];
|
||||
$groups = [];
|
||||
$repository = new ProductRepository();
|
||||
|
||||
if (is_wp_error($products)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($products as $product) {
|
||||
$versions = (new self())->versionsForProduct($product->slug);
|
||||
$meta = $repository->frontendMeta($product);
|
||||
$groupSlug = (string) $meta['group_slug'];
|
||||
|
||||
if (! $versions) {
|
||||
continue;
|
||||
if (! isset($groups[$groupSlug])) {
|
||||
$groups[$groupSlug] = [
|
||||
'term' => (object) [
|
||||
'term_id' => 0,
|
||||
'name' => (string) $meta['group_name'],
|
||||
'slug' => $groupSlug,
|
||||
],
|
||||
'source_terms' => [],
|
||||
'parts' => [],
|
||||
'category' => (string) $meta['category'],
|
||||
'versions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
$groups[$groupSlug]['source_terms'][] = $product;
|
||||
$groups[$groupSlug]['parts'][$product->slug] = [
|
||||
'term' => $product,
|
||||
'versions' => $versions,
|
||||
'label' => (string) ($meta['part_label'] ?: $product->name),
|
||||
'category' => (string) $meta['category'],
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
$router = new self();
|
||||
foreach ($groups as $groupSlug => &$group) {
|
||||
$group['versions'] = $router->versionsForProduct((string) $groupSlug);
|
||||
}
|
||||
unset($group);
|
||||
|
||||
$groups = array_values(array_filter($groups, static fn (array $group): bool => ! empty($group['versions'])));
|
||||
usort($groups, static function (array $a, array $b): int {
|
||||
$categoryCompare = strcasecmp((string) ($a['category'] ?? ''), (string) ($b['category'] ?? ''));
|
||||
return 0 !== $categoryCompare ? $categoryCompare : strcasecmp((string) $a['term']->name, (string) $b['term']->name);
|
||||
});
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
public static function frontendProduct(string $productSlug): ?array
|
||||
{
|
||||
$items = self::productsWithVersions();
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($productSlug === (string) $item['term']->slug) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
$term = get_term_by('slug', $productSlug, 'kb_product');
|
||||
if ($term instanceof \WP_Term) {
|
||||
$meta = (new ProductRepository())->frontendMeta($term);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ((string) $meta['group_slug'] === (string) $item['term']->slug) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function versionsForProduct(string $productSlug): array
|
||||
{
|
||||
$productSlugs = $this->sourceProductSlugs($productSlug);
|
||||
|
||||
if (! $productSlugs) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'kb_doc_page',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'tax_query' => [
|
||||
['taxonomy' => 'kb_product', 'field' => 'slug', 'terms' => $productSlug],
|
||||
['taxonomy' => 'kb_product', 'field' => 'slug', 'terms' => $productSlugs],
|
||||
],
|
||||
]);
|
||||
$versions = [];
|
||||
@@ -404,6 +509,12 @@ final class Router
|
||||
|
||||
private function pagesForVersion(string $productSlug, string $versionSlug): array
|
||||
{
|
||||
$productSlugs = $this->sourceProductSlugs($productSlug);
|
||||
|
||||
if (! $productSlugs) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'kb_doc_page',
|
||||
'post_status' => 'publish',
|
||||
@@ -412,7 +523,7 @@ final class Router
|
||||
'orderby' => ['meta_value_num' => 'ASC', 'title' => 'ASC'],
|
||||
'tax_query' => [
|
||||
'relation' => 'AND',
|
||||
['taxonomy' => 'kb_product', 'field' => 'slug', 'terms' => $productSlug],
|
||||
['taxonomy' => 'kb_product', 'field' => 'slug', 'terms' => $productSlugs],
|
||||
['taxonomy' => 'kb_version', 'field' => 'slug', 'terms' => $versionSlug],
|
||||
],
|
||||
]);
|
||||
@@ -423,7 +534,7 @@ final class Router
|
||||
private function landingPageForVersion(string $productSlug, string $versionSlug): ?\WP_Post
|
||||
{
|
||||
$repository = new PageRepository();
|
||||
$landing = $repository->findFrontendPage($productSlug, $versionSlug, '');
|
||||
$landing = $repository->findFrontendPageInProducts($this->sourceProductSlugs($productSlug), $versionSlug, '');
|
||||
|
||||
if ($landing) {
|
||||
return $landing;
|
||||
@@ -433,4 +544,45 @@ final class Router
|
||||
|
||||
return $pages[0] ?? null;
|
||||
}
|
||||
|
||||
private function sourceProductSlugs(string $productSlug): array
|
||||
{
|
||||
$terms = get_terms(['taxonomy' => 'kb_product', 'hide_empty' => false]);
|
||||
$repository = new ProductRepository();
|
||||
$slugs = [];
|
||||
|
||||
if (is_wp_error($terms)) {
|
||||
return [$productSlug];
|
||||
}
|
||||
|
||||
foreach ($terms as $term) {
|
||||
$meta = $repository->frontendMeta($term);
|
||||
|
||||
if ($productSlug === (string) $meta['group_slug']) {
|
||||
$slugs[] = $term->slug;
|
||||
}
|
||||
}
|
||||
|
||||
return $slugs ?: [$productSlug];
|
||||
}
|
||||
|
||||
private function pageLinkSlugs(array $pages, string $productSlug): array
|
||||
{
|
||||
$product = self::frontendProduct($productSlug);
|
||||
$sourceTerms = (array) ($product['source_terms'] ?? []);
|
||||
$multiPart = count($sourceTerms) > 1;
|
||||
$slugs = [];
|
||||
|
||||
foreach ($pages as $page) {
|
||||
if (! $page instanceof \WP_Post) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pageSlug = (string) get_post_meta($page->ID, '_kb_page_slug', true);
|
||||
$sourceSlug = (string) get_post_meta($page->ID, '_kb_product_slug', true);
|
||||
$slugs[$page->ID] = ($multiPart && '' !== $sourceSlug) ? $sourceSlug . '--' . ($pageSlug ?: 'index') : $pageSlug;
|
||||
}
|
||||
|
||||
return $slugs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
namespace KbMarkdownImporter\Frontend;
|
||||
|
||||
use KbMarkdownImporter\Plugin;
|
||||
use KbMarkdownImporter\Repository\ProductRepository;
|
||||
|
||||
final class UrlBuilder
|
||||
{
|
||||
@@ -41,9 +42,40 @@ final class UrlBuilder
|
||||
|
||||
public static function page(string $productSlug, string $versionSlug, string $pageSlug = ''): string
|
||||
{
|
||||
[$productSlug, $pageSlug] = self::normalizeProductRoute($productSlug, $pageSlug);
|
||||
|
||||
return self::route('page', $productSlug, $versionSlug, $pageSlug);
|
||||
}
|
||||
|
||||
private static function normalizeProductRoute(string $productSlug, string $pageSlug): array
|
||||
{
|
||||
$term = get_term_by('slug', $productSlug, 'kb_product');
|
||||
|
||||
if (! $term instanceof \WP_Term) {
|
||||
return [$productSlug, $pageSlug];
|
||||
}
|
||||
|
||||
$repository = new ProductRepository();
|
||||
$meta = $repository->frontendMeta($term);
|
||||
$groupSlug = (string) $meta['group_slug'];
|
||||
$groupTermCount = 0;
|
||||
$terms = get_terms(['taxonomy' => 'kb_product', 'hide_empty' => false]);
|
||||
|
||||
if (! is_wp_error($terms)) {
|
||||
foreach ($terms as $candidate) {
|
||||
if ($groupSlug === (string) $repository->frontendMeta($candidate)['group_slug']) {
|
||||
$groupTermCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($groupTermCount > 1 && ! str_contains($pageSlug, '--')) {
|
||||
$pageSlug = $term->slug . '--' . ($pageSlug ?: 'index');
|
||||
}
|
||||
|
||||
return [$groupSlug, $pageSlug];
|
||||
}
|
||||
|
||||
private static function route(string $route, string $productSlug = '', string $versionSlug = '', string $pageSlug = ''): string
|
||||
{
|
||||
if (self::isEmbed()) {
|
||||
|
||||
Reference in New Issue
Block a user