Passed
Push — master ( d945e3...418e0a )
by Fran
03:26
created

Router::getRoute()   B

Complexity

Conditions 10
Paths 11

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 11.5625

Importance

Changes 0
Metric Value
cc 10
eloc 11
nc 11
nop 3
dl 0
loc 17
ccs 9
cts 12
cp 0.75
crap 11.5625
rs 7.2765
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PSFS\base;
4
5
use PSFS\base\config\Config;
6
use PSFS\base\exception\AccessDeniedException;
7
use PSFS\base\exception\ConfigException;
8
use PSFS\base\exception\RouterException;
9
use PSFS\base\types\helpers\AdminHelper;
10
use PSFS\base\types\helpers\I18nHelper;
11
use PSFS\base\types\helpers\RequestHelper;
12
use PSFS\base\types\helpers\RouterHelper;
13
use PSFS\base\types\helpers\SecurityHelper;
14
use PSFS\base\types\SingletonTrait;
15
use PSFS\controller\base\Admin;
16
use PSFS\services\AdminServices;
17
use Symfony\Component\Finder\Finder;
18
19
20
/**
21
 * Class Router
22
 * @package PSFS
23
 */
24
class Router
25
{
26
27
    use SingletonTrait;
28
29
    protected $routing;
30
    protected $slugs;
31
    private $domains;
32
    /**
33
     * @var Finder $finder
34
     */
35
    private $finder;
36
    /**
37
     * @var \PSFS\base\Cache $cache
38
     */
39
    private $cache;
40
    /**
41
     * @var bool headersSent
42
     */
43
    protected $headersSent = false;
44
45
    /**
46
     * Constructor Router
47
     * @throws ConfigException
48
     */
49 6
    public function __construct()
50
    {
51 6
        $this->finder = new Finder();
52 6
        $this->cache = Cache::getInstance();
53 6
        $this->init();
54 6
    }
55
56
    /**
57
     * Inicializador Router
58
     * @throws ConfigException
59
     */
60 1
    public function init()
61
    {
62 1
        if (!file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . "urls.json") || Config::getInstance()->getDebugMode()) {
63 1
            $this->hydrateRouting();
64 1
            $this->simpatize();
65 1
        } else {
66 1
            list($this->routing, $this->slugs) = $this->cache->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . "urls.json", Cache::JSON, TRUE);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 146 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
67 1
            $this->domains = $this->cache->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . "domains.json", Cache::JSON, TRUE);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 129 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
68
        }
69 1
    }
70
71
    /**
72
     * Método que deriva un error HTTP de página no encontrada
73
     *
74
     * @param \Exception $e
75
     *
76
     * @return string HTML
77
     */
78
    public function httpNotFound(\Exception $e = NULL)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $e. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
79
    {
80
        Logger::log('Throw not found exception');
81
        if (NULL === $e) {
82
            Logger::log('Not found page throwed without previous exception', LOG_WARNING);
83
            $e = new \Exception(_('Page not found'), 404);
84
        }
85
        $template = Template::getInstance()->setStatus($e->getCode());
86
        if (preg_match('/json/i', Request::getInstance()->getServer('CONTENT_TYPE'))) {
87
            return $template->output(json_encode(array(
88
                "success" => FALSE,
89
                "error" => $e->getMessage(),
90
            )), 'application/json');
91
        } else {
92
            return $template->render('error.html.twig', array(
93
                'exception' => $e,
94
                'trace' => $e->getTraceAsString(),
95
                'error_page' => TRUE,
96
            ));
97
        }
98
    }
99
100
    /**
101
     * Método que devuelve las rutas
102
     * @return string|null
103
     */
104
    public function getSlugs()
105
    {
106
        return $this->slugs;
107
    }
108
109
    /**
110
     * Method that extract all routes in the platform
111
     * @return array
112
     */
113
    public function getAllRoutes() {
114
        $routes = [];
115
        foreach($this->routing as $path => $route) {
116
            if(array_key_exists('slug', $route)) {
117
                $routes[$route['slug']] = $path;
118
            }
119
        }
120
        return $routes;
121
    }
122
123
    /**
124
     * Método que calcula el objeto a enrutar
125
     *
126
     * @param string|null $route
127
     *
128
     * @throws \Exception
129
     * @return string HTML
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
130
     */
131
    public function execute($route)
132
    {
133
        Logger::log('Executing the request');
134
        try {
135
            //Check CORS for requests
136
            RequestHelper::checkCORS();
137
            // Checks restricted access
138
            SecurityHelper::checkRestrictedAccess($route);
139
            //Search action and execute
140
            $this->searchAction($route);
141
        } catch (AccessDeniedException $e) {
142
            Logger::log(_('Solicitamos credenciales de acceso a zona restringida'));
143
            return Admin::staticAdminLogon($route);
144
        } catch (RouterException $r) {
145
            Logger::log($r->getMessage(), LOG_WARNING);
146
        } catch (\Exception $e) {
147
            Logger::log($e->getMessage(), LOG_ERR);
148
            throw $e;
149
        }
150
151
        return $this->httpNotFound();
152
    }
153
154
    /**
155
     * Método que busca el componente que ejecuta la ruta
156
     *
157
     * @param string $route
158
     *
159
     * @throws \PSFS\base\exception\RouterException
160
     */
161
    protected function searchAction($route)
162
    {
163
        Logger::log('Searching action to execute: ' . $route, LOG_INFO);
164
        //Revisamos si tenemos la ruta registrada
165
        $parts = parse_url($route);
166
        $path = (array_key_exists('path', $parts)) ? $parts['path'] : $route;
167
        $httpRequest = Request::getInstance()->getMethod();
168
        foreach ($this->routing as $pattern => $action) {
169
            list($httpMethod, $routePattern) = RouterHelper::extractHttpRoute($pattern);
170
            $matched = RouterHelper::matchRoutePattern($routePattern, $path);
171
            if ($matched && ($httpMethod === "ALL" || $httpRequest === $httpMethod) && RouterHelper::compareSlashes($routePattern, $path)) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 140 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
172
                $get = RouterHelper::extractComponents($route, $routePattern);
173
                /** @var $class \PSFS\base\types\Controller */
174
                $class = RouterHelper::getClassToCall($action);
175
                try {
176
                    $this->executeCachedRoute($route, $action, $class, $get);
177
                } catch (\Exception $e) {
178
                    Logger::log($e->getMessage(), LOG_ERR);
179
                    throw new RouterException($e->getMessage(), 404, $e);
180
                }
181
            }
182
        }
183
        throw new RouterException(_("Ruta no encontrada"));
184
    }
185
186
    /**
187
     * Método que manda las cabeceras de autenticación
188
     * @return string HTML
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
189
     */
190
    protected function sentAuthHeader()
191
    {
192
        return AdminServices::getInstance()->setAdminHeaders();
193
    }
194
195 1
    private function checkExternalModules() {
196 1
        $externalModules = Config::getParam('modules.extend');
197 1
        if(null !== $externalModules) {
198
            $externalModules = explode(',', $externalModules);
199
            foreach($externalModules as &$module) {
200
                $module = preg_replace('/(\\\|\/)/', DIRECTORY_SEPARATOR, $module);
201
                $externalModulePath = VENDOR_DIR . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . 'src';
202
                if(file_exists($externalModulePath)) {
203
                    $externalModule = $this->finder->directories()->in($externalModulePath)->depth(0);
204
                    if(!empty($externalModule)) {
205
                        foreach($externalModule as $modulePath) {
206
                            $extModule = $modulePath->getBasename();
207
                            $moduleAutoloader = realpath($externalModulePath . DIRECTORY_SEPARATOR . $extModule . DIRECTORY_SEPARATOR . 'autoload.php');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 152 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
208
                            if(file_exists($moduleAutoloader)) {
209
                                @include $moduleAutoloader;
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
210
                                $this->routing = $this->inspectDir($externalModulePath . DIRECTORY_SEPARATOR . $extModule, $extModule, $this->routing);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 151 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
211
                            }
212
                        }
213
                    }
214
                }
215
            }
216
        }
217 1
    }
218
219
    /**
220
     * Method that gather all the routes in the project
221
     */
222 1
    private function generateRouting()
223
    {
224 1
        $base = SOURCE_DIR;
225 1
        $modulesPath = realpath(CORE_DIR);
226 1
        $this->routing = $this->inspectDir($base, "PSFS", array());
227 1
        if (file_exists($modulesPath)) {
228
            $modules = $this->finder->directories()->in($modulesPath)->depth(0);
229
            foreach($modules as $modulePath) {
230
                $module = $modulePath->getBasename();
231
                $this->routing = $this->inspectDir($modulesPath . DIRECTORY_SEPARATOR . $module, $module, $this->routing);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
232
            }
233
        }
234 1
        $this->checkExternalModules();
235 1
        $this->cache->storeData(CONFIG_DIR . DIRECTORY_SEPARATOR . "domains.json", $this->domains, Cache::JSON, TRUE);
236 1
    }
237
238
    /**
239
     * Método que regenera el fichero de rutas
240
     * @throws ConfigException
241
     */
242 1
    public function hydrateRouting()
243
    {
244 1
        $this->generateRouting();
245 1
        $home = Config::getInstance()->get('home_action');
246 1
        if (NULL !== $home || $home !== '') {
247 1
            $home_params = NULL;
248 1
            foreach ($this->routing as $pattern => $params) {
249 1
                list($method, $route) = RouterHelper::extractHttpRoute($pattern);
250 1
                if (preg_match("/" . preg_quote($route, "/") . "$/i", "/" . $home)) {
251
                    $home_params = $params;
252
                }
253 1
            }
254 1
            if (NULL !== $home_params) {
255
                $this->routing['/'] = $home_params;
256
            }
257 1
        }
258 1
    }
259
260
    /**
261
     * Método que inspecciona los directorios en busca de clases que registren rutas
262
     *
263
     * @param string $origen
264
     * @param string $namespace
265
     * @param array $routing
266
     *
267
     * @return array
268
     * @throws ConfigException
269
     */
270 1
    private function inspectDir($origen, $namespace = 'PSFS', $routing = [])
271
    {
272 1
        $files = $this->finder->files()->in($origen)->path('/(controller|api)/i')->depth(1)->name("*.php");
273 1
        foreach ($files as $file) {
274 1
            $filename = str_replace("/", '\\', str_replace($origen, '', $file->getPathname()));
275 1
            $routing = $this->addRouting($namespace . str_replace('.php', '', $filename), $routing, $namespace);
276 1
        }
277 1
        $this->finder = new Finder();
278
279 1
        return $routing;
280
    }
281
282
    /**
283
     * Checks that a namespace exists
284
     * @param string $namespace
285
     * @return bool
286
     */
287 1
    public static function exists($namespace) {
288 1
        return (class_exists($namespace) || interface_exists($namespace) || trait_exists($namespace));
289
    }
290
291
    /**
292
     * Método que añade nuevas rutas al array de referencia
293
     *
294
     * @param string $namespace
295
     * @param array $routing
296
     * @param string $module
297
     *
298
     * @return array
299
     * @throws ConfigException
300
     */
301 1
    private function addRouting($namespace, &$routing, $module = 'PSFS')
302
    {
303 1
        if (self::exists($namespace)) {
304 1
            $reflection = new \ReflectionClass($namespace);
305 1
            if (FALSE === $reflection->isAbstract() && FALSE === $reflection->isInterface()) {
306 1
                $this->extractDomain($reflection);
307 1
                $classComments = $reflection->getDocComment();
308 1
                preg_match('/@api\ (.*)\n/im', $classComments, $apiPath);
309 1
                $api = '';
310 1
                if (count($apiPath)) {
311
                    $api = array_key_exists(1, $apiPath) ? $apiPath[1] : $api;
312
                }
313 1
                foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
314 1
                    if(preg_match('/@route\ /i', $method->getDocComment())) {
315 1
                        list($route, $info) = RouterHelper::extractRouteInfo($method, $api, $module);
316 1
                        if(null !== $route && null !== $info) {
317 1
                            $info['class'] = $namespace;
318 1
                            $routing[$route] = $info;
319 1
                        }
320 1
                    }
321 1
                }
322 1
            }
323 1
        }
324
325 1
        return $routing;
326
    }
327
328
    /**
329
     * Método que extrae de la ReflectionClass los datos necesarios para componer los dominios en los templates
330
     *
331
     * @param \ReflectionClass $class
332
     *
333
     * @return Router
334
     * @throws ConfigException
335
     */
336 1
    protected function extractDomain(\ReflectionClass $class)
337
    {
338
        //Calculamos los dominios para las plantillas
339 1
        if ($class->hasConstant("DOMAIN") && !$class->isAbstract()) {
340 1
            if(!$this->domains) {
341 1
                $this->domains = [];
342 1
            }
343 1
            $domain = "@" . $class->getConstant("DOMAIN") . "/";
344 1
            if(!array_key_exists($domain, $this->domains)) {
345 1
                $this->domains[$domain] = RouterHelper::extractDomainInfo($class, $domain);
346 1
            }
347 1
        }
348
349 1
        return $this;
350
    }
351
352
    /**
353
     * Método que genera las urls amigables para usar dentro del framework
354
     * @return Router
355
     */
356 1
    public function simpatize()
357
    {
358 1
        $translationFileName = "translations" . DIRECTORY_SEPARATOR . "routes_translations.php";
359 1
        $absoluteTranslationFileName = CACHE_DIR . DIRECTORY_SEPARATOR . $translationFileName;
360 1
        $this->generateSlugs($absoluteTranslationFileName);
361 1
        Config::createDir(CONFIG_DIR);
362 1
        Cache::getInstance()->storeData(CONFIG_DIR . DIRECTORY_SEPARATOR . "urls.json", array($this->routing, $this->slugs), Cache::JSON, TRUE);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 144 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
363
364 1
        return $this;
365
    }
366
367
    /**
368
     * Método que devuelve una ruta del framework
369
     *
370
     * @param string $slug
371
     * @param boolean $absolute
372
     * @param array $params
373
     *
374
     * @return string|null
375
     * @throws RouterException
376
     */
377 1
    public function getRoute($slug = '', $absolute = FALSE, $params = [])
378
    {
379 1
        if (strlen($slug) === 0) {
380
            return ($absolute) ? Request::getInstance()->getRootUrl() . '/' : '/';
381
        }
382 1
        if (NULL === $slug || !array_key_exists($slug, $this->slugs)) {
383
            throw new RouterException(_("No existe la ruta especificada"));
384
        }
385 1
        $url = ($absolute) ? Request::getInstance()->getRootUrl() . $this->slugs[$slug] : $this->slugs[$slug];
386 1
        if (!empty($params)) foreach ($params as $key => $value) {
387
            $url = str_replace("{" . $key . "}", $value, $url);
388 1
        } elseif (!empty($this->routing[$this->slugs[$slug]]["default"])) {
389 1
            $url = ($absolute) ? Request::getInstance()->getRootUrl() . $this->routing[$this->slugs[$slug]]["default"] : $this->routing[$this->slugs[$slug]]["default"];
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 168 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
390 1
        }
391
392 1
        return preg_replace('/(GET|POST|PUT|DELETE|ALL)\#\|\#/', '', $url);
393
    }
394
395
    /**
396
     * Método que devuelve las rutas de administración
397
     * @return array
398
     */
399 1
    public function getAdminRoutes()
400
    {
401 1
        return AdminHelper::getAdminRoutes($this->routing);
402
    }
403
404
    /**
405
     * Método que devuelve le controlador del admin
406
     * @return Admin
407
     */
408
    public function getAdmin()
409
    {
410
        return Admin::getInstance();
411
    }
412
413
    /**
414
     * Método que extrae los dominios
415
     * @return array
416
     */
417
    public function getDomains()
418
    {
419
        return $this->domains ?: [];
420
    }
421
422
    /**
423
     * @param $class
424
     * @param $method
425
     * @param array $params
426
     * @return \ReflectionMethod
427
     */
428
    private function checkAction($class, $method, array $params) {
429
        $action = new \ReflectionMethod($class, $method);
430
431
        foreach($action->getParameters() as $parameter) {
432
            if(!$parameter->isOptional() && !array_key_exists($parameter->getName(), $params)) {
433
                throw new RouterException('Required parameters not sent');
434
            }
435
        }
436
        return $action;
437
    }
438
439
    /**
440
     * Método que ejecuta una acción del framework y revisa si lo tenemos cacheado ya o no
441
     *
442
     * @param string $route
443
     * @param array|null $action
444
     * @param types\Controller $class
445
     * @param array $params
0 ignored issues
show
Documentation introduced by
Should the type for parameter $params not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
446
     */
447
    protected function executeCachedRoute($route, $action, $class, $params = NULL)
448
    {
449
        Logger::log('Executing route ' . $route, LOG_INFO);
450
        Security::getInstance()->setSessionKey("__CACHE__", $action);
451
        $cache = Cache::needCache();
452
        $execute = TRUE;
453
        if (FALSE !== $cache && Config::getInstance()->getDebugMode() === FALSE) {
454
            $cacheDataName = $this->cache->getRequestCacheHash();
455
            $tmpDir = substr($cacheDataName, 0, 2) . DIRECTORY_SEPARATOR . substr($cacheDataName, 2, 2) . DIRECTORY_SEPARATOR;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
456
            $cachedData = $this->cache->readFromCache("json" . DIRECTORY_SEPARATOR . $tmpDir . $cacheDataName,
457
                $cache, function () {});
0 ignored issues
show
Bug introduced by
It seems like $cache defined by \PSFS\base\Cache::needCache() on line 451 can also be of type boolean; however, PSFS\base\Cache::readFromCache() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
458
            if (NULL !== $cachedData) {
459
                $headers = $this->cache->readFromCache("json" . DIRECTORY_SEPARATOR . $tmpDir . $cacheDataName . ".headers",
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
460
                    $cache, function () {}, Cache::JSON);
0 ignored issues
show
Bug introduced by
It seems like $cache defined by \PSFS\base\Cache::needCache() on line 451 can also be of type boolean; however, PSFS\base\Cache::readFromCache() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
461
                Template::getInstance()->renderCache($cachedData, $headers);
462
                $execute = FALSE;
463
            }
464
        }
465
        if ($execute) {
466
            Logger::log(_('Start executing action'), LOG_DEBUG);
467
            $method = $this->checkAction($class, $action['method'], $params);
468
            $method->invoke($class, $params);
469
        }
470
    }
471
472
    /**
473
     * Parse slugs to create translations
474
     *
475
     * @param string $absoluteTranslationFileName
476
     */
477 1
    private function generateSlugs($absoluteTranslationFileName)
478
    {
479 1
        $translations = I18nHelper::generateTranslationsFile($absoluteTranslationFileName);
480 1
        foreach ($this->routing as $key => &$info) {
481 1
            $keyParts = $key;
482 1
            if (FALSE === strstr("#|#", $key)) {
483 1
                $keyParts = explode("#|#", $key);
484 1
                $keyParts = array_key_exists(1, $keyParts) ? $keyParts[1] : '';
485 1
            }
486 1
            $slug = RouterHelper::slugify($keyParts);
487 1
            if (NULL !== $slug && !array_key_exists($slug, $translations)) {
488 1
                $translations[$slug] = $key;
489 1
                file_put_contents($absoluteTranslationFileName, "\$translations[\"{$slug}\"] = _(\"{$slug}\");\n", FILE_APPEND);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
490 1
            }
491 1
            $this->slugs[$slug] = $key;
492 1
            $info["slug"] = $slug;
493 1
        }
494 1
    }
495
496
}
497