Passed
Push — master ( 70f98a...9ce8e2 )
by Fran
05:26
created

Router::extractReflectionCacheability()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 2
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\SingletonTrait;
10
use PSFS\controller\base\Admin;
11
use PSFS\services\AdminServices;
12
use Symfony\Component\Finder\Finder;
13
14
15
/**
16
 * Class Router
17
 * @package PSFS
18
 */
19
class Router
20
{
21
22
    use SingletonTrait;
23
24
    protected $routing;
25
    protected $slugs;
26
    private $domains;
27
    /**
28
     * @var Finder $finder
29
     */
30
    private $finder;
31
    /**
32
     * @var \PSFS\base\Cache $cache
33
     */
34
    private $cache;
35
    /**
36
     * @var bool headersSent
37
     */
38
    protected $headersSent = false;
39
40
    /**
41
     * Constructor Router
42
     * @throws ConfigException
43
     */
44 6
    public function __construct()
45
    {
46 6
        $this->finder = new Finder();
47 6
        $this->cache = Cache::getInstance();
48 6
        $this->init();
49 6
    }
50
51
    /**
52
     * Inicializador Router
53
     * @throws ConfigException
54
     */
55 1
    public function init()
56
    {
57 1
        if (!file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . "urls.json") || Config::getInstance()->getDebugMode()) {
58 1
            $this->hydrateRouting();
59 1
            $this->simpatize();
60 1
        } else {
61 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...
62 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...
63
        }
64 1
    }
65
66
    /**
67
     * Método que deriva un error HTTP de página no encontrada
68
     *
69
     * @param \Exception $e
70
     *
71
     * @return string HTML
72
     */
73
    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...
74
    {
75
        Logger::log('Throw not found exception');
76
        $template = Template::getInstance()->setStatus($e->getCode());
77
        if (preg_match('/json/i', Request::getInstance()->getServer('CONTENT_TYPE'))) {
78
            return $template->output(json_encode(array(
79
                "success" => FALSE,
80
                "error" => $e->getMessage(),
81
            )), 'application/json');
82
        } else {
83
            if (NULL === $e) {
84
                Logger::log('Not found page throwed without previus exception');
85
                $e = new \Exception(_('Page not found'), 404);
86
            }
87
88
            return $template->render('error.html.twig', array(
89
                'exception' => $e,
90
                'trace' => $e->getTraceAsString(),
91
                'error_page' => TRUE,
92
            ));
93
        }
94
    }
95
96
    /**
97
     * Método que devuelve las rutas
98
     * @return string|null
99
     */
100 1
    public function getSlugs()
101 1
    {
102
        return $this->slugs;
103
    }
104
105
    /**
106
     * Method that extract all routes in the platform
107
     * @return array
108
     */
109
    public function getAllRoutes() {
110
        $routes = [];
111
        foreach($this->routing as $path => $route) {
112
            if(array_key_exists('slug', $route)) {
113
                $routes[$route['slug']] = $path;
114
            }
115
        }
116
        return $routes;
117
    }
118
119
    /**
120
     * Método que calcula el objeto a enrutar
121
     *
122
     * @param string|null $route
123
     *
124
     * @throws \Exception
125
     * @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...
126
     */
127
    public function execute($route)
128
    {
129
        Logger::log('Executing the request');
130
        try {
131
            //Check CORS for requests
132
            $this->checkCORS();
133
            // Checks restricted access
134
            $this->checkRestrictedAccess($route);
135
            //Search action and execute
136
            $this->searchAction($route);
137
        } catch (AccessDeniedException $e) {
138
            Logger::log(_('Solicitamos credenciales de acceso a zona restringida'));
139
            if ('login' === Config::getInstance()->get('admin_login')) {
140
                return $this->redirectLogin($route);
141
            } else {
142
                return $this->sentAuthHeader();
143
            }
144
        } catch (RouterException $r) {
145
            if (FALSE !== preg_match('/\/$/', $route)) {
146
                if (preg_match('/admin/', $route)) {
147
                    $default = Config::getInstance()->get('admin_action');
148
                } else {
149
                    $default = Config::getInstance()->get('home_action');
150
                }
151
152
                return $this->execute($this->getRoute($default));
153
            }
154
        } catch (\Exception $e) {
155
            Logger::log($e->getMessage(), LOG_ERR);
156
            throw $e;
157
        }
158
159
        return $this->httpNotFound();
160
    }
161
162
    /**
163
     * Check CROS requests
164
     */
165
    private function checkCORS()
166
    {
167
        Logger::log('Checking CORS');
168
        $corsEnabled = Config::getInstance()->get('cors.enabled');
169
        $request = Request::getInstance();
170
        if (NULL !== $corsEnabled && null !== $request->getServer('HTTP_REFERER')) {
171
            if ($corsEnabled == '*' || preg_match($corsEnabled, $request->getServer('HTTP_REFERER'))) {
172
                if (!$this->headersSent) {
173
                    // TODO include this headers in Template class output method
174
                    header("Access-Control-Allow-Credentials: true");
175
                    header("Access-Control-Allow-Origin: *");
176
                    header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
177
                    header("Access-Control-Allow-Headers: Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Origin, X-Requested-With, Content-Type, Accept, Authorization, X-API-SEC-TOKEN, X-API-USER-TOKEN");
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 246 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...
178
                    $this->headersSent = true;
179
                }
180
                if (Request::getInstance()->getMethod() == 'OPTIONS') {
181
                    Logger::log('Returning OPTIONS header confirmation for CORS pre flight requests');
182
                    header("HTTP/1.1 200 OK");
183
                    exit();
184
                }
185
            }
186
        }
187
    }
188
189
    /**
190
     * Function that checks if the long of the patterns match
191
     * @param $routePattern
192
     * @param $path
193
     * @return bool
194
     */
195
    private function compareSlashes($routePattern, $path)
196
    {
197
        $pattern_sep = count(explode('/', $routePattern));
198
        if (preg_match('/\/$/', $routePattern)) {
199
            $pattern_sep--;
200
        }
201
        $path_sep = count(explode('/', $path));
202
        if (preg_match('/\/$/', $path)) {
203
            $path_sep--;
204
        }
205
        return abs($pattern_sep - $path_sep) < 1;
206
    }
207
208
    /**
209
     * Método que busca el componente que ejecuta la ruta
210
     *
211
     * @param string $route
212
     *
213
     * @throws \PSFS\base\exception\RouterException
214
     */
215
    protected function searchAction($route)
216
    {
217
        Logger::log('Searching action to execute');
218
        //Revisamos si tenemos la ruta registrada
219
        $parts = parse_url($route);
220
        $path = (array_key_exists('path', $parts)) ? $parts['path'] : $route;
221
        $httpRequest = Request::getInstance()->getMethod();
222
        foreach ($this->routing as $pattern => $action) {
223
            list($httpMethod, $routePattern) = $this->extractHttpRoute($pattern);
224
            $matched = $this->matchRoutePattern($routePattern, $path);
225
            if ($matched && ($httpMethod === "ALL" || $httpRequest === $httpMethod) && $this->compareSlashes($routePattern, $path)) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 133 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...
226
                $get = $this->extractComponents($route, $routePattern);
227
                /** @var $class \PSFS\base\types\Controller */
228
                $class = $this->getClassToCall($action);
229
                try {
230
                    $this->executeCachedRoute($route, $action, $class, $get);
231
                } catch (\Exception $e) {
232
                    Logger::log($e->getMessage(), LOG_ERR);
233
                    throw new RouterException($e->getMessage(), 404, $e);
234
                }
235
            }
236
        }
237
        throw new RouterException(_("Ruta no encontrada"));
238
    }
239
240
    /**
241
     * Método que manda las cabeceras de autenticación
242
     * @return string HTML
243
     */
244
    protected function sentAuthHeader()
245
    {
246
        return AdminServices::getInstance()->setAdminHeaders();
247
    }
248
249
    /**
250
     * Método que redirige a la pantalla web del login
251
     *
252
     * @param string $route
253
     *
254
     * @return string HTML
255
     */
256
    public function redirectLogin($route)
257
    {
258
        return Admin::staticAdminLogon($route);
259
    }
260
261
    /**
262
     * Método que chequea el acceso a una zona restringida
263
     *
264
     * @param string $route
265
     *
266
     * @throws AccessDeniedException
267
     */
268
    protected function checkRestrictedAccess($route)
269
    {
270
        Logger::log('Checking admin zone');
271
        //Chequeamos si entramos en el admin
272
        if (!Config::getInstance()->checkTryToSaveConfig()
273
            && (preg_match('/^\/(admin|setup\-admin)/i', $route) || NULL !== Config::getInstance()->get('restricted'))
274
        ) {
275
            if (!Security::getInstance()->checkAdmin()) {
276
                throw new AccessDeniedException();
277
            }
278
            Logger::log('Admin access granted');
279
        }
280
    }
281
282
    /**
283
     * Método que extrae de la url los parámetros REST
284
     *
285
     * @param string $route
286
     *
287
     * @param string $pattern
288
     *
289
     * @return array
290
     */
291
    protected function extractComponents($route, $pattern)
292
    {
293
        Logger::log('Extracting parts for the request to execute');
294
        $url = parse_url($route);
295
        $_route = explode("/", $url['path']);
296
        $_pattern = explode("/", $pattern);
297
        $get = array();
298
        if (!empty($_pattern)) foreach ($_pattern as $index => $component) {
299
            $_get = array();
300
            preg_match_all('/^\{(.*)\}$/i', $component, $_get);
301
            if (!empty($_get[1]) && isset($_route[$index])) {
302
                $get[array_pop($_get[1])] = $_route[$index];
303
            }
304
        }
305
306
        return $get;
307
    }
308
309
    /**
310
     * Method that gather all the routes in the project
311
     */
312 1
    private function generateRouting()
313
    {
314 1
        $base = SOURCE_DIR;
315 1
        $modules = realpath(CORE_DIR);
316 1
        $this->routing = $this->inspectDir($base, "PSFS", array());
317 1
        if (file_exists($modules)) {
318
            $module = "";
319
            if(file_exists($modules . DIRECTORY_SEPARATOR . 'module.json')) {
320
                $mod_cfg = json_decode(file_get_contents($modules . DIRECTORY_SEPARATOR . 'module.json'), true);
321
                $module = $mod_cfg['module'];
322
            }
323
            $this->routing = $this->inspectDir($modules, $module, $this->routing);
324
        }
325 1
        $this->cache->storeData(CONFIG_DIR . DIRECTORY_SEPARATOR . "domains.json", $this->domains, Cache::JSON, TRUE);
326 1
    }
327
328
    /**
329
     * Método que regenera el fichero de rutas
330
     * @throws ConfigException
331
     */
332 1
    public function hydrateRouting()
333
    {
334 1
        $this->generateRouting();
335 1
        $home = Config::getInstance()->get('home_action');
336 1
        if (NULL !== $home || $home !== '') {
337 1
            $home_params = NULL;
338 1
            foreach ($this->routing as $pattern => $params) {
339 1
                list($method, $route) = $this->extractHttpRoute($pattern);
340 1
                if (preg_match("/" . preg_quote($route, "/") . "$/i", "/" . $home)) {
341
                    $home_params = $params;
342
                }
343 1
            }
344 1
            if (NULL !== $home_params) {
345
                $this->routing['/'] = $home_params;
346
            }
347 1
        }
348 1
    }
349
350
    /**
351
     * Método que inspecciona los directorios en busca de clases que registren rutas
352
     *
353
     * @param string $origen
354
     * @param string $namespace
355
     * @param array $routing
356
     *
357
     * @return array
358
     * @throws ConfigException
359
     */
360 1
    private function inspectDir($origen, $namespace = 'PSFS', $routing)
361
    {
362 1
        $files = $this->finder->files()->in($origen)->path('/(controller|api)/i')->name("*.php");
363 1
        foreach ($files as $file) {
364 1
            $filename = str_replace("/", '\\', str_replace($origen, '', $file->getPathname()));
365 1
            $routing = $this->addRouting($namespace . str_replace('.php', '', $filename), $routing);
366 1
        }
367 1
        $this->finder = new Finder();
368
369 1
        return $routing;
370
    }
371
372
    /**
373
     * Checks that a namespace exists
374
     * @param string $namespace
375
     * @return bool
376
     */
377 1
    public static function exists($namespace) {
378 1
        return (class_exists($namespace) || interface_exists($namespace) || trait_exists($namespace));
379
    }
380
381
    /**
382
     * Método que añade nuevas rutas al array de referencia
383
     *
384
     * @param string $namespace
385
     * @param array $routing
386
     *
387
     * @return array
388
     * @throws ConfigException
389
     */
390 1
    private function addRouting($namespace, &$routing)
391
    {
392 1
        if (self::exists($namespace)) {
393 1
            $reflection = new \ReflectionClass($namespace);
394 1
            if (FALSE === $reflection->isAbstract() && FALSE === $reflection->isInterface()) {
395 1
                $this->extractDomain($reflection);
396 1
                $classComments = $reflection->getDocComment();
397 1
                preg_match('/@api\ (.*)\n/im', $classComments, $apiPath);
398 1
                $api = '';
399 1
                if (count($apiPath)) {
400
                    $api = array_key_exists(1, $apiPath) ? $apiPath[1] : $api;
401
                }
402 1
                foreach ($reflection->getMethods() as $method) {
403 1
                    if ($method->isPublic()) {
404 1
                        $docComments = $method->getDocComment();
405 1
                        preg_match('/@route\ (.*)\n/i', $docComments, $sr);
406 1
                        if (count($sr)) {
407 1
                            list($regex, $default, $params) = $this->extractReflectionParams($sr, $method);
408 1
                            if (strlen($api)) {
409
                                $regex = str_replace('{__API__}', $api, $regex);
410
                                $default = str_replace('{__API__}', $api, $default);
411
                            }
412 1
                            $httpMethod = $this->extractReflectionHttpMethod($docComments);
413 1
                            $visible = $this->extractReflectionVisibility($docComments);
414 1
                            $expiration = $this->extractReflectionCacheability($docComments);
415 1
                            $routing[$httpMethod . "#|#" . $regex] = array(
416 1
                                "class" => $namespace,
417 1
                                "method" => $method->getName(),
418 1
                                "params" => $params,
419 1
                                "default" => $default,
420 1
                                "visible" => $visible,
421 1
                                "http" => $httpMethod,
422 1
                                "cache" => $expiration,
423
                            );
424 1
                        }
425 1
                    }
426 1
                }
427 1
            }
428 1
        }
429
430 1
        return $routing;
431
    }
432
433
    /**
434
     * Método que extrae de la ReflectionClass los datos necesarios para componer los dominios en los templates
435
     *
436
     * @param \ReflectionClass $class
437
     *
438
     * @return Router
439
     * @throws ConfigException
440
     */
441 1
    protected function extractDomain(\ReflectionClass $class)
442
    {
443
        //Calculamos los dominios para las plantillas
444 1
        if ($class->hasConstant("DOMAIN")) {
445 1
            $domain = "@" . $class->getConstant("DOMAIN") . "/";
446 1
            $path = dirname($class->getFileName()) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
447 1
            $path = realpath($path) . DIRECTORY_SEPARATOR;
448 1
            $tpl_path = "templates";
449 1
            $public_path = "public";
450 1
            $model_path = "models";
451 1
            if (!preg_match("/ROOT/", $domain)) {
452
                $tpl_path = ucfirst($tpl_path);
453
                $public_path = ucfirst($public_path);
454
                $model_path = ucfirst($model_path);
455
            }
456 1
            if ($class->hasConstant("TPL")) {
457
                $tpl_path .= DIRECTORY_SEPARATOR . $class->getConstant("TPL");
458
            }
459 1
            $this->domains[$domain] = array(
460 1
                "template" => $path . $tpl_path,
461 1
                "model" => $path . $model_path,
462 1
                "public" => $path . $public_path,
463
            );
464 1
        }
465
466 1
        return $this;
467
    }
468
469
    /**
470
     * Método que genera las urls amigables para usar dentro del framework
471
     * @return Router
472
     */
473 1
    public function simpatize()
474
    {
475 1
        $translationFileName = "translations" . DIRECTORY_SEPARATOR . "routes_translations.php";
476 1
        $absoluteTranslationFileName = CACHE_DIR . DIRECTORY_SEPARATOR . $translationFileName;
477 1
        $this->generateSlugs($absoluteTranslationFileName);
478 1
        Config::createDir(CONFIG_DIR);
479 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...
480
481 1
        return $this;
482
    }
483
484
    /**
485
     * Método que devuelve el slug de un string dado
486
     *
487
     * @param string $text
488
     *
489
     * @return string
490
     */
491 1
    private function slugify($text)
492
    {
493
        // replace non letter or digits by -
494 1
        $text = preg_replace('~[^\\pL\d]+~u', '-', $text);
495
496
        // trim
497 1
        $text = trim($text, '-');
498
499
        // transliterate
500 1
        if (function_exists('iconv')) {
501 1
            $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
502 1
        }
503
504
        // lowercase
505 1
        $text = strtolower($text);
506
507
        // remove unwanted characters
508 1
        $text = preg_replace('~[^-\w]+~', '', $text);
509
510 1
        if (empty($text)) {
511
            return 'n-a';
512
        }
513
514 1
        return $text;
515
    }
516
517
    /**
518
     * Método que devuelve una ruta del framework
519
     *
520
     * @param string $slug
521
     * @param boolean $absolute
522
     * @param array $params
523
     *
524
     * @return string|null
525
     * @throws RouterException
526
     */
527
    public function getRoute($slug = '', $absolute = FALSE, $params = [])
528
    {
529
        if (strlen($slug) === 0) {
530
            return ($absolute) ? Request::getInstance()->getRootUrl() . '/' : '/';
531
        }
532
        if (NULL === $slug || !array_key_exists($slug, $this->slugs)) {
533
            throw new RouterException(_("No existe la ruta especificada"));
534
        }
535
        $url = ($absolute) ? Request::getInstance()->getRootUrl() . $this->slugs[$slug] : $this->slugs[$slug];
536
        if (!empty($params)) foreach ($params as $key => $value) {
537
            $url = str_replace("{" . $key . "}", $value, $url);
538
        } elseif (!empty($this->routing[$this->slugs[$slug]]["default"])) {
539
            $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...
540
        }
541
542
        return preg_replace('/(GET|POST|PUT|DELETE|ALL)\#\|\#/', '', $url);
543
    }
544
545
    /**
546
     * Método que devuelve las rutas de administración
547
     * @return array
548
     */
549 1
    public function getAdminRoutes()
550
    {
551 1
        $routes = array();
552 1
        foreach ($this->routing as $route => $params) {
553 1
            list($httpMethod, $routePattern) = $this->extractHttpRoute($route);
554 1
            if (preg_match('/^\/admin(\/|$)/', $routePattern)) {
555 1
                if (preg_match('/^\\\?PSFS/', $params["class"])) {
556 1
                    $profile = "superadmin";
557 1
                } else {
558
                    $profile = "admin";
559
                }
560 1
                if (!empty($params["default"]) && preg_match('/(GET|ALL)/i', $httpMethod)) {
561 1
                    $_profile = ($params["visible"]) ? $profile : 'adminhidden';
562 1
                    if (!array_key_exists($_profile, $routes)) {
563 1
                        $routes[$_profile] = array();
564 1
                    }
565 1
                    $routes[$_profile][] = $params["slug"];
566 1
                }
567 1
            }
568 1
        }
569 1
        if (array_key_exists("superadmin", $routes)) {
570 1
            asort($routes["superadmin"]);
571 1
        }
572 1
        if (array_key_exists("adminhidden", $routes)) {
573 1
            asort($routes["adminhidden"]);
574 1
        }
575 1
        if (array_key_exists('admin', $routes)) {
576
            asort($routes["admin"]);
577
        }
578 1
        return $routes;
579
    }
580
581
    /**
582
     * Método que devuelve le controlador del admin
583
     * @return Admin
584
     */
585
    public function getAdmin()
586
    {
587
        return Admin::getInstance();
588
    }
589
590
    /**
591
     * Método que extrae los dominios
592
     * @return array
593
     */
594
    public function getDomains()
595
    {
596
        return $this->domains ?: [];
597
    }
598
599
    /**
600
     * Método que extrae el controller a invocar
601
     *
602
     * @param string $action
603
     *
604
     * @return Object
605
     */
606
    protected function getClassToCall($action)
607
    {
608
        Logger::log('Getting class to call for executing the request action', LOG_DEBUG, $action);
0 ignored issues
show
Documentation introduced by
$action is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
609
        $actionClass = class_exists($action["class"]) ? $action["class"] : "\\" . $action["class"];
610
        $class = (method_exists($actionClass, "getInstance")) ? $actionClass::getInstance() : new $actionClass;
611
        return $class;
612
    }
613
614
    /**
615
     * Método que compara la ruta web con la guardada en la cache
616
     *
617
     * @param $routePattern
618
     * @param $path
619
     *
620
     * @return bool
621
     */
622
    protected function matchRoutePattern($routePattern, $path)
623
    {
624
        $expr = preg_replace('/\{([^}]+)\}/', '###', $routePattern);
625
        $expr = preg_quote($expr, '/');
626
        $expr = str_replace('###', '(.*)', $expr);
627
        $expr2 = preg_replace('/\(\.\*\)$/', '', $expr);
628
        $matched = preg_match('/^' . $expr . '\/?$/i', $path) || preg_match('/^' . $expr2 . '?$/i', $path);
629
        return $matched;
630
    }
631
632
    /**
633
     * @param $pattern
634
     *
635
     * @return array
636
     */
637 2
    protected function extractHttpRoute($pattern)
638
    {
639 2
        $httpMethod = "ALL";
640 2
        $routePattern = $pattern;
641 2
        if (FALSE !== strstr($pattern, "#|#")) {
642 2
            list($httpMethod, $routePattern) = explode("#|#", $pattern, 2);
643 2
        }
644
645 2
        return array(strtoupper($httpMethod), $routePattern);
646
    }
647
648
    /**
649
     * Método que extrae los parámetros de una función
650
     *
651
     * @param array $sr
652
     * @param \ReflectionMethod $method
653
     *
654
     * @return array
655
     */
656 1
    private function extractReflectionParams($sr, $method)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $sr. 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...
657
    {
658 1
        $regex = $sr[1] ?: $sr[0];
659 1
        $default = '';
660 1
        $params = array();
661 1
        $parameters = $method->getParameters();
662 1
        if (count($parameters) > 0) foreach ($parameters as $param) {
663 1
            if ($param->isOptional() && !is_array($param->getDefaultValue())) {
664 1
                $params[$param->getName()] = $param->getDefaultValue();
665 1
                $default = str_replace('{' . $param->getName() . '}', $param->getDefaultValue(), $regex);
666 1
            }
667 1
        } else $default = $regex;
668
669 1
        return array($regex, $default, $params);
670
    }
671
672
    /**
673
     * Método que extrae el método http
674
     *
675
     * @param string $docComments
676
     *
677
     * @return string
678
     */
679 1
    private function extractReflectionHttpMethod($docComments)
680
    {
681 1
        preg_match('/@(GET|POST|PUT|DELETE)\n/i', $docComments, $routeMethod);
682
683 1
        return (count($routeMethod) > 0) ? $routeMethod[1] : "ALL";
684
    }
685
686
    /**
687
     * Método que extrae la visibilidad de una ruta
688
     *
689
     * @param string $docComments
690
     *
691
     * @return bool
692
     */
693 1
    private function extractReflectionVisibility($docComments)
694
    {
695 1
        preg_match('/@visible\ (.*)\n/i', $docComments, $visible);
696 1
        return !(array_key_exists(1, $visible) && preg_match('/false/i', $visible[1]));
697
    }
698
699
    /**
700
     * Método que extrae el parámetro de caché
701
     *
702
     * @param string $docComments
703
     *
704
     * @return bool
705
     */
706 1
    private function extractReflectionCacheability($docComments)
707
    {
708 1
        preg_match('/@cache\ (.*)\n/i', $docComments, $cache);
709
710 1
        return (count($cache) > 0) ? $cache[1] : "0";
711
    }
712
713
    /**
714
     * Método que ejecuta una acción del framework y revisa si lo tenemos cacheado ya o no
715
     *
716
     * @param string $route
717
     * @param array|null $action
718
     * @param types\Controller $class
719
     * @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...
720
     */
721
    protected function executeCachedRoute($route, $action, $class, $params = NULL)
722
    {
723
        Logger::log('Executing route ' . $route);
724
        Security::getInstance()->setSessionKey("__CACHE__", $action);
725
        $cache = Cache::needCache();
726
        $execute = TRUE;
727
        if (FALSE !== $cache && Config::getInstance()->getDebugMode() === FALSE) {
728
            $cacheDataName = $this->cache->getRequestCacheHash();
729
            $cachedData = $this->cache->readFromCache("templates" . DIRECTORY_SEPARATOR . $cacheDataName,
730
                $cache, function () {});
0 ignored issues
show
Bug introduced by
It seems like $cache defined by \PSFS\base\Cache::needCache() on line 725 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...
731
            if (NULL !== $cachedData) {
732
                $headers = $this->cache->readFromCache("templates" . DIRECTORY_SEPARATOR . $cacheDataName . ".headers",
733
                    $cache, function () {}, Cache::JSON);
0 ignored issues
show
Bug introduced by
It seems like $cache defined by \PSFS\base\Cache::needCache() on line 725 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...
734
                Template::getInstance()->renderCache($cachedData, $headers);
735
                $execute = FALSE;
736
            }
737
        }
738
        if ($execute) {
739
            call_user_func_array(array($class, $action['method']), $params);
740
        }
741
    }
742
743
    /**
744
     * Parse slugs to create translations
745
     *
746
     * @param string $absoluteTranslationFileName
747
     */
748 1
    private function generateSlugs($absoluteTranslationFileName)
749
    {
750 1
        $translations = $this->generateTranslationsFile($absoluteTranslationFileName);
751 1
        foreach ($this->routing as $key => &$info) {
752 1
            $keyParts = $key;
753 1
            if (FALSE === strstr("#|#", $key)) {
754 1
                $keyParts = explode("#|#", $key);
755 1
                $keyParts = array_key_exists(1, $keyParts) ? $keyParts[1] : '';
756 1
            }
757 1
            $slug = $this->slugify($keyParts);
758 1
            if (NULL !== $slug && !array_key_exists($slug, $translations)) {
759 1
                $translations[$slug] = $key;
760 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...
761 1
            }
762 1
            $this->slugs[$slug] = $key;
763 1
            $info["slug"] = $slug;
764 1
        }
765 1
    }
766
767
    /**
768
     * Create translation file if not exists
769
     *
770
     * @param string $absoluteTranslationFileName
771
     *
772
     * @return array
773
     */
774 1
    private function generateTranslationsFile($absoluteTranslationFileName)
775
    {
776 1
        $translations = array();
777 1
        if (file_exists($absoluteTranslationFileName)) {
778
            include($absoluteTranslationFileName);
779
        } else {
780 1
            Cache::getInstance()->storeData($absoluteTranslationFileName, "<?php \$translations = array();\n", Cache::TEXT, TRUE);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 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...
781
        }
782
783 1
        return $translations;
784
    }
785
786
}
787