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
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
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
opened 10:25AM - 31 May 20 UTC
Page Models are IMO a better construct than [custom page methods](https://getkir… by.com/docs/reference/plugins/extensions/page-methods), but models always applying to a single page type can be frustrating. Would it be useful to have a built-in mechanism for defining a page model that applies to all classes?
I’ll try to explain my concern with page methods, what I’m doing with page models and how it might be improved with built-in support. It makes this post a bit long, which I apologize for in advance.
## Custom page methods vs. page models
Page methods have a few downsides:
1. You need to declare a plugin, which feels a bit awkward when it's part of the "core" features of your site or theme. You have to pick a plugin name (`'projectname'` doesn't work, you need `'somenamespace/projectname'`), copy-paste or write some boilerplate code.
2. They’re closures that rely on the `$this` object to access the page object, and editors cannot know what `$this` refers to and provide completions and validation.
3. They can't override methods of the `Page` class, and when you try to do it, it fails silently.
They’re a bit hidden away in the "Reference > Plugins" documentation, compared to Page Models which are described more proeminently in the Guide. The page methods docs also compares page models and page methods:
> **Custom page methods vs. page models**
>
> Page models are a great way to create an extended version of the default page object with additional methods and functionalities. But a page model is tied to a specific template, while custom page methods apply to all page objects. If your use case is based on one specific template, a page model might be the better solution. If you have methods that should be available for all page objects in all templates, a custom page method is the way to go.
For these reasons, I tend to favor Page Models:
1. Easier to create (less boilerplate)
2. Good typing/intellisense support!
3. And the ability to override a parent method if need be.
The only downside is that when you do want to add a method to all page types, you need to go back to plugin page methods.
## A workaround for a 'base' page model
One way to share functionality between page models is to use class inheritance.
```php
<?php // site/models/common.php
class CommonPage extends Page {
// some methods here
}
```
```php
<?php // site/models/article.php
require_once 'common.php';
class ArticlePage extends CommonPage {
// other methods here
}
```
```php
<?php // site/models/product.php
require_once 'common.php';
class ProductPage extends CommonPage {}
```
This works well and fixes all the issues mentioned before, but introduces two new issues:
1. We have to explicitly require `site/models/common.php` (which can live anywhere)
2. If we want the `CommonPage` methods to be available for *all* page types, we have to create a page model that extends `CommonPage` for every single page type, even if it's an "empty" one (like `ProductPage` in this example). In setups with half a dozen page types or more, that can be verbose.
## A built-in way to have a "base" or "default" page model?
Kirby has fallback mechanisms for:
- Templates: use `site/templates/default.php` (required)
- Controllers: use `site/templates/site.php` (optional)
I propose introducing a fallback or "default" mechanism for page models as well.
For instance, Kirby could:
1. autoload `site/models/default.php`
2. then, for a page whose intendedTemplate is 'article', look first for an `ArticlePage` class then for a `DefaultPage` class.
Developers could then decide to:
- create a `DefaultPage` class or not;
- make their other page models extend `Page` or extend `DefaultPage`, depending on whether they want to inherit methods from DefaultPage or not.
Downside: that's a bit magical.
But the way page models are loaded and defined (with conventions for file names and class names) is already a bit magical, this would fit the current model and using 'default.php' as a fallback has some precedent with templates.
I wouldn't give plugins an explicit way to define a 'default' page model though. Doesn't feel right, but I haven't thought about it much.
What do you think? Good idea? Too complex or too magical?
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!