As designed String parameter routes don't seem to match properly if the parameter contains a dot.

Jake B.

Well-known member
Affected version
2.1.10
I've got a route /test/sub/:id
Route prefix: test
Sub-name: sub
Route format: sub/:str<sub_id>

Anything with a sub_id containing a ., for example app.test gives me:

Code:
The requested page could not be found. (Code: invalid_action, controller: Test:Sub, action: AppTest)
 
The short answer is that :str match is alphanumeric and underscore/dash only. A dot is used as part of int matching and resolves ambiguity in routes that can support both string and int style matching.

There isn't really a match that is like string plus dot. :any is the closest, though that will basically consume up to the end normally, so you may have to get a bit more creative if you want actions. It's not really designed for this usage.
 
Managed to get it working pretty easily this morning:

PHP:
<?php

namespace Test\XF\Mvc;

use XF\Mvc\RouteBuiltLink;

class Router extends XFCP_Router
{
    public function generateMatchRegexInner($format, $wrapper = '#')
    {
        $matchRegex = parent::generateMatchRegexInner($format, $wrapper);

        $matchRegex = preg_replace_callback(
            '#:(\+)?test_str_extended(?:_p)?<([a-zA-Z0-9_.]+)>/?#',
            function ($match) {
                $mainMatch = '(?:(?P<' . $match[2] . '>[a-zA-Z0-9_-]+)/)';
                return $match[1] ? $mainMatch : "{$mainMatch}?";
            },
            $matchRegex
        );

        return $matchRegex;
    }

    protected function buildRouteUrl($prefix, array $route, $action, $data = null, array &$parameters = [])
    {
        $url = parent::buildRouteUrl($prefix, $route, $action, $data, $parameters);

        if (!empty($route['build_callback']) && is_string($url) || $url instanceof RouteBuiltLink) {
            return $url;
        }

        $url = preg_replace_callback(
            '#:(?:\+)?test_str_extended(_p)?<([a-zA-Z0-9_.]+)>(/?)#',
            function($match) use ($data, &$parameters) {
                $inParams = !empty($match[1]);
                $stringKey = $match[2];
                $trailingSlash = $match[3];

                $search = $inParams ? $parameters : $data;

                if ($search && isset($search[$stringKey])) {
                    $key = strval($search[$stringKey]);

                    if ($inParams) {
                        unset($parameters[$stringKey]);
                    }

                    if (strlen($key)) {
                        return $key . $trailingSlash;
                    }
                }

                return '';
            },
            $url
        );

        return $url;
    }
}
 
Top Bottom