Issues (6)

src/Routing/Route/I18nRoute.php (2 issues)

1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2019 ChannelWeb Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
namespace BEdita\I18n\Routing\Route;
16
17
use BEdita\I18n\Core\I18nTrait;
18
use Cake\Routing\Route\Route;
19
20
/**
21
 * I18n Route class.
22
 *
23
 * It helps to build and match I18n routing rules.
24
 */
25
class I18nRoute extends Route
26
{
27
    use I18nTrait;
28
29
    /**
30
     * Language string used in route template.
31
     *
32
     * @var string
33
     */
34
    public const LANG_STRING = 'lang';
35
36
    /**
37
     * The placeholder to use in route template.
38
     *
39
     * @var string|null
40
     */
41
    protected ?string $placeholder = null;
42
43
    /**
44
     * @inheritDoc
45
     */
46
    public function __construct($template, $defaults = [], array $options = [])
47
    {
48
        parent::__construct($this->buildTemplate($template), $defaults, $options);
49
50
        if (empty($options['lang'])) {
51
            $this->setPatterns(['lang' => implode('|', array_keys($this->getLanguages()))]);
52
        }
53
    }
54
55
    /**
56
     * Build the right route template adding {lang} or :lang if needed.
57
     *
58
     * If {lang} or :lang is not found add it at the beginning, for example /simple/path becomes /{lang}/simple/path
59
     *
60
     * @param string $template The initial template.
61
     * @return string
62
     */
63
    protected function buildTemplate(string $template): string
64
    {
65
        $this->setPlaceholder($template);
66
        if ($template === '/') {
67
            return '/' . $this->placeholder;
68
        }
69
70
        $path = sprintf('/\/%s(\/.*|$)/', $this->getSearchPattern());
71
        if (preg_match($path, $template)) {
72
            return $template;
73
        }
74
75
        return sprintf('/%s%s', $this->placeholder, $template);
76
    }
77
78
    /**
79
     * Set the right placeholder style.
80
     * If it's present some placeholder in old colon style it uses `:lang`
81
     * else it uses the braces style `{lang}`.
82
     *
83
     * @param string $template The template to analyze
84
     * @return void
85
     */
86
    protected function setPlaceholder(string $template): void
87
    {
88
        $placeholder = '{%s}';
89
        if (preg_match('/:([a-z0-9-_]+(?<![-_]))/i', $template)) {
90
            $placeholder = ':%s';
91
        }
92
93
        $this->placeholder = sprintf($placeholder, static::LANG_STRING);
94
    }
95
96
    /**
97
     * Get search pattern used to know if lang pattern is already present in template.
98
     *
99
     * @return string
100
     */
101
    protected function getSearchPattern(): string
102
    {
103
        if (strpos($this->placeholder, ':' . static::LANG_STRING) === 0) {
0 ignored issues
show
It seems like $this->placeholder can also be of type null; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

103
        if (strpos(/** @scrutinizer ignore-type */ $this->placeholder, ':' . static::LANG_STRING) === 0) {
Loading history...
104
            return $this->placeholder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->placeholder could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
105
        }
106
107
        return sprintf('\{%s\}', static::LANG_STRING);
108
    }
109
110
    /**
111
     * @inheritDoc
112
     */
113
    public function match(array $url, array $context = []): ?string
114
    {
115
        if (!array_key_exists('lang', $url)) {
116
            $url['lang'] = $this->getLang();
117
        }
118
119
        return parent::match($url, $context) ?: null;
120
    }
121
}
122