Files
adocWP/kb-markdown-importer/includes/Frontend/UrlBuilder.php
2026-05-27 14:17:22 +02:00

196 lines
5.8 KiB
PHP

<?php
declare(strict_types=1);
namespace KbMarkdownImporter\Frontend;
use KbMarkdownImporter\Plugin;
use KbMarkdownImporter\Repository\ProductRepository;
final class UrlBuilder
{
private static string $embedBaseUrl = '';
public static function beginEmbed(string $baseUrl): void
{
self::$embedBaseUrl = remove_query_arg(['kb_docs_route', 'kb_docs_product', 'kb_docs_version', 'kb_docs_page'], $baseUrl);
}
public static function endEmbed(): void
{
self::$embedBaseUrl = '';
}
public static function isEmbed(): bool
{
return '' !== self::$embedBaseUrl;
}
public static function docsIndex(): string
{
return self::route('index');
}
public static function product(string $productSlug): string
{
return self::route('product', $productSlug);
}
public static function version(string $productSlug, string $versionSlug): string
{
return self::route('version', $productSlug, $versionSlug);
}
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()) {
$args = ['kb_docs_route' => $route];
if ($productSlug) {
$args['kb_docs_product'] = $productSlug;
}
if ($versionSlug) {
$args['kb_docs_version'] = $versionSlug;
}
if ($pageSlug) {
$args['kb_docs_page'] = $pageSlug;
}
return add_query_arg($args, self::$embedBaseUrl);
}
$base = trim((string) Plugin::settings()['docs_base_slug'], '/') ?: 'docs';
if (self::supportsPrettyPermalinks()) {
$parts = array_filter([$base, $productSlug, $versionSlug, $pageSlug], static fn (string $part): bool => '' !== $part);
return home_url('/' . implode('/', array_map('rawurlencode', $parts)) . '/');
}
$args = ['kb_markdown_route' => $route];
if ($productSlug) {
$args['kb_product_slug'] = $productSlug;
}
if ($versionSlug) {
$args['kb_version_slug'] = $versionSlug;
}
if ($pageSlug) {
$args['kb_page_slug'] = $pageSlug;
}
return add_query_arg($args, home_url('/'));
}
private static function supportsPrettyPermalinks(): bool
{
return '' !== (string) get_option('permalink_structure', '');
}
public static function rewriteHtml(string $html): string
{
if (! self::isEmbed()) {
return $html;
}
return preg_replace_callback('/href=(["\'])([^"\']+)\1/i', static function (array $matches): string {
$url = html_entity_decode((string) $matches[2], ENT_QUOTES);
$replacement = self::rewriteUrl($url);
if (! $replacement) {
return $matches[0];
}
return 'href=' . $matches[1] . esc_url($replacement) . $matches[1];
}, $html) ?? $html;
}
private static function rewriteUrl(string $url): string
{
$parts = wp_parse_url($url);
if (! is_array($parts)) {
return '';
}
$query = [];
if (! empty($parts['query'])) {
wp_parse_str((string) $parts['query'], $query);
}
if (! empty($query['kb_markdown_route'])) {
return self::route(
sanitize_key((string) $query['kb_markdown_route']),
sanitize_title((string) ($query['kb_product_slug'] ?? '')),
sanitize_title((string) ($query['kb_version_slug'] ?? '')),
sanitize_title((string) ($query['kb_page_slug'] ?? ''))
);
}
$base = trim((string) Plugin::settings()['docs_base_slug'], '/') ?: 'docs';
$path = trim((string) ($parts['path'] ?? ''), '/');
if ($path === $base) {
return self::docsIndex();
}
if (! str_starts_with($path . '/', $base . '/')) {
return '';
}
$routeParts = array_values(array_filter(explode('/', substr($path, strlen($base))), static fn (string $part): bool => '' !== $part));
if (1 === count($routeParts)) {
return self::product(sanitize_title($routeParts[0]));
}
if (2 === count($routeParts)) {
return self::version(sanitize_title($routeParts[0]), sanitize_title($routeParts[1]));
}
return self::page(
sanitize_title($routeParts[0] ?? ''),
sanitize_title($routeParts[1] ?? ''),
sanitize_title(implode('/', array_slice($routeParts, 2)))
);
}
}