Custom URL function for trailing slashes

Hi,

we are using the recommended .htaccess settings for trailing slashes in the url:
https://getkirby.com/docs/cookbook/setup/trailing-slash

This breaks form submissions due to the redirect.

In order to prevent unnecessary redirects and fix the form submission problems I want to modify the url() function to append a trailing slash.

Unfortunately the code from the documentation leads to an error
https://getkirby.com/docs/reference/plugins/components/url

<?php

Kirby::plugin('my/urls', [
  'components' => [
    'url' => function (Kirby $kirby, string $path, array $options = [], Closure $originalHandler): string {
        return $originalHandler($path, $options) . '/';
    }
  ]
]);

?>

Error

TypeError
Argument 3 passed to Kirby\Toolkit\F::{closure}() must be of the type array, null given, called in /var/www/html/kirby/src/Cms/Url.php on line 67

I just want to add a trailing slash to url() function for all pages, so all links get printed out correctly upfront without the need for additional redirects.

Thanks in advance.

Kind Regards,
Michael

I wonder if it wouldn’t be sufficient to set the URL with the slash in the config file with the url option:

Thanks for your reply.
This only affects the base part of the URL and has no effect on the trailing slashes on page links :slightly_frowning_face:

What is the reason to have trailing slashes for all URLs?

I think the code snippet in the docs might be outdated.
I’m no expert, but I’d expect the native component to have the same function signature as the custom component. Therefore you would be missing to pass the $kirby object to the original handler.

Could you try this instead?

Kirby::plugin('my/urls', [
  'components' => [
    'url' => function (Kirby $kirby, string $path = null, array $options = null, $deprecated = null): string {
        $native = $kirby->nativeComponent('url');
        return $native($kirby, $path, $options) . '/';
    }
  ]
]);

Also, the $originalHandler argument was deprecated and will probably be removed in version 3.6

@Adspectus this is mainly for a SEO perspective. We are using trailing slashes in .htaccess but Kirby outputs all links without a trailing slash. So each time a user clicks a link or the search engine crawls the page an unnecessary 301 happens. This also breaks form submissions using action="<?= $page->url ?>"

@rasteiner thanks, that seems to work :slightly_smiling_face:
Is there any option to distinguish between a page and for example a css asset inside this function?

The trailing slash should only be added to pages not css or js files for example. The kirby object doesn’t seem to hold this information or I am missing something here?

Thanks in advance.

Kind Regards,
Michael

I don’t know.
An easy way could be checking if the path resolves to a page:

Kirby::plugin('my/urls', [
  'components' => [
    'url' => function (Kirby $kirby, string $path = null, array $options = [], $deprecated = null): string {
        $native = $kirby->nativeComponent('url');

        $parts = Str::split('#', $path);
        if(page($parts[0] ?? null)) {
           $options['slash'] = true;
        } 

        return $native($kirby, $path, $options);
    }
  ]
]);

Well, I just found out that we are trying to modify the wrong function here.
The function that needs to be modified is the page method $page->url()

Page Methods - Docs

Be aware that - contrary to page models - custom page methods cannot override default page methods.

So I would have to extend the default page model in order to add the trailing slashes to all page links. Tried this but the function doesn’t get called:

<?php
//    File: /site/models/default.php
class DefaultPage extends Page {
  public function url($options = null): string {
    return parent::url($options) . '/';
  }
}
?>

This all seems to be a ugly workaround. Maybe we will have to discard the trailing slashes globally.

As stated in the following open issue default page models don’t seem to be possible at the moment :slightly_frowning_face:

Excactly, you would have to create a BaseModel and extend that for every page type.

Although you can probably make your life easier by registering the models in a plugin: Quicktip: Different blueprints — same template | Kirby CMS then you would only need one file and register this under different names

Thanks for all the hard work researching this! Reading this, I went for a simpler solution. I placed this global helper function inside site/config/config.php:

/**
 * Get the URL from a page, with a trailing slash
 */
function permalink(Kirby\Cms\Page $page): string {
	return $page->url() . '/';
}

And then I replaced all occurrences of $page->url() in my site with permalink($page). Works!