Passed
Push — master ( 1c0893...f7ffcc )
by Fran
03:22
created

Template::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 7
ccs 6
cts 6
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
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
    use SingletonTrait;
16
    const STATUS_OK = 'HTTP/1.0 200 OK';
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 = Template::STATUS_OK;
26
27
    /**
28
     * @var \PSFS\base\Cache $cache
29
     */
30
    protected $cache;
31
32
    /**
33
     * Constructor por defecto
34
     */
35 1
    public function __construct()
36
    {
37 1
        $this->setup();
38 1
        $this->addTemplateFunctions();
39 1
        $this->addTemplateTokens();
40 1
        $this->optimizeTemplates();
41 1
    }
42
43
    /**
44
     * Método que devuelve el loader del Template
45
     * @return \Twig_LoaderInterface
46
     */
47
    public function getLoader()
48
    {
49
        return $this->tpl->getLoader();
50
    }
51
52
    /**
53
     * Método que activa la zona pública
54
     * @param bool $public
55
     *
56
     * @return Template
57
     */
58
    public function setPublicZone($public = true)
59
    {
60
        $this->public_zone = $public;
61
        return $this;
62
    }
63
64
    /**
65
     * Método que establece un header de http status code
66
     * @param string $status
67
     *
68
     * @return Template
69
     */
70
    public function setStatus($status = null)
71
    {
72
        switch ($status) {
73
            //TODO implement all status codes
74
            case '500':
75
                $this->status_code = "HTTP/1.0 500 Internal Server Error";
76
                break;
77
            case '404':
78
                $this->status_code = "HTTP/1.0 404 Not Found";
79
                break;
80
            case '403':
81
                $this->status_code = "HTTP/1.0 403 Forbidden";
82
                break;
83
            case '402':
84
                $this->status_code = "HTTP/1.0 402 Payment Required";
85
                break;
86
            case '401':
87
                $this->status_code = "HTTP/1.0 401 Unauthorized";
88
                break;
89
            case '400':
90
                $this->status_code = "HTTP/1.0 400 Bad Request";
91
                break;
92
        }
93
        return $this;
94
    }
95
96
    /**
97
     * Método que procesa la plantilla
98
     *
99
     * @param string $tpl
100
     * @param array $vars
101
     * @param array $cookies
102
     *
103
     * @return string HTML
104
     */
105
    public function render($tpl, array $vars = array(), array $cookies = array())
106
    {
107
        Logger::log('Start render response');
108
        $vars = $this->setDebugHeaders($vars);
109
        $output = $this->dump($tpl, $vars);
110
111
        return $this->output($output, 'text/html', $cookies);
112
    }
113
114
    /**
115
     * Servicio que establece las cabeceras de la respuesta
116
     * @param string $contentType
117
     * @param array $cookies
118
     */
119 1
    private function setReponseHeaders($contentType = 'text/html', array $cookies = array())
120
    {
121
        $config = Config::getInstance();
122
        $powered = $config->get("poweredBy");
123
        if (empty($powered)) {
124
            $powered = "@c15k0";
125
        }
126 1
        header("X-Powered-By: $powered");
127 1
        $this->setStatusHeader();
128 1
        $this->setAuthHeaders();
129 1
        $this->setCookieHeaders($cookies);
130
        header('Content-type: ' . $contentType);
131
132
    }
133
134
    /**
135
     * Servicio que devuelve el output
136
     * @param string $output
137
     * @param string $contentType
138
     * @param array $cookies
139
     * @return string HTML
140
     */
141
    public function output($output = '', $contentType = 'text/html', array $cookies = array())
142
    {
143
        Logger::log('Start output response');
144
        ob_start();
145
        $this->setReponseHeaders($contentType, $cookies);
146
        header('Content-length: ' . strlen($output));
147
148
        $cache = Cache::needCache();
149
        if (false !== $cache && $this->status_code === Template::STATUS_OK && $this->debug === false) {
150
            Logger::log('Saving output response into cache');
151
            $cacheName = $this->cache->getRequestCacheHash();
152
            $this->cache->storeData("json" . DIRECTORY_SEPARATOR . $cacheName, $output);
153
            $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...
154
        }
155
        echo $output;
156
157
        ob_flush();
158
        ob_end_clean();
159
        Logger::log('End output response');
160
        $this->closeRender();
161
    }
162
163
    /**
164
     * Método que cierra y limpia los buffers de salida
165
     */
166
    public function closeRender()
167
    {
168
        Logger::log('Close template render');
169
        Security::getInstance()->setSessionKey("lastRequest", array(
170
            "url" => Request::getInstance()->getRootUrl() . Request::requestUri(),
171
            "ts" => microtime(true),
172
        ));
173
        Security::getInstance()->updateSession();
174
        Logger::log('End request: ' . Request::requestUri(), LOG_INFO);
175
        exit;
176
    }
177
178
    /**
179
     * Método que devuelve los datos cacheados con las cabeceras que tenía por entonces
180
     * @param string $data
181
     * @param array $headers
182
     */
183
    public function renderCache($data, $headers = array())
184
    {
185
        ob_start();
186
        for ($i = 0, $ct = count($headers); $i < $ct; $i++) {
187
            header($headers[$i]);
188
        }
189
        echo $data;
190
        ob_flush();
191
        ob_end_clean();
192
        $this->closeRender();
193
    }
194
195
    /**
196
     * Método que añade una nueva ruta al path de Twig
197
     * @param $path
198
     * @param $domain
199
     *
200
     * @return Template
201
     */
202 1
    public function addPath($path, $domain = '')
203
    {
204 1
        $this->tpl->getLoader()->addPath($path, $domain);
205 1
        return $this;
206
    }
207
208
    /**
209
     * Método que devuelve el contenido de una plantilla
210
     * @param string $tpl
211
     * @param array $vars
212
     * @return string
213
     */
214
    public function dump($tpl, array $vars = array())
215
    {
216
        $vars["__user__"] = Security::getInstance()->getUser();
217
        $vars["__admin__"] = Security::getInstance()->getAdmin();
218
        $vars["__profiles__"] = Security::getCleanProfiles();
219
        $vars["__flash__"] = Security::getInstance()->getFlashes();
220
        $dump = '';
221
        try {
222
            $dump = $this->tpl->render($tpl, $vars);
223
        } catch (\Exception $e) {
224
            Logger::log($e->getMessage(), LOG_ERR);
225
        }
226
        return $dump;
227
    }
228
229
    /**
230
     * Método que añade una función al motor de plantillas
231
     * @param string $templateFunction
232
     * @param $functionName
233
     *
234
     * @return Template
235
     */
236 1
    protected function addTemplateFunction($templateFunction, $functionName)
237
    {
238 1
        $function = new \Twig_SimpleFunction($templateFunction, $functionName);
239 1
        $this->tpl->addFunction($function);
240 1
        return $this;
241
    }
242
243
    /**
244
     * Funcion Twig para los assets en las plantillas
245
     * @return Template
246
     */
247 1
    private function addAssetFunction()
248
    {
249 1
        return $this->addTemplateFunction("asset", TemplateFunctions::ASSETS_FUNCTION);
250
    }
251
252
    /**
253
     * Función que pinta un formulario
254
     * @return Template
255
     */
256 1
    private function addFormsFunction()
257
    {
258 1
        return $this->addTemplateFunction("form", TemplateFunctions::FORM_FUNCTION);
259
    }
260
261
    /**
262
     * Función que pinta un campo de un formulario
263
     * @return Template
264
     */
265 1
    private function addFormWidgetFunction()
266
    {
267 1
        return $this->addTemplateFunction("form_widget", TemplateFunctions::WIDGET_FUNCTION);
268
    }
269
270
    /**
271
     * Función que pinta un botón de un formulario
272
     * @return Template
273
     */
274 1
    private function addFormButtonFunction()
275
    {
276 1
        return $this->addTemplateFunction("form_button", TemplateFunctions::BUTTON_FUNCTION);
277
    }
278
279
    /**
280
     * Método que devuelve un parámetro de configuración en la plantilla
281
     * @return Template
282
     */
283 1
    private function addConfigFunction()
284
    {
285 1
        return $this->addTemplateFunction("get_config", TemplateFunctions::CONFIG_FUNCTION);
286
    }
287
288
    /**
289
     * Método que añade la función path a Twig
290
     * @return Template
291
     */
292 1
    private function addRouteFunction()
293
    {
294 1
        return $this->addTemplateFunction("path", TemplateFunctions::ROUTE_FUNCTION);
295
    }
296
297
    /**
298
     * Método que copia directamente el recurso solicitado a la carpeta pública
299
     * @return Template
300
     */
301 1
    private function addResourceFunction()
302
    {
303 1
        return $this->addTemplateFunction("resource", TemplateFunctions::RESOURCE_FUNCTION);
304
    }
305
306
    /**
307
     * @return Template
308
     */
309 1
    private function addSessionFunction()
310
    {
311 1
        return $this->addTemplateFunction("session", TemplateFunctions::SESSION_FUNCTION);
312
    }
313
314
    /**
315
     * @return Template
316
     */
317 1
    private function addExistsFlashFunction()
318
    {
319 1
        return $this->addTemplateFunction("existsFlash", TemplateFunctions::EXISTS_FLASH_FUNCTION);
320
    }
321
322
    /**
323
     * @return Template
324
     */
325 1
    private function addGetFlashFunction()
326
    {
327 1
        return $this->addTemplateFunction("getFlash", TemplateFunctions::GET_FLASH_FUNCTION);
328
    }
329
330
    /**
331
     * Servicio que regenera todas las plantillas
332
     * @return array
333
     */
334
    public function regenerateTemplates()
335
    {
336
        $this->generateTemplatesCache();
337
        $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...
338
        $translations = [];
339
        if (is_array($domains)) {
340
            $translations = $this->parsePathTranslations($domains);
341
        }
342
        $translations[] = _("Plantillas regeneradas correctamente");
343
        return $translations;
344
    }
345
346
    /**
347
     * @param $tplDir
348
     * @param string $domain
349
     *
350
     * @return mixed
351
     */
352
    protected function generateTemplate($tplDir, $domain = '')
353
    {
354
        $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...
355
        foreach ($templatesDir as $file) {
356
            // force compilation
357
            if ($file->isFile()) {
358
                try {
359
                    $this->tpl->loadTemplate(str_replace($tplDir . '/', '', $file));
360
                } catch (\Exception $e) {
361
                    Logger::log($e->getMessage(), LOG_ERR);
362
                    throw $e;
363
                }
364
            }
365
        }
366
        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...
367
    }
368
369
    /**
370
     * Método que extrae el path de un string
371
     * @param $path
372
     *
373
     * @return string
374
     */
375
    public static function extractPath($path)
376
    {
377
        $explodePath = explode(DIRECTORY_SEPARATOR, $path);
378
        $realPath = array();
379
        for ($i = 0, $parts = count($explodePath) - 1; $i < $parts; $i++) {
380
            $realPath[] = $explodePath[$i];
381
        }
382
        return implode(DIRECTORY_SEPARATOR, $realPath);
383
    }
384
385
    /**
386
     * Método que devuelve los dominios de una plataforma
387
     * @param bool $append
388
     * @return array
389
     */
390
    static public function getDomains($append = false)
391
    {
392
        $domains = Router::getInstance()->getDomains();
393
        if ($append) {
394
            foreach ($domains as &$domain) {
395
                foreach ($domain as &$path) {
396
                    $path .= DIRECTORY_SEPARATOR;
397
                }
398
            }
399
        }
400
        return $domains;
401
    }
402
403
    /**
404
     * @param $cookies
405
     */
406
    protected function setCookieHeaders($cookies)
407
    {
408
        if (!empty($cookies) && is_array($cookies)) {
409
            foreach ($cookies as $cookie) {
410
                setcookie($cookie["name"],
411
                    $cookie["value"],
412
                    (array_key_exists('expire', $cookie)) ? $cookie["expire"] : NULL,
413
                    (array_key_exists('path', $cookie)) ? $cookie["path"] : "/",
414
                    (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...
415
                    (array_key_exists('secure', $cookie)) ? $cookie["secure"] : FALSE,
416
                    (array_key_exists('http', $cookie)) ? $cookie["http"] : FALSE
417
                );
418
            }
419
        }
420
    }
421
422
    /**
423
     * Método que inyecta las cabeceras necesarias para la autenticación
424
     */
425
    protected function setAuthHeaders()
426
    {
427
        if ($this->public_zone) {
428
            unset($_SERVER["PHP_AUTH_USER"]);
429
            unset($_SERVER["PHP_AUTH_PW"]);
430
            header_remove("Authorization");
431
        } else {
432
            header('Authorization:');
433
        }
434
    }
435
436
    /**
437
     * Método que establece el status code
438
     */
439
    protected function setStatusHeader()
440
    {
441
        if (NULL !== $this->status_code) {
442
            header($this->status_code);
443
        }
444
    }
445
446
    /**
447
     * Método que mete en las variables de las plantillas las cabeceras de debug
448
     * @param array $vars
449
     *
450
     * @return array
451
     */
452
    protected function setDebugHeaders(array $vars)
453
    {
454
        if ($this->debug) {
455
            Logger::log('Adding debug headers to render response');
456
            $vars["__DEBUG__"]["includes"] = get_included_files();
457
            $vars["__DEBUG__"]["trace"] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
458
            header('X-PSFS-DEBUG-TS: ' . Dispatcher::getInstance()->getTs() . ' s');
459
            header('X-PSFS-DEBUG-MEM: ' . Dispatcher::getInstance()->getMem('MBytes') . ' MBytes');
460
            header('X-PSFS-DEBUG-FILES: ' . count(get_included_files()) . ' files opened');
461
        }
462
463
        return $vars;
464
    }
465
466
    /**
467
     * Método que añade todas las funciones de las plantillas
468
     */
469 1
    private function addTemplateFunctions()
470
    {
471
        //Asignamos las funciones especiales
472 1
        $this->addAssetFunction()
473 1
            ->addFormsFunction()
474 1
            ->addFormWidgetFunction()
475 1
            ->addFormButtonFunction()
476 1
            ->addConfigFunction()
477 1
            ->addRouteFunction()
478 1
            ->addSessionFunction()
479 1
            ->addExistsFlashFunction()
480 1
            ->addGetFlashFunction()
481 1
            ->addResourceFunction();
482 1
    }
483
484
    /**
485
     * Método que devuelve el motod de plantillas
486
     * @return \Twig_Environment
487
     */
488
    public function getTemplateEngine()
489
    {
490
        return $this->tpl;
491
    }
492
493
    /**
494
     * Método que inicializa el motor de plantillas
495
     */
496 1
    private function setup()
497
    {
498 1
        $this->debug = Config::getInstance()->getDebugMode() ?: FALSE;
499 1
        $this->cache = Cache::getInstance();
500 1
        $loader = new \Twig_Loader_Filesystem(Config::getInstance()->getTemplatePath());
501 1
        $this->tpl = new \Twig_Environment($loader, array(
502 1
            'cache' => Config::getInstance()->getCachePath() . DIRECTORY_SEPARATOR . 'twig',
503 1
            'debug' => (bool)$this->debug,
504 1
            'auto_reload' => TRUE,
505 1
        ));
506 1
    }
507
508
    /**
509
     * Método que inyecta los parseadores
510
     */
511 1
    private function addTemplateTokens()
512
    {
513
        //Añadimos las extensiones de los tags
514 1
        $this->tpl->addTokenParser(new AssetsTokenParser("css"));
515 1
        $this->tpl->addTokenParser(new AssetsTokenParser("js"));
516 1
    }
517
518
    /**
519
     * Método que inyecta las optimizaciones al motor de la plantilla
520
     */
521 1
    private function optimizeTemplates()
522
    {
523
        //Optimizamos
524 1
        $this->tpl->addExtension(new \Twig_Extensions_Extension_I18n());
525 1
    }
526
527
    /**
528
     * Method that extract all path tag for extracting translations
529
     * @param array $domains
530
     *
531
     * @return array
532
     */
533
    private function parsePathTranslations($domains)
534
    {
535
        $translations = array();
536
        if (!empty($domains)) {
537
            foreach ($domains as $domain => $paths) {
538
                if (strlen($domain) && array_key_exists("template", $paths)) {
539
                    $this->addPath($paths["template"], $domain);
540
                    $translations[] = $this->generateTemplate($paths["template"], $domain);
541
                }
542
            }
543
        }
544
545
        return $translations;
546
    }
547
548
    /**
549
     * Method that generate all template caches
550
     */
551
    private function generateTemplatesCache()
552
    {
553
        /** @var \Twig_Loader_Filesystem $loader */
554
        $loader = $this->tpl->getLoader();
555
        $availablePaths = $loader->getPaths();
556
        if (!empty($availablePaths)) {
557
            foreach ($availablePaths as $path) {
558
                $this->generateTemplate($path);
559
            }
560
        }
561
    }
562
}
563