From d014e1139c4f16fe9dec605795ab3f28433ad836 Mon Sep 17 00:00:00 2001 From: Johann Schopplich Date: Tue, 21 Nov 2023 20:58:22 +0100 Subject: [PATCH] feat!: `urlParser` for `resolvePermalinks` --- README.md | 27 ++++++++++--- composer.lock | 24 ++++++------ src/extensions/fieldMethods.php | 67 ++++++++++++++++++--------------- 3 files changed, 71 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 3a5bae7..f7d29a5 100755 --- a/README.md +++ b/README.md @@ -271,21 +271,38 @@ A middleware checks if a `Authentication` header is set, which is not the case i ### `resolvePermalinks()` -This field method resolves page and file permalinks to their respective URLs. It is primarily intended for usage with KQL queries, because the value of `writer` fields contain permalink URLs like `/@/page/nDvVIAwDBph4uOpm`. But the method works with any field that contains permalinks in `href` attributes. +This field method resolves page and file permalinks to their respective URLs. It is primarily intended for usage with KQL queries, because the value of `writer` fields contain permalink URLs like `/@/page/nDvVIAwDBph4uOpm`. But the method works with any field values that contains permalinks in `href` or `src` attributes. -In multilanguage setups, you may want to remove a language prefix like `/de` from the URL. You can do so by defining a custom path parser in your `config.php`: +For headless usage, you may want to remove the origin from the URL and just return the path. You can do so by defining a custom URL parser in your `config.php`: ```php # /site/config/config.php return [ 'permalinksResolver' => [ - // Strip the language code prefix from the URL for German - 'pathParser' => function (string $path, \Kirby\Cms\App $kirby) { + // Strip the origin from the URL + 'urlParser' => function (string $url, \Kirby\Cms\App $kirby) { + $path = parse_url($url, PHP_URL_PATH); + return $path; + } + ] +]; +``` + +Or in multilanguage setups, you may want to remove a language prefix like `/de` from the URL: + +```php +# /site/config/config.php +return [ + 'permalinksResolver' => [ + // Strip the language code prefix from German URLs + 'urlParser' => function (string $url, \Kirby\Cms\App $kirby) { + $path = parse_url($url, PHP_URL_PATH); + if (str_starts_with($path, '/de')) { return substr($path, 3); } - return ''; + return $path; } ] ]; diff --git a/composer.lock b/composer.lock index 2f387a8..f072d1b 100644 --- a/composer.lock +++ b/composer.lock @@ -551,16 +551,16 @@ }, { "name": "getkirby/cms", - "version": "4.0.0-rc.1", + "version": "4.0.0-rc.2", "source": { "type": "git", "url": "https://github.com/getkirby/kirby.git", - "reference": "da7346e7abd09cef271ce03eba637e739a85bb67" + "reference": "d0f505e87f7ac30ad2beb90b86f16467ee32c643" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getkirby/kirby/zipball/da7346e7abd09cef271ce03eba637e739a85bb67", - "reference": "da7346e7abd09cef271ce03eba637e739a85bb67", + "url": "https://api.github.com/repos/getkirby/kirby/zipball/d0f505e87f7ac30ad2beb90b86f16467ee32c643", + "reference": "d0f505e87f7ac30ad2beb90b86f16467ee32c643", "shasum": "" }, "require": { @@ -586,7 +586,7 @@ "phpmailer/phpmailer": "6.8.1", "symfony/polyfill-intl-idn": "1.28.0", "symfony/polyfill-mbstring": "1.28.0", - "symfony/yaml": "6.3.7" + "symfony/yaml": "6.3.8" }, "replace": { "symfony/polyfill-php72": "*" @@ -650,7 +650,7 @@ "type": "custom" } ], - "time": "2023-11-10T09:53:21+00:00" + "time": "2023-11-21T10:14:23+00:00" }, { "name": "laminas/laminas-escaper", @@ -2508,16 +2508,16 @@ }, { "name": "symfony/yaml", - "version": "v6.3.7", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "9758b6c69d179936435d0ffb577c3708d57e38a8" + "reference": "3493af8a8dad7fa91c77fa473ba23ecd95334a92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/9758b6c69d179936435d0ffb577c3708d57e38a8", - "reference": "9758b6c69d179936435d0ffb577c3708d57e38a8", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3493af8a8dad7fa91c77fa473ba23ecd95334a92", + "reference": "3493af8a8dad7fa91c77fa473ba23ecd95334a92", "shasum": "" }, "require": { @@ -2560,7 +2560,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.3.7" + "source": "https://github.com/symfony/yaml/tree/v6.3.8" }, "funding": [ { @@ -2576,7 +2576,7 @@ "type": "tidelift" } ], - "time": "2023-10-28T23:31:00+00:00" + "time": "2023-11-06T10:58:05+00:00" } ], "aliases": [], diff --git a/src/extensions/fieldMethods.php b/src/extensions/fieldMethods.php index 51374b3..68557f8 100644 --- a/src/extensions/fieldMethods.php +++ b/src/extensions/fieldMethods.php @@ -1,6 +1,12 @@ kirby(); $blocks = $kirby->option('blocksResolver.files', ['image' => 'image']); @@ -48,7 +54,7 @@ return $block; }; -$pagesFieldResolver = function (\Kirby\Cms\Block $block) { +$pagesFieldResolver = function (Block $block) { $kirby = $block->kirby(); $blocks = $kirby->option('blocksResolver.pages', []); @@ -94,7 +100,7 @@ }; // Support any field type -$customFieldResolver = function (\Kirby\Cms\Block $block) { +$customFieldResolver = function (Block $block) { $kirby = $block->kirby(); $resolvers = $kirby->option('blocksResolver.resolvers', []); @@ -118,14 +124,13 @@ return $block; }; -$nestedBlocksFieldResolver = function (\Kirby\Cms\Block $block) use ($filesFieldResolver) { - /** @var \Kirby\Cms\Block $block */ +$nestedBlocksFieldResolver = function (Block $block) use ($filesFieldResolver) { + /** @var Block $block */ $kirby = $block->kirby(); $nestedBlocks = $kirby->option('blocksResolver.nested', ['prose']); $blocksKeys = array_intersect($block->content()->keys(), $nestedBlocks); foreach ($blocksKeys as $key) { - /** @var \Kirby\Content\Field $ktField */ $field = $block->content()->get($key); $block->content()->update([ @@ -142,31 +147,33 @@ * * @kql-allowed */ - 'resolvePermalinks' => function (\Kirby\Content\Field $field) { + 'resolvePermalinks' => function (Field $field) { $kirby = $field->parent()->kirby(); - $pathParser = $kirby->option('permalinksResolver.pathParser', fn (string $path) => $path); - - if (!is_string($field->value)) { - return $field; - } - - $field->value = preg_replace_callback( - '!href="\/@\/(page|file)\/([^"]+)"!', - function ($matches) use ($kirby, $pathParser) { - $type = $matches[1]; // Either `page` or `file` - $id = $matches[2]; // The UUID - - // Resolve the UUID to the actual model URL - if ($model = \Kirby\Uuid\Uuid::for($type . '://' . $id)?->model()) { - $parsedUrl = parse_url($model->url()); - return 'href="' . $pathParser($parsedUrl['path'] ?? '/', $kirby) . '"'; + $urlParser = $kirby->option('permalinksResolver.urlParser', fn (string $url, \Kirby\Cms\App $kirby) => $url); + + if ($field->isNotEmpty()) { + $dom = new Dom($field->value); + $attributes = ['href', 'src']; + $elements = $dom->query('//*[' . implode(' | ', A::map($attributes, fn ($attribute) => '@' . $attribute)) . ']'); + + foreach ($elements as $element) { + foreach ($attributes as $attribute) { + if ($element->hasAttribute($attribute) && $url = $element->getAttribute($attribute)) { + try { + if ($uuid = Uuid::for($url)) { + $url = $uuid->model()?->url(); + $parsedUrl = $url ? $urlParser($url, $kirby) : null; + $element->setAttribute($attribute, $parsedUrl); + } + } catch (InvalidArgumentException) { + // Ignore anything else than permalinks + } + } } + } - // If not resolvable, return the original match - return $matches[0]; - }, - $field->value - ); + $field->value = $dom->toString(); + } return $field; }, @@ -176,7 +183,7 @@ function ($matches) use ($kirby, $pathParser) { * * @kql-allowed */ - 'toResolvedBlocks' => function (\Kirby\Content\Field $field) use ($pagesFieldResolver, $filesFieldResolver, $customFieldResolver, $nestedBlocksFieldResolver) { + 'toResolvedBlocks' => function (Field $field) use ($pagesFieldResolver, $filesFieldResolver, $customFieldResolver, $nestedBlocksFieldResolver) { return $field ->toBlocks() ->map($nestedBlocksFieldResolver) @@ -190,7 +197,7 @@ function ($matches) use ($kirby, $pathParser) { * * @kql-allowed */ - 'toResolvedLayouts' => function (\Kirby\Content\Field $field) use ($filesFieldResolver, $pagesFieldResolver, $customFieldResolver) { + 'toResolvedLayouts' => function (Field $field) use ($filesFieldResolver, $pagesFieldResolver, $customFieldResolver) { return $field ->toLayouts() ->map(function (\Kirby\Cms\Layout $layout) use ($filesFieldResolver, $pagesFieldResolver, $customFieldResolver) {