Passed
Push — master ( 1dadf3...61c4e2 )
by Fran
03:43
created

Router::searchAction()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 8
nop 1
dl 0
loc 24
ccs 0
cts 18
cp 0
crap 72
rs 5.7377
c 0
b 0
f 0
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\GeneratorHelper;
11
use PSFS\base\types\helpers\I18nHelper;
12
use PSFS\base\types\helpers\RequestHelper;
13
use PSFS\base\types\helpers\RouterHelper;
14
use PSFS\base\types\helpers\SecurityHelper;
15
use PSFS\base\types\SingletonTrait;
16
use PSFS\controller\base\Admin;
17
use PSFS\services\AdminServices;
18
use Symfony\Component\Finder\Finder;
19
20
21
/**
22
 * Class Router
23
 * @package PSFS
24
 */
25
class Router
26
{
27
28
    use SingletonTrait;
29
30
    protected $routing;
31
    protected $slugs;
32
    private $domains;
33
    /**
34
     * @var Finder $finder
35
     */
36
    private $finder;
37
    /**
38
     * @var \PSFS\base\Cache $cache
39
     */
40
    private $cache;
41
    /**
42
     * @var bool headersSent
43
     */
44
    protected $headersSent = false;
45
46
    /**
47
     * Constructor Router
48
     * @throws ConfigException
49
     */
50 6
    public function __construct()
51
    {
52 6
        $this->finder = new Finder();
53 6
        $this->cache = Cache::getInstance();
54 6
        $this->init();
55 6
    }
56
57
    /**
58
     * Inicializador Router
59
     * @throws ConfigException
60
     */
61 1
    public function init()
62
    {
63 1
        if (!file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . "urls.json") || Config::getInstance()->getDebugMode()) {
64 1
            $this->hydrateRouting();
65 1
            $this->simpatize();
66 1
        } else {
67 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...
68 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...
69
        }
70 1
    }
71
72
    /**
73
     * Método que deriva un error HTTP de página no encontrada
74
     *
75
     * @param \Exception $e
76
     *
77
     * @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...
78
     */
79
    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...
80
    {
81
        Logger::log('Throw not found exception');
82
        if (NULL === $e) {
83
            Logger::log('Not found page throwed without previous exception', LOG_WARNING);
84
            $e = new \Exception(_('Page not found'), 404);
85
        }
86
        $template = Template::getInstance()->setStatus($e->getCode());
87
        if (preg_match('/json/i', Request::getInstance()->getServer('CONTENT_TYPE'))) {
88
            return $template->output(json_encode(array(
89
                "success" => FALSE,
90
                "error" => $e->getMessage(),
91
            )), 'application/json');
92
        } else {
93
            return $template->render('error.html.twig', array(
94
                'exception' => $e,
95
                'trace' => $e->getTraceAsString(),
96
                'error_page' => TRUE,
97
            ));
98
        }
99
    }
100
101
    /**
102
     * Método que devuelve las rutas
103
     * @return string|null
104
     */
105
    public function getSlugs()
106
    {
107
        return $this->slugs;
108
    }
109
110
    /**
111
     * Method that extract all routes in the platform
112
     * @return array
113
     */
114 1
    public function getAllRoutes()
115
    {
116
        $routes = [];
117
        foreach ($this->routing as $path => $route) {
118
            if (array_key_exists('slug', $route)) {
119
                $routes[$route['slug']] = $path;
120
            }
121
        }
122
        return $routes;
123 1
    }
124
125
    /**
126
     * Método que calcula el objeto a enrutar
127
     *
128
     * @param string|null $route
129
     *
130
     * @throws \Exception
131
     * @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...
132
     */
133
    public function execute($route)
134
    {
135
        Logger::log('Executing the request');
136
        try {
137
            //Check CORS for requests
138
            RequestHelper::checkCORS();
139
            // Checks restricted access
140
            SecurityHelper::checkRestrictedAccess($route);
141
            //Search action and execute
142
            $this->searchAction($route);
143
        } catch (AccessDeniedException $e) {
144
            Logger::log(_('Solicitamos credenciales de acceso a zona restringida'));
145
            return Admin::staticAdminLogon($route);
146
        } catch (RouterException $r) {
147
            Logger::log($r->getMessage(), LOG_WARNING);
148
        } catch (\Exception $e) {
149
            Logger::log($e->getMessage(), LOG_ERR);
150
            throw $e;
151
        }
152
153
        return $this->httpNotFound();
154
    }
155
156
    /**
157
     * Método que busca el componente que ejecuta la ruta
158
     *
159
     * @param string $route
160
     *
161
     * @throws \PSFS\base\exception\RouterException
162
     */
163
    protected function searchAction($route)
164
    {
165
        Logger::log('Searching action to execute: ' . $route, LOG_INFO);
166
        //Revisamos si tenemos la ruta registrada
167
        $parts = parse_url($route);
168
        $path = (array_key_exists('path', $parts)) ? $parts['path'] : $route;
169
        $httpRequest = Request::getInstance()->getMethod();
170
        foreach ($this->routing as $pattern => $action) {
171
            list($httpMethod, $routePattern) = RouterHelper::extractHttpRoute($pattern);
172
            $matched = RouterHelper::matchRoutePattern($routePattern, $path);
173
            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...
174
                $get = RouterHelper::extractComponents($route, $routePattern);
175
                /** @var $class \PSFS\base\types\Controller */
176
                $class = RouterHelper::getClassToCall($action);
177
                try {
178
                    $this->executeCachedRoute($route, $action, $class, $get);
179
                } catch (\Exception $e) {
180
                    Logger::log($e->getMessage(), LOG_ERR);
181
                    throw new RouterException($e->getMessage(), 404, $e);
182
                }
183
            }
184
        }
185
        throw new RouterException(_("Ruta no encontrada"));
186
    }
187
188
    /**
189
     * Método que manda las cabeceras de autenticación
190
     * @return string HTML
191
     */
192
    protected function sentAuthHeader()
193
    {
194
        return AdminServices::getInstance()->setAdminHeaders();
195
    }
196
197 1
    private function checkExternalModules()
198
    {
199 1
        $externalModules = Config::getParam('modules.extend');
200 1
        if (null !== $externalModules) {
201
            $externalModules = explode(',', $externalModules);
202
            foreach ($externalModules as &$module) {
203
                $module = preg_replace('/(\\\|\/)/', DIRECTORY_SEPARATOR, $module);
204
                $externalModulePath = VENDOR_DIR . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . 'src';
205
                if (file_exists($externalModulePath)) {
206
                    $externalModule = $this->finder->directories()->in($externalModulePath)->depth(0);
207
                    if (!empty($externalModule)) {
208
                        foreach ($externalModule as $modulePath) {
209
                            $extModule = $modulePath->getBasename();
210
                            $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...
211
                            if (file_exists($moduleAutoloader)) {
212
                                @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...
213
                                $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...
214
                            }
215
                        }
216
                    }
217
                }
218
            }
219
        }
220 1
    }
221
222
    /**
223
     * Method that gather all the routes in the project
224
     */
225 1
    private function generateRouting()
226
    {
227 1
        $base = SOURCE_DIR;
228 1
        $modulesPath = realpath(CORE_DIR);
229 1
        $this->routing = $this->inspectDir($base, "PSFS", array());
230 1
        if (file_exists($modulesPath)) {
231
            $modules = $this->finder->directories()->in($modulesPath)->depth(0);
232
            foreach ($modules as $modulePath) {
233
                $module = $modulePath->getBasename();
234
                $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...
235
            }
236
        }
237 1
        $this->checkExternalModules();
238 1
        $this->cache->storeData(CONFIG_DIR . DIRECTORY_SEPARATOR . "domains.json", $this->domains, Cache::JSON, TRUE);
239 1
    }
240
241
    /**
242
     * Método que regenera el fichero de rutas
243
     * @throws ConfigException
244
     */
245 1
    public function hydrateRouting()
246
    {
247 1
        $this->generateRouting();
248 1
        $home = Config::getInstance()->get('home_action');
249 1
        if (NULL !== $home || $home !== '') {
250 1
            $home_params = NULL;
251 1
            foreach ($this->routing as $pattern => $params) {
252 1
                list($method, $route) = RouterHelper::extractHttpRoute($pattern);
253 1
                if (preg_match("/" . preg_quote($route, "/") . "$/i", "/" . $home)) {
254
                    $home_params = $params;
255
                }
256 1
            }
257 1
            if (NULL !== $home_params) {
258
                $this->routing['/'] = $home_params;
259
            }
260 1
        }
261 1
    }
262
263
    /**
264
     * Método que inspecciona los directorios en busca de clases que registren rutas
265
     *
266
     * @param string $origen
267
     * @param string $namespace
268
     * @param array $routing
269
     *
270
     * @return array
271
     * @throws ConfigException
272
     */
273 1
    private function inspectDir($origen, $namespace = 'PSFS', $routing = [])
274
    {
275 1
        $files = $this->finder->files()->in($origen)->path('/(controller|api)/i')->depth(1)->name("*.php");
276 1
        foreach ($files as $file) {
277 1
            $filename = str_replace("/", '\\', str_replace($origen, '', $file->getPathname()));
278 1
            $routing = $this->addRouting($namespace . str_replace('.php', '', $filename), $routing, $namespace);
279 1
        }
280 1
        $this->finder = new Finder();
281
282 1
        return $routing;
283
    }
284
285
    /**
286
     * Checks that a namespace exists
287
     * @param string $namespace
288
     * @return bool
289
     */
290 1
    public static function exists($namespace)
291
    {
292 1
        return (class_exists($namespace) || interface_exists($namespace) || trait_exists($namespace));
293
    }
294
295
    /**
296
     * Método que añade nuevas rutas al array de referencia
297
     *
298
     * @param string $namespace
299
     * @param array $routing
300
     * @param string $module
301
     *
302
     * @return array
303
     * @throws ConfigException
304
     */
305 1
    private function addRouting($namespace, &$routing, $module = 'PSFS')
306
    {
307 1
        if (self::exists($namespace)) {
308 1
            $reflection = new \ReflectionClass($namespace);
309 1
            if (FALSE === $reflection->isAbstract() && FALSE === $reflection->isInterface()) {
310 1
                $this->extractDomain($reflection);
311 1
                $classComments = $reflection->getDocComment();
312 1
                preg_match('/@api\ (.*)\n/im', $classComments, $apiPath);
313 1
                $api = '';
314 1
                if (count($apiPath)) {
315
                    $api = array_key_exists(1, $apiPath) ? $apiPath[1] : $api;
316
                }
317 1
                foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
318 1
                    if (preg_match('/@route\ /i', $method->getDocComment())) {
319 1
                        list($route, $info) = RouterHelper::extractRouteInfo($method, $api, $module);
320 1
                        if (null !== $route && null !== $info) {
321 1
                            $info['class'] = $namespace;
322 1
                            $routing[$route] = $info;
323 1
                        }
324 1
                    }
325 1
                }
326 1
            }
327 1
        }
328
329 1
        return $routing;
330
    }
331
332
    /**
333
     * Método que extrae de la ReflectionClass los datos necesarios para componer los dominios en los templates
334
     *
335
     * @param \ReflectionClass $class
336
     *
337
     * @return Router
338
     * @throws ConfigException
339
     */
340 1
    protected function extractDomain(\ReflectionClass $class)
341
    {
342
        //Calculamos los dominios para las plantillas
343 1
        if ($class->hasConstant("DOMAIN") && !$class->isAbstract()) {
344 1
            if (!$this->domains) {
345 1
                $this->domains = [];
346 1
            }
347 1
            $domain = "@" . $class->getConstant("DOMAIN") . "/";
348 1
            if (!array_key_exists($domain, $this->domains)) {
349 1
                $this->domains[$domain] = RouterHelper::extractDomainInfo($class, $domain);
350 1
            }
351 1
        }
352
353 1
        return $this;
354
    }
355
356
    /**
357
     * Método que genera las urls amigables para usar dentro del framework
358
     * @return Router
359
     */
360 1
    public function simpatize()
361
    {
362 1
        $translationFileName = "translations" . DIRECTORY_SEPARATOR . "routes_translations.php";
363 1
        $absoluteTranslationFileName = CACHE_DIR . DIRECTORY_SEPARATOR . $translationFileName;
364 1
        $this->generateSlugs($absoluteTranslationFileName);
365 1
        GeneratorHelper::createDir(CONFIG_DIR);
366 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...
367
368 1
        return $this;
369
    }
370
371
    /**
372
     * Método que devuelve una ruta del framework
373
     *
374
     * @param string $slug
375
     * @param boolean $absolute
376
     * @param array $params
377
     *
378
     * @return string|null
379
     * @throws RouterException
380
     */
381 1
    public function getRoute($slug = '', $absolute = FALSE, $params = [])
382
    {
383 1
        if (strlen($slug) === 0) {
384
            return ($absolute) ? Request::getInstance()->getRootUrl() . '/' : '/';
385
        }
386 1
        if (NULL === $slug || !array_key_exists($slug, $this->slugs)) {
387
            throw new RouterException(_("No existe la ruta especificada"));
388
        }
389 1
        $url = ($absolute) ? Request::getInstance()->getRootUrl() . $this->slugs[$slug] : $this->slugs[$slug];
390 1
        if (!empty($params)) foreach ($params as $key => $value) {
391
            $url = str_replace("{" . $key . "}", $value, $url);
392 1
        } elseif (!empty($this->routing[$this->slugs[$slug]]["default"])) {
393 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...
394 1
        }
395
396 1
        return preg_replace('/(GET|POST|PUT|DELETE|ALL)\#\|\#/', '', $url);
397
    }
398
399
    /**
400
     * Método que devuelve las rutas de administración
401
     * @return array
402
     */
403 1
    public function getAdminRoutes()
404
    {
405 1
        return AdminHelper::getAdminRoutes($this->routing);
406
    }
407
408
    /**
409
     * Método que devuelve le controlador del admin
410
     * @return Admin
411
     */
412
    public function getAdmin()
413
    {
414
        return Admin::getInstance();
415
    }
416
417
    /**
418
     * Método que extrae los dominios
419
     * @return array
420
     */
421
    public function getDomains()
422
    {
423
        return $this->domains ?: [];
424
    }
425
426
    /**
427
     * @param $class
428
     * @param $method
429
     * @param array $params
430
     * @return \ReflectionMethod
431
     */
432
    private function checkAction($class, $method, array $params)
433
    {
434
        $action = new \ReflectionMethod($class, $method);
435
436
        foreach ($action->getParameters() as $parameter) {
437
            if (!$parameter->isOptional() && !array_key_exists($parameter->getName(), $params)) {
438
                throw new RouterException('Required parameters not sent');
439
            }
440
        }
441
        return $action;
442
    }
443
444
    /**
445
     * Método que ejecuta una acción del framework y revisa si lo tenemos cacheado ya o no
446
     *
447
     * @param string $route
448
     * @param array|null $action
449
     * @param types\Controller $class
450
     * @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...
451
     */
452
    protected function executeCachedRoute($route, $action, $class, $params = NULL)
453
    {
454
        Logger::log('Executing route ' . $route, LOG_INFO);
455
        Security::getInstance()->setSessionKey("__CACHE__", $action);
456
        $cache = Cache::needCache();
457
        $execute = TRUE;
458
        if (FALSE !== $cache && Config::getInstance()->getDebugMode() === FALSE) {
459
            $cacheDataName = $this->cache->getRequestCacheHash();
460
            $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...
461
            $cachedData = $this->cache->readFromCache("json" . DIRECTORY_SEPARATOR . $tmpDir . $cacheDataName,
462
                $cache, function () {
0 ignored issues
show
Bug introduced by
It seems like $cache defined by \PSFS\base\Cache::needCache() on line 456 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...
463
                });
464
            if (NULL !== $cachedData) {
465
                $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...
466
                    $cache, function () {
0 ignored issues
show
Bug introduced by
It seems like $cache defined by \PSFS\base\Cache::needCache() on line 456 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...
467
                    }, Cache::JSON);
468
                Template::getInstance()->renderCache($cachedData, $headers);
469
                $execute = FALSE;
470
            }
471
        }
472
        if ($execute) {
473
            Logger::log(_('Start executing action'), LOG_DEBUG);
474
            if (false === call_user_func_array(array($class, $action['method']), $params)) {
475
                Logger::log(_('An error ocurred trying to execute the action'), LOG_ERR, [error_get_last()]);
476
            }
477
        }
478
    }
479
480
    /**
481
     * Parse slugs to create translations
482
     *
483
     * @param string $absoluteTranslationFileName
484
     */
485 1
    private function generateSlugs($absoluteTranslationFileName)
486
    {
487 1
        $translations = I18nHelper::generateTranslationsFile($absoluteTranslationFileName);
488 1
        foreach ($this->routing as $key => &$info) {
489 1
            $keyParts = $key;
490 1
            if (FALSE === strstr("#|#", $key)) {
491 1
                $keyParts = explode("#|#", $key);
492 1
                $keyParts = array_key_exists(1, $keyParts) ? $keyParts[1] : '';
493 1
            }
494 1
            $slug = RouterHelper::slugify($keyParts);
495 1
            if (NULL !== $slug && !array_key_exists($slug, $translations)) {
496 1
                $translations[$slug] = $key;
497 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...
498 1
            }
499 1
            $this->slugs[$slug] = $key;
500 1
            $info["slug"] = $slug;
501 1
        }
502 1
    }
503
504
}
505