Completed
Push — master ( 8a02a1...c877ec )
by Fran
04:56 queued 14s
created

Template::setStatus()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 25
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 7
eloc 21
c 5
b 0
f 0
nc 7
nop 1
dl 0
loc 25
rs 6.7272
1
<?php
2
3
namespace PSFS\base;
4
5
6
use PSFS\base\config\Config;
7
use PSFS\base\extension\AssetsTokenParser;
8
use PSFS\base\extension\TemplateFunctions;
9
use PSFS\base\types\SingletonTrait;
10
use PSFS\Dispatcher;
11
12
13
class Template
14
{
15
16
    use SingletonTrait;
17
    /**
18
     * @var \Twig_Environment tpl
19
     */
20
    protected $tpl;
21
    protected $filters = array();
22
23
    protected $debug = false;
24
    protected $public_zone = true;
25
    private $status_code = 200;
26
27
    /**
28
     * @var \PSFS\base\Security $security
29
     */
30
    protected $security;
31
32
    /**
33
     * @var \PSFS\base\Cache $cache
34
     */
35
    protected $cache;
36
37
    /**
38
     * Constructor por defecto
39
     */
40
    public function __construct()
41
    {
42
        $this->setup();
43
        $this->addTemplateFunctions();
44
        $this->addTemplateTokens();
45
        $this->optimizeTemplates();
46
    }
47
48
    /**
49
     * Método que devuelve el loader del Template
50
     * @return \Twig_LoaderInterface
51
     */
52
    public function getLoader()
53
    {
54
        return $this->tpl->getLoader();
55
    }
56
57
    /**
58
     * Método que activa la zona pública
59
     * @param bool $public
60
     *
61
     * @return Template
62
     */
63
    public function setPublicZone($public = true)
64
    {
65
        $this->public_zone = $public;
66
        return $this;
67
    }
68
69
    /**
70
     * Método que establece un header de http status code
71
     * @param string $status
72
     *
73
     * @return Template
74
     */
75
    public function setStatus($status = null)
76
    {
77
        switch ($status) {
78
            //TODO implement all status codes
79
            case '500':
80
                $this->status_code = "HTTP/1.0 500 Internal Server Error";
0 ignored issues
show
Documentation Bug introduced by
The property $status_code was declared of type integer, but 'HTTP/1.0 500 Internal Server Error' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
81
                break;
82
            case '404':
83
                $this->status_code = "HTTP/1.0 404 Not Found";
0 ignored issues
show
Documentation Bug introduced by
The property $status_code was declared of type integer, but 'HTTP/1.0 404 Not Found' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
84
                break;
85
            case '403':
86
                $this->status_code = "HTTP/1.0 403 Forbidden";
0 ignored issues
show
Documentation Bug introduced by
The property $status_code was declared of type integer, but 'HTTP/1.0 403 Forbidden' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
87
                break;
88
            case '402':
89
                $this->status_code = "HTTP/1.0 402 Payment Required";
0 ignored issues
show
Documentation Bug introduced by
The property $status_code was declared of type integer, but 'HTTP/1.0 402 Payment Required' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
90
                break;
91
            case '401':
92
                $this->status_code = "HTTP/1.0 401 Unauthorized";
0 ignored issues
show
Documentation Bug introduced by
The property $status_code was declared of type integer, but 'HTTP/1.0 401 Unauthorized' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
93
                break;
94
            case '400':
95
                $this->status_code = "HTTP/1.0 400 Bad Request";
0 ignored issues
show
Documentation Bug introduced by
The property $status_code was declared of type integer, but 'HTTP/1.0 400 Bad Request' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
96
                break;
97
        }
98
        return $this;
99
    }
100
101
    /**
102
     * Método que procesa la plantilla
103
     *
104
     * @param string $tpl
105
     * @param array $vars
106
     * @param array $cookies
107
     *
108
     * @return string HTML
109
     */
110
    public function render($tpl, array $vars = array(), array $cookies = array())
111
    {
112
        Logger::log('Start render response');
113
        $vars = $this->setDebugHeaders($vars);
114
        $output = $this->dump($tpl, $vars);
115
116
        return $this->output($output, 'text/html', $cookies);
117
    }
118
119
    /**
120
     * Servicio que establece las cabeceras de la respuesta
121
     * @param string $contentType
122
     * @param array $cookies
123
     */
124
    private function setReponseHeaders($contentType = 'text/html', array $cookies = array())
125
    {
126
        $config = Config::getInstance();
127
        $powered = $config->get("poweredBy");
128
        if (empty($powered)) {
129
            $powered = "@c15k0";
130
        }
131
        header("X-Powered-By: $powered");
132
        $this->setStatusHeader();
133
        $this->setAuthHeaders();
134
        $this->setCookieHeaders($cookies);
135
        header('Content-type: ' . $contentType);
136
137
    }
138
139
    /**
140
     * Servicio que devuelve el output
141
     * @param string $output
142
     * @param string $contentType
143
     * @param array $cookies
144
     * @return string HTML
145
     */
146
    public function output($output = '', $contentType = 'text/html', array $cookies = array())
147
    {
148
        Logger::log('Start output response');
149
        ob_start();
150
        $this->setReponseHeaders($contentType, $cookies);
151
        header('Content-length: ' . strlen($output));
152
153
        $cache = Cache::needCache();
154
        if (false !== $cache && $this->status_code === 200 && $this->debug === false) {
155
            Logger::log('Saving output response into cache');
156
            $cacheName = $this->cache->getRequestCacheHash();
157
            $this->cache->storeData("json" . DIRECTORY_SEPARATOR . $cacheName, $output);
158
            $this->cache->storeData("json" . DIRECTORY_SEPARATOR . $cacheName . ".headers", headers_list(), Cache::JSON);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 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...
159
        }
160
        echo $output;
161
162
        ob_flush();
163
        ob_end_clean();
164
        Logger::log('End output response');
165
        $this->closeRender();
166
    }
167
168
    /**
169
     * Método que cierra y limpia los buffers de salida
170
     */
171
    public function closeRender()
172
    {
173
        Logger::log('Close template render');
174
        $this->security->setSessionKey("lastRequest", array(
175
            "url" => Request::getInstance()->getRootUrl() . Request::requestUri(),
176
            "ts" => microtime(true),
177
        ));
178
        $this->security->updateSession();
179
        exit;
180
    }
181
182
    /**
183
     * Método que devuelve los datos cacheados con las cabeceras que tenía por entonces
184
     * @param string $data
185
     * @param string|null $headers
0 ignored issues
show
Documentation introduced by
Should the type for parameter $headers not be string|null|array? 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...
186
     */
187
    public function renderCache($data, $headers = array())
188
    {
189
        ob_start();
190
        for ($i = 0, $ct = count($headers); $i < $ct; $i++) {
191
            header($headers[$i]);
192
        }
193
        echo $data;
194
        ob_flush();
195
        ob_end_clean();
196
        $this->closeRender();
197
    }
198
199
    /**
200
     * Método que añade una nueva ruta al path de Twig
201
     * @param $path
202
     * @param $domain
203
     *
204
     * @return Template
205
     */
206
    public function addPath($path, $domain = '')
207
    {
208
        $this->tpl->getLoader()->addPath($path, $domain);
209
        return $this;
210
    }
211
212
    /**
213
     * Método que devuelve el contenido de una plantilla
214
     * @param string $tpl
215
     * @param array $vars
216
     * @return string
217
     */
218
    public function dump($tpl, array $vars = array())
219
    {
220
        $vars["__user__"] = $this->security->getUser();
221
        $vars["__admin__"] = $this->security->getAdmin();
222
        $vars["__profiles__"] = Security::getCleanProfiles();
223
        $vars["__flash__"] = Security::getInstance()->getFlashes();
224
        $dump = '';
225
        try {
226
            $dump = $this->tpl->render($tpl, $vars);
227
        } catch (\Exception $e) {
228
            echo $e->getMessage() . "<pre>" . $e->getTraceAsString() . "</pre>";
229
        }
230
        return $dump;
231
    }
232
233
    /**
234
     * Método que añade una función al motor de plantillas
235
     * @param string $templateFunction
236
     * @param $functionName
237
     *
238
     * @return Template
239
     */
240
    protected function addTemplateFunction($templateFunction, $functionName)
241
    {
242
        $function = new \Twig_SimpleFunction($templateFunction, $functionName);
243
        $this->tpl->addFunction($function);
244
        return $this;
245
    }
246
247
    /**
248
     * Funcion Twig para los assets en las plantillas
249
     * @return Template
250
     */
251
    private function addAssetFunction()
252
    {
253
        return $this->addTemplateFunction("asset", TemplateFunctions::ASSETS_FUNCTION);
254
    }
255
256
    /**
257
     * Función que pinta un formulario
258
     * @return Template
259
     */
260
    private function addFormsFunction()
261
    {
262
        return $this->addTemplateFunction("form", TemplateFunctions::FORM_FUNCTION);
263
    }
264
265
    /**
266
     * Función que pinta un campo de un formulario
267
     * @return Template
268
     */
269
    private function addFormWidgetFunction()
270
    {
271
        return $this->addTemplateFunction("form_widget", TemplateFunctions::WIDGET_FUNCTION);
272
    }
273
274
    /**
275
     * Función que pinta un botón de un formulario
276
     * @return Template
277
     */
278
    private function addFormButtonFunction()
279
    {
280
        return $this->addTemplateFunction("form_button", TemplateFunctions::BUTTON_FUNCTION);
281
    }
282
283
    /**
284
     * Método que devuelve un parámetro de configuración en la plantilla
285
     * @return Template
286
     */
287
    private function addConfigFunction()
288
    {
289
        return $this->addTemplateFunction("get_config", TemplateFunctions::CONFIG_FUNCTION);
290
    }
291
292
    /**
293
     * Método que añade la función path a Twig
294
     * @return Template
295
     */
296
    private function addRouteFunction()
297
    {
298
        return $this->addTemplateFunction("path", TemplateFunctions::ROUTE_FUNCTION);
299
    }
300
301
    /**
302
     * Método que copia directamente el recurso solicitado a la carpeta pública
303
     * @return Template
304
     */
305
    private function addResourceFunction()
306
    {
307
        return $this->addTemplateFunction("resource", TemplateFunctions::RESOURCE_FUNCTION);
308
    }
309
310
    /**
311
     * @return Template
312
     */
313
    private function addSessionFunction()
314
    {
315
        return $this->addTemplateFunction("session", TemplateFunctions::SESSION_FUNCTION);
316
    }
317
318
    /**
319
     * @return Template
320
     */
321
    private function addExistsFlashFunction()
322
    {
323
        return $this->addTemplateFunction("existsFlash", TemplateFunctions::EXISTS_FLASH_FUNCTION);
324
    }
325
326
    /**
327
     * @return Template
328
     */
329
    private function addGetFlashFunction()
330
    {
331
        return $this->addTemplateFunction("getFlash", TemplateFunctions::GET_FLASH_FUNCTION);
332
    }
333
334
    /**
335
     * Servicio que regenera todas las plantillas
336
     * @return array
337
     */
338
    public function regenerateTemplates()
339
    {
340
        $this->generateTemplatesCache();
341
        $domains = Cache::getInstance()->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 127 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...
342
        $translations = [];
343
        if (is_array($domains)) {
344
            $translations = $this->parsePathTranslations($domains);
345
        }
346
        $translations[] = _("Plantillas regeneradas correctamente");
347
        return $translations;
348
    }
349
350
    /**
351
     * @param $tplDir
352
     * @param string $domain
353
     *
354
     * @return mixed
355
     */
356
    protected function generateTemplate($tplDir, $domain = '')
357
    {
358
        $templatesDir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tplDir), \RecursiveIteratorIterator::LEAVES_ONLY);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 138 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...
359
        foreach ($templatesDir as $file) {
360
            // force compilation
361
            if ($file->isFile()) {
362
                try {
363
                    $this->tpl->loadTemplate(str_replace($tplDir . '/', '', $file));
364
                } catch (\Exception $e) {
365
                    Logger::log($e->getMessage(), LOG_ERR);
366
                }
367
            }
368
        }
369
        return str_replace("%d", $domain, str_replace("%s", $tplDir, _("Generando plantillas en path '%s' para el dominio '%d'")));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 131 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...
370
    }
371
372
    /**
373
     * Método que extrae el path de un string
374
     * @param $path
375
     *
376
     * @return string
377
     */
378
    public static function extractPath($path)
379
    {
380
        $explodePath = explode(DIRECTORY_SEPARATOR, $path);
381
        $realPath = array();
382
        for ($i = 0, $parts = count($explodePath) - 1; $i < $parts; $i++) {
383
            $realPath[] = $explodePath[$i];
384
        }
385
        return implode(DIRECTORY_SEPARATOR, $realPath);
386
    }
387
388
    /**
389
     * Método que devuelve los dominios de una plataforma
390
     * @param bool $append
391
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string|null? Also, consider making the array more specific, something like array<String>, or String[].

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.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
392
     */
393
    static public function getDomains($append = false)
394
    {
395
        $domains = Router::getInstance()->getDomains();
396
        if ($append) {
397
            foreach ($domains as &$domain) {
0 ignored issues
show
Bug introduced by
The expression $domains of type array|string|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
398
                foreach ($domain as &$path) {
399
                    $path .= DIRECTORY_SEPARATOR;
400
                }
401
            }
402
        }
403
        return $domains;
404
    }
405
406
    /**
407
     * @param $cookies
408
     */
409
    protected function setCookieHeaders($cookies)
410
    {
411
        if (!empty($cookies) && is_array($cookies)) {
412
            foreach ($cookies as $cookie) {
413
                setcookie($cookie["name"],
414
                    $cookie["value"],
415
                    (array_key_exists('expire', $cookie)) ? $cookie["expire"] : NULL,
416
                    (array_key_exists('path', $cookie)) ? $cookie["path"] : "/",
417
                    (array_key_exists('domain', $cookie)) ? $cookie["domain"] : Request::getInstance()->getRootUrl(FALSE),
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...
418
                    (array_key_exists('secure', $cookie)) ? $cookie["secure"] : FALSE,
419
                    (array_key_exists('http', $cookie)) ? $cookie["http"] : FALSE
420
                );
421
            }
422
        }
423
    }
424
425
    /**
426
     * Método que inyecta las cabeceras necesarias para la autenticación
427
     */
428
    protected function setAuthHeaders()
429
    {
430
        if ($this->public_zone) {
431
            unset($_SERVER["PHP_AUTH_USER"]);
432
            unset($_SERVER["PHP_AUTH_PW"]);
433
            header_remove("Authorization");
434
        } else {
435
            header('Authorization:');
436
        }
437
    }
438
439
    /**
440
     * Método que establece el status code
441
     */
442
    protected function setStatusHeader()
443
    {
444
        if (NULL !== $this->status_code) {
445
            header($this->status_code);
446
        }
447
    }
448
449
    /**
450
     * Método que mete en las variables de las plantillas las cabeceras de debug
451
     * @param array $vars
452
     *
453
     * @return array
454
     */
455
    protected function setDebugHeaders(array $vars)
456
    {
457
        if ($this->debug) {
458
            Logger::log('Adding debug headers to render response');
459
            $vars["__DEBUG__"]["includes"] = get_included_files();
460
            $vars["__DEBUG__"]["trace"] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
461
            header('X-PSFS-DEBUG-TS: ' . Dispatcher::getInstance()->getTs() . ' s');
462
            header('X-PSFS-DEBUG-MEM: ' . Dispatcher::getInstance()->getMem('MBytes') . ' MBytes');
463
            header('X-PSFS-DEBUG-FILES: ' . count(get_included_files()) . ' files opened');
464
        }
465
466
        return $vars;
467
    }
468
469
    /**
470
     * Método que añade todas las funciones de las plantillas
471
     */
472
    private function addTemplateFunctions()
473
    {
474
        //Asignamos las funciones especiales
475
        $this->addAssetFunction()
476
            ->addFormsFunction()
477
            ->addFormWidgetFunction()
478
            ->addFormButtonFunction()
479
            ->addConfigFunction()
480
            ->addRouteFunction()
481
            ->addSessionFunction()
482
            ->addExistsFlashFunction()
483
            ->addGetFlashFunction()
484
            ->addResourceFunction();
485
    }
486
487
    /**
488
     * Método que devuelve el motod de plantillas
489
     * @return \Twig_Environment
490
     */
491
    public function getTemplateEngine()
492
    {
493
        return $this->tpl;
494
    }
495
496
    /**
497
     * Método que inicializa el motor de plantillas
498
     */
499
    private function setup()
500
    {
501
        $this->debug = Config::getInstance()->getDebugMode() ?: FALSE;
502
        $this->security = Security::getInstance();
503
        $this->cache = Cache::getInstance();
504
        $loader = new \Twig_Loader_Filesystem(Config::getInstance()->getTemplatePath());
505
        $this->tpl = new \Twig_Environment($loader, array(
506
            'cache' => Config::getInstance()->getCachePath(),
507
            'debug' => (bool)$this->debug,
508
            'auto_reload' => TRUE,
509
        ));
510
    }
511
512
    /**
513
     * Método que inyecta los parseadores
514
     */
515
    private function addTemplateTokens()
516
    {
517
        //Añadimos las extensiones de los tags
518
        $this->tpl->addTokenParser(new AssetsTokenParser("css"));
519
        $this->tpl->addTokenParser(new AssetsTokenParser("js"));
520
    }
521
522
    /**
523
     * Método que inyecta las optimizaciones al motor de la plantilla
524
     */
525
    private function optimizeTemplates()
526
    {
527
        //Optimizamos
528
        $this->tpl->addExtension(new \Twig_Extensions_Extension_I18n());
529
    }
530
531
    /**
532
     * Method that extract all path tag for extracting translations
533
     * @param array $domains
534
     *
535
     * @return array
536
     */
537
    private function parsePathTranslations($domains)
538
    {
539
        $translations = array();
540
        if (!empty($domains)) {
541
            foreach ($domains as $domain => $paths) {
542
                if (strlen($domain) && array_key_exists("template", $paths)) {
543
                    $this->addPath($paths["template"], $domain);
544
                    $translations[] = $this->generateTemplate($paths["template"], $domain);
545
                }
546
            }
547
        }
548
549
        return $translations;
550
    }
551
552
    /**
553
     * Method that generate all template caches
554
     */
555
    private function generateTemplatesCache()
556
    {
557
        /** @var \Twig_Loader_Filesystem $loader */
558
        $loader = $this->tpl->getLoader();
559
        $availablePaths = $loader->getPaths();
560
        if (!empty($availablePaths)) {
561
            foreach ($availablePaths as $path) {
562
                $this->generateTemplate($path);
563
            }
564
        }
565
    }
566
}
567