Route filter does not work like expected

I just tried out the new route filter function but I did not get it to work like expected.

  1. The first route matches anything.
  2. If it finds a page prefixed with items/ like items/about it will return true and run the action.
  3. The action will fire if it matches correctly.

All the steps above work, so the first route work… as long as I don’t add a second one.

$kirby = kirby();
$kirby->set('route', [
    'pattern' => '(:any)',
    'filter' => function($route) {
        if( ! page('items/' . $route->arguments[0]) )
            return false;
    },
    'action' => function($uid) {
        return site()->visit('items/' . $uid);
    }
]);

/*$kirby->set('route', [
    'pattern' => '(:any)',
    'action' => function() {
        echo 'Overwritten';
    }
]);*/

If someone like @texnixe or @lukasbestle are doing a test, run the uncommented code first and make sure it works.

Second route

When uncommending the second route, it will run instead of the first one, writing Overwritten on the screen. Because the first route successfully matched, it should go to that action and visit the page and never even go down to the second route?

Just to make it super clear. I expected the success of the first route to prevent the second route to take over.

Maybe I understand the concept of filter wrong, maybe it’s a bug, I don’t know.

Another case

Two similar routes that does not work together like this. It always takes the last route no matter what I do.

$kirby->set('route', [
    'pattern' => '(:any)',
    'filter' => function($route) {
        if( ! page('items/' . $route->arguments[0]) )
            return false;
    },
    'action' => function($uid) {
        return site()->visit('items/' . $uid);
    }
]);

$kirby->set('route', [
    'pattern' => '(:any)',
    'filter' => function($route) {
        if( ! page('_pages/' . $route->arguments[0]) )
            return false;
    },
    'action' => function($uid) {
        return site()->visit('_pages/' . $uid);
    }
]);

Are 2 routes with the same pattern added, or is only the last one added?
Can you check with var_dump($kirby->routes()); ?

It looks like both routes are added. The pattern does not seem to be used as a key, but it’s a long and messy object/array which is a bit hard to follow.

Probably the first one matched in the array (which is the last one added to it?) will be run, and the rest is discarded?

Somehow it does not make sense to add two routes with the same pattern; I would put the logic for both routes within the filter/action key.

I agree @texnixe, but I think @jenstornell was looking at how routing internally works.

There can currently only be one route per pattern. I somehow consider this a bug, but we can’t easily fix it without a breaking change as that’s how the router works at the moment.

I have noted this as a feature request for v3.

@texnixe Thanks for the idea of having them in the same route! :slight_smile:

About my idea with two routes having the same pattern, I have a real life case for that.

I have a plugin from the past: https://github.com/jenstornell/kirby-status. To make it redirect, a quite hacky solution is needed at the moment (adding a function before <html> in the header.php).

I thought that a route filter could be a good solution here.

Here is kind of how the code for it would look like for Kirby Status (untested):

$kirby->set('route', [
    'pattern' => '(:any)',
    'filter' => function($route) {
        if( page($route->arguments[0])->published() )
            return false;
    },
    'action' => function($uid) {
        return site()->visit('error');
    }
]);

Later I might have a custom plugin with that same pattern for something else, maybe unaware of that this plugin also has the same pattern.

I’m aware that the order of things is still important and a custom route should be run before all the plugins, or after them depending on the importance of the route. The difference with filters is that it will jump to the next if the first one did not match the filter, which is awesome.

@bvdputte Yeah, what possibilities we have, especially for plugin development.

@lukasbestle Thanks! That would open up some new possibilites. :slight_smile:

Maybe it’s already possible to get around this issue. I’m really not good with regex, but maybe some hackish route solution like this could work in the meantime?

'pattern' => '(:any)^(?!.*[my_unique_word]).*$'

It should match anything that does not contain [my_unique_word]. Then the pattern would be unique, which seems to be the Kirby issue here and the unique word will never be matched so it will not affect the route.

The regex probably have some errors but maybe you can see what I try to do?

Update

This pattern almost works:

(^[^abc]+$)

It matches a or b or c, but it should match abc as a word. Anyway it’s true that when having two different patterns, both routes can co-exist…

Update 2

This seems to work:

(^(?:(?!\[my_unique_word\]).)*$)

Does it look correct? Match anything except [my_unique_word].

Complete workaround

It works perfectly so far! :slight_smile:

$kirby = kirby();
$kirby->set('route', [
    'pattern' => '(^(?:(?!\[items\]).)*$)',
    'filter' => function($route) {
        if( ! page('items/' . $route->arguments[0]) )
            return false;
    },
    'action' => function($uid) {
        return site()->visit('items/' . $uid);
    }
]);

$kirby->set('route', [
    'pattern' => '(^(?:(?!\[pages\]).)*$)',
    'filter' => function($route) {
        if( ! page('_pages/' . $route->arguments[0]) )
            return false;
    },
    'action' => function($uid) {
        return site()->visit('_pages/' . $uid);
    }
]);

If there is a better/shorter regex I would be thankful for that.

Well, that’s quite a hack, but fortunately not one that affects other plugins or other parts of Kirby, so it’s probably fine for a change. :wink:
I see your use-case – we’ll definitely try to fix this in v3. :slight_smile:

1 Like