Issues (174)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

classes/PHPTAL.php (14 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * PHPTAL templating engine
4
 *
5
 * PHP Version 5
6
 *
7
 * @category HTML
8
 * @package  PHPTAL
9
 * @author   Laurent Bedubourg <[email protected]>
10
 * @author   Kornel Lesiński <[email protected]>
11
 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
12
 * @version  SVN: $Id$
13
 * @link     http://phptal.org/
14
 */
15
16
define('PHPTAL_VERSION', '1_3_1');
17
18
PHPTAL::autoloadRegister();
19
20
/**
21
 * PHPTAL template entry point.
22
 *
23
 * <code>
24
 * <?php
25
 * require_once 'PHPTAL.php';
26
 * try {
27
 *      $tpl = new PHPTAL('mytemplate.html');
28
 *      $tpl->title = 'Welcome here';
29
 *      $tpl->result = range(1, 100);
30
 *      ...
31
 *      echo $tpl->execute();
32
 * }
33
 * catch (Exception $e) {
34
 *      echo $e;
35
 * }
36
 * ?>
37
 * </code>
38
 *
39
 * @category HTML
40
 * @package  PHPTAL
41
 * @author   Laurent Bedubourg <[email protected]>
42
 * @author   Kornel Lesiński <[email protected]>
43
 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
44
 * @link     http://phptal.org/
45
 */
46
class PHPTAL
47
{
48
    //{{{
49
    /**
50
     * constants for output mode
51
     * @see setOutputMode()
52
     */
53
    const XHTML = 11;
54
    const XML   = 22;
55
    const HTML5 = 55;
56
57
    /**
58
     * @see getPreFilters()
59
     */
60
    protected $prefilters = array();
61
62
    /**
63
     * Prefilters have been redesigned. Old property is no longer used.
64
     *
65
     * @deprecated
66
     */
67
    private $_prefilter = 'REMOVED: DO NOT USE';
68
    protected $_postfilter = null;
69
70
    /**
71
     *  list of template source repositories given to file source resolver
72
     */
73
    protected $_repositories = array();
74
75
    /**
76
     *  template path (path that has been set, not necessarily loaded)
77
     */
78
    protected $_path = null;
79
80
    /**
81
     *  template source resolvers (classes that search for templates by name)
82
     */
83
    protected $resolvers = array();
84
85
    /**
86
     *  template source (only set when not working with file)
87
     */
88
    protected $_source = null;
89
90
    /**
91
     * destination of PHP intermediate file
92
     */
93
    protected $_codeFile = null;
94
95
    /**
96
     * php function generated for the template
97
     */
98
    protected $_functionName = null;
99
100
    /**
101
     * set to true when template is ready for execution
102
     */
103
    protected $_prepared = false;
104
105
    /**
106
     * associative array of phptal:id => PHPTAL_Trigger
107
     */
108
    protected $_triggers = array();
109
110
    /**
111
     * i18n translator
112
     */
113
    protected $_translator = null;
114
115
    /**
116
     * global execution context
117
     */
118
    protected $_globalContext = null;
119
120
    /**
121
     * current execution context
122
     */
123
    protected $_context = null;
124
125
    /**
126
     * list of on-error caught exceptions
127
     */
128
    protected $_errors = array();
129
130
    /**
131
     * encoding used throughout
132
     */
133
    protected $_encoding = 'UTF-8';
134
135
    /**
136
     * type of syntax used in generated templates
137
     */
138
    protected $_outputMode = PHPTAL::XHTML;
139
    /**
140
     * should all comments be stripped
141
     */
142
143
    // configuration properties
144
145
    /**
146
     * don't use code cache
147
     */
148
    protected $_forceReparse = null;
149
150
    /**
151
     * directory where code cache is
152
     */
153
    private $_phpCodeDestination;
154
    private $_phpCodeExtension = 'php';
155
156
    /**
157
     * number of days
158
     */
159
    private $_cacheLifetime = 30;
160
161
    /**
162
     * 1/x
163
     */
164
    private $_cachePurgeFrequency = 30;
165
166
    /**
167
     * speeds up calls to external templates
168
     */
169
    private $externalMacroTemplatesCache = array();
170
171
    //}}}
172
173
    /**
174
     * PHPTAL Constructor.
175
     *
176
     * @param string $path Template file path.
177
     */
178
    public function __construct($path=false)
179
    {
180
        $this->_path = $path;
181
        $this->_globalContext = new stdClass();
182
        $this->_context = new PHPTAL_Context();
183
        $this->_context->setGlobal($this->_globalContext);
184
185
        if (function_exists('sys_get_temp_dir')) {
186
            $this->setPhpCodeDestination(sys_get_temp_dir());
187
        } elseif (substr(PHP_OS, 0, 3) == 'WIN') {
188
            if (file_exists('c:\\WINNT\\Temp\\')) {
189
                $this->setPhpCodeDestination('c:\\WINNT\\Temp\\');
190
            } else {
191
                $this->setPhpCodeDestination('c:\\WINDOWS\\Temp\\');
192
            }
193
        } else {
194
            $this->setPhpCodeDestination('/tmp/');
195
        }
196
    }
197
198
    /**
199
     * create
200
     * returns a new PHPTAL object
201
     *
202
     * @param string $path Template file path.
203
     *
204
     * @return PHPTAL
205
     */
206
    public static function create($path=false)
207
    {
208
        return new PHPTAL($path);
209
    }
210
211
    /**
212
     * Clone template state and context.
213
     *
214
     * @return void
215
     */
216
    public function __clone()
217
    {
218
        $this->_context = $this->_context->pushContext();
219
    }
220
221
    /**
222
     * Set template from file path.
223
     *
224
     * @param string $path filesystem path,
225
     *                     or any path that will be accepted by source resolver
226
     *
227
     * @return $this
228
     */
229 View Code Duplication
    public function setTemplate($path)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
    {
231
        $this->_prepared = false;
232
        $this->_functionName = null;
233
        $this->_codeFile = null;
234
        $this->_path = $path;
235
        $this->_source = null;
236
        $this->_context->_docType = null;
237
        $this->_context->_xmlDeclaration = null;
238
        return $this;
239
    }
240
241
    /**
242
     * Set template from source.
243
     *
244
     * Should be used only with temporary template sources.
245
     * Use setTemplate() or addSourceResolver() whenever possible.
246
     *
247
     * @param string $src The phptal template source.
248
     * @param string $path Fake and 'unique' template path.
249
     *
250
     * @return $this
251
     */
252 View Code Duplication
    public function setSource($src, $path = null)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
    {
254
        $this->_prepared = false;
255
        $this->_functionName = null;
256
        $this->_codeFile = null;
257
        $this->_source = new PHPTAL_StringSource($src, $path);
258
        $this->_path = $this->_source->getRealPath();
259
        $this->_context->_docType = null;
260
        $this->_context->_xmlDeclaration = null;
261
        return $this;
262
    }
263
264
    /**
265
     * Specify where to look for templates.
266
     *
267
     * @param mixed $rep string or Array of repositories
268
     *
269
     * @return $this
270
     */
271
    public function setTemplateRepository($rep)
272
    {
273
        if (is_array($rep)) {
274
            $this->_repositories = $rep;
275
        } else {
276
            $this->_repositories[] = $rep;
277
        }
278
        return $this;
279
    }
280
281
    /**
282
     * Get template repositories.
283
     *
284
     * @return array
285
     */
286
    public function getTemplateRepositories()
287
    {
288
        return $this->_repositories;
289
    }
290
291
    /**
292
     * Clears the template repositories.
293
     *
294
     * @return $this
295
     */
296
    public function clearTemplateRepositories()
297
    {
298
        $this->_repositories = array();
299
        return $this;
300
    }
301
302
    /**
303
     * Specify how to look for templates.
304
     *
305
     * @param PHPTAL_SourceResolver $resolver instance of resolver
306
     *
307
     * @return $this
308
     */
309
    public function addSourceResolver(PHPTAL_SourceResolver $resolver)
310
    {
311
        $this->resolvers[] = $resolver;
312
        return $this;
313
    }
314
315
    /**
316
     * Ignore XML/XHTML comments on parsing.
317
     * Comments starting with <!--! are always stripped.
318
     *
319
     * @param bool $bool if true all comments are stripped during parse
320
     *
321
     * @return $this
322
     */
323
    public function stripComments($bool)
324
    {
325
        $this->resetPrepared();
326
327
        if ($bool) {
328
            $this->prefilters['_phptal_strip_comments_'] = new PHPTAL_PreFilter_StripComments();
329
        } else {
330
            unset($this->prefilters['_phptal_strip_comments_']);
331
        }
332
        return $this;
333
    }
334
335
    /**
336
     * Set output mode
337
     * XHTML output mode will force elements like <link/>, <meta/> and <img/>, etc.
338
     * to be empty and threats attributes like selected, checked to be
339
     * boolean attributes.
340
     *
341
     * XML output mode outputs XML without such modifications
342
     * and is neccessary to generate RSS feeds properly.
343
     *
344
     * @param int $mode (PHPTAL::XML, PHPTAL::XHTML or PHPTAL::HTML5).
345
     *
346
     * @return $this
347
     */
348
    public function setOutputMode($mode)
349
    {
350
        $this->resetPrepared();
351
352
        if ($mode != PHPTAL::XHTML && $mode != PHPTAL::XML && $mode != PHPTAL::HTML5) {
353
            throw new PHPTAL_ConfigurationException('Unsupported output mode '.$mode);
354
        }
355
        $this->_outputMode = $mode;
356
        return $this;
357
    }
358
359
    /**
360
     * Get output mode
361
     * @see setOutputMode()
362
     *
363
     * @return output mode constant
364
     */
365
    public function getOutputMode()
366
    {
367
        return $this->_outputMode;
368
    }
369
370
    /**
371
     * Set input and ouput encoding. Encoding is case-insensitive.
372
     *
373
     * @param string $enc example: 'UTF-8'
374
     *
375
     * @return $this
376
     */
377
    public function setEncoding($enc)
378
    {
379
        $enc = strtoupper($enc);
380
        if ($enc != $this->_encoding) {
381
            $this->_encoding = $enc;
382
            if ($this->_translator) $this->_translator->setEncoding($enc);
383
384
            $this->resetPrepared();
385
        }
386
        return $this;
387
    }
388
389
    /**
390
     * Get input and ouput encoding.
391
     *
392
     * @param string $enc example: 'UTF-8'
0 ignored issues
show
There is no parameter named $enc. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
393
     *
394
     * @return $this
395
     */
396
    public function getEncoding()
397
    {
398
        return $this->_encoding;
399
    }
400
401
    /**
402
     * Set the storage location for intermediate PHP files.
403
     * The path cannot contain characters that would be interpreted by glob() (e.g. *[]?)
404
     *
405
     * @param string $path Intermediate file path.
406
     *
407
     * @return $this
408
     */
409
    public function setPhpCodeDestination($path)
410
    {
411
        $this->_phpCodeDestination = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
412
        $this->resetPrepared();
413
        return $this;
414
    }
415
416
    /**
417
     * Get the storage location for intermediate PHP files.
418
     *
419
     * @return string
420
     */
421
    public function getPhpCodeDestination()
422
    {
423
        return $this->_phpCodeDestination;
424
    }
425
426
    /**
427
     * Set the file extension for intermediate PHP files.
428
     *
429
     * @param string $extension The file extension.
430
     *
431
     * @return $this
432
     */
433
    public function setPhpCodeExtension($extension)
434
    {
435
        $this->_phpCodeExtension = $extension;
436
        $this->resetPrepared();
437
        return $this;
438
    }
439
440
    /**
441
     * Get the file extension for intermediate PHP files.
442
     */
443
    public function getPhpCodeExtension()
444
    {
445
        return $this->_phpCodeExtension;
446
    }
447
448
    /**
449
     * Flags whether to ignore intermediate php files and to
450
     * reparse templates every time (if set to true).
451
     *
452
     * DON'T USE IN PRODUCTION - this makes PHPTAL many times slower.
453
     *
454
     * @param bool $bool Forced reparse state.
455
     *
456
     * @return $this
457
     */
458
    public function setForceReparse($bool)
459
    {
460
        $this->_forceReparse = (bool) $bool;
461
        return $this;
462
    }
463
464
    /**
465
     * Get the value of the force reparse state.
466
     */
467
    public function getForceReparse()
468
    {
469
        return $this->_forceReparse;
470
    }
471
472
    /**
473
     * Set I18N translator.
474
     *
475
     * This sets encoding used by the translator, so be sure to use encoding-dependent
476
     * features of the translator (e.g. addDomain) _after_ calling setTranslator.
477
     *
478
     * @param PHPTAL_TranslationService $t instance
479
     *
480
     * @return $this
481
     */
482
    public function setTranslator(PHPTAL_TranslationService $t)
483
    {
484
        $this->_translator = $t;
485
        $t->setEncoding($this->getEncoding());
486
        return $this;
487
    }
488
489
490
    /**
491
     * Please use addPreFilter instead.
492
     *
493
     * This method and use of PHPTAL_Filter for prefilters are deprecated.
494
     *
495
     * @see PHPTAL::addPreFilter()
496
     * @deprecated
497
     */
498
    final public function setPreFilter(PHPTAL_Filter $filter)
499
    {
500
        $this->resetPrepared();
501
        $this->prefilters['_phptal_old_filter_'] = $filter;
502
    }
503
504
    /**
505
     * Add new prefilter to filter chain.
506
     * Prefilters are called only once template is compiled.
507
     *
508
     * PreFilters must inherit PHPTAL_PreFilter class.
509
     * (in future this method will allow string with filter name instead of object)
510
     *
511
     * @param mixed $filter PHPTAL_PreFilter object or name of prefilter to add
512
     *
513
     * @return PHPTAL
514
     */
515
    final public function addPreFilter($filter)
516
    {
517
        $this->resetPrepared();
518
519
        if (!$filter instanceof PHPTAL_PreFilter) {
520
            throw new PHPTAL_ConfigurationException("addPreFilter expects PHPTAL_PreFilter object");
521
        }
522
523
        $this->prefilters[] = $filter;
524
        return $this;
525
    }
526
527
    /**
528
     * Array with all prefilter objects *or strings* that are names of prefilter classes.
529
     * (the latter is not implemented in 1.2.1)
530
     *
531
     * Array keys may be non-numeric!
532
     *
533
     * @return array
534
     */
535
    protected function getPreFilters()
536
    {
537
        return $this->prefilters;
538
    }
539
540
    /**
541
     * Returns string that is unique for every different configuration of prefilters.
542
     * Result of prefilters may be cached until this string changes.
543
     *
544
     * You can override this function.
545
     *
546
     * @return string
547
     */
548
    private function getPreFiltersCacheId()
549
    {
550
        $cacheid = '';
551
        foreach($this->getPreFilters() as $key => $prefilter) {
552
            if ($prefilter instanceof PHPTAL_PreFilter) {
553
                $cacheid .= $key.$prefilter->getCacheId();
554
            } elseif ($prefilter instanceof PHPTAL_Filter) {
555
                $cacheid .= $key.get_class($prefilter);
556
            } else {
557
                $cacheid .= $key.$prefilter;
558
            }
559
        }
560
        return $cacheid;
561
    }
562
563
    /**
564
     * Instantiate prefilters
565
     *
566
     * @return array of PHPTAL_[Pre]Filter objects
567
     */
568
    private function getPreFilterInstances()
569
    {
570
        $prefilters = $this->getPreFilters();
571
572
        foreach($prefilters as $prefilter) {
573
            if ($prefilter instanceof PHPTAL_PreFilter) {
574
                $prefilter->setPHPTAL($this);
575
            }
576
        }
577
        return $prefilters;
578
    }
579
580
    /**
581
     * Set template post filter.
582
     * It will be called every time after template generates output.
583
     *
584
     * See PHPTAL_PostFilter class.
585
     *
586
     * @param PHPTAL_Filter $filter filter instance
587
     */
588
    public function setPostFilter(PHPTAL_Filter $filter)
589
    {
590
        $this->_postfilter = $filter;
591
        return $this;
592
    }
593
594
    /**
595
     * Register a trigger for specified phptal:id.
596
     * @param string $id phptal:id to look for
597
     */
598
    public function addTrigger($id, PHPTAL_Trigger $trigger)
599
    {
600
        $this->_triggers[$id] = $trigger;
601
        return $this;
602
    }
603
604
    /**
605
     * Returns trigger for specified phptal:id.
606
     *
607
     * @param string $id phptal:id
608
     *
609
     * @return PHPTAL_Trigger or NULL
610
     */
611
    public function getTrigger($id)
612
    {
613
        if (array_key_exists($id, $this->_triggers)) {
614
            return $this->_triggers[$id];
615
        }
616
        return null;
617
    }
618
619
    /**
620
     * Set a context variable.
621
     * Use it by setting properties on PHPTAL object.
622
     *
623
     * @param string $varname
624
     * @param mixed $value
625
     *
626
     * @return void
627
     */
628
    public function __set($varname, $value)
629
    {
630
        $this->_context->__set($varname, $value);
631
    }
632
633
    /**
634
     * Set a context variable.
635
     *
636
     * @see PHPTAL::__set()
637
     * @param string $varname name of the variable
638
     * @param mixed $value value of the variable
639
     *
640
     * @return $this
641
     */
642
    public function set($varname, $value)
643
    {
644
        $this->_context->__set($varname, $value);
645
        return $this;
646
    }
647
648
    /**
649
     * Execute the template code and return generated markup.
650
     *
651
     * @return string
652
     */
653
    public function execute()
654
    {
655
        try
656
        {
657
            if (!$this->_prepared) {
658
                // includes generated template PHP code
659
                $this->prepare();
660
            }
661
            $this->_context->echoDeclarations(false);
662
663
            $templateFunction = $this->getFunctionName();
664
665
            try {
666
                ob_start();
667
                $templateFunction($this, $this->_context);
668
                $res = ob_get_clean();
669
            }
670
            catch (Exception $e)
671
            {
672
                ob_end_clean();
673
                throw $e;
674
            }
675
676
            // unshift doctype
677
            if ($this->_context->_docType) {
678
                $res = $this->_context->_docType . $res;
679
            }
680
681
            // unshift xml declaration
682
            if ($this->_context->_xmlDeclaration) {
683
                $res = $this->_context->_xmlDeclaration . "\n" . $res;
684
            }
685
686
            if ($this->_postfilter) {
687
                return $this->_postfilter->filter($res);
688
            }
689
        }
690
        catch (Exception $e)
691
        {
692
            PHPTAL_ExceptionHandler::handleException($e, $this->getEncoding());
693
        }
694
695
        return $res;
0 ignored issues
show
The variable $res does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
696
    }
697
698
    /**
699
     * Execute and echo template without buffering of the output.
700
     * This function does not allow postfilters nor DOCTYPE/XML declaration.
701
     *
702
     * @return NULL
703
     */
704
    public function echoExecute()
705
    {
706
        try {
707
            if (!$this->_prepared) {
708
                // includes generated template PHP code
709
                $this->prepare();
710
            }
711
712
            if ($this->_postfilter) {
713
                throw new PHPTAL_ConfigurationException("echoExecute() does not support postfilters");
714
            }
715
716
            $this->_context->echoDeclarations(true);
717
718
            $templateFunction = $this->getFunctionName();
719
            $templateFunction($this, $this->_context);
720
        }
721
        catch (Exception $e)
722
        {
723
            PHPTAL_ExceptionHandler::handleException($e, $this->getEncoding());
724
        }
725
    }
726
727
    /**
728
     * Execute a template macro.
729
     * Should be used only from within generated template code!
730
     *
731
     * @param string $path Template macro path
732
     */
733
    public function executeMacro($path)
734
    {
735
        $this->_executeMacroOfTemplate($path, $this);
736
    }
737
738
    /**
739
     * This is PHPTAL's internal function that handles
740
     * execution of macros from templates.
741
     *
742
     * $this is caller's context (the file where execution had originally started)
743
     *
744
     * @param PHPTAL $local_tpl is PHPTAL instance of the file in which macro is defined
745
     *                          (it will be different from $this if it's external macro call)
746
     * @access private
747
     */
748
    final public function _executeMacroOfTemplate($path, PHPTAL $local_tpl)
749
    {
750
        // extract macro source file from macro name, if macro path does not
751
        // contain filename, then the macro is assumed to be local
752
753
        if (preg_match('/^(.*?)\/([a-z0-9_-]*)$/i', $path, $m)) {
754
            list(, $file, $macroName) = $m;
755
756
            if (isset($this->externalMacroTemplatesCache[$file])) {
757
                $tpl = $this->externalMacroTemplatesCache[$file];
758
            } else {
759
                $tpl = clone $this;
760
                array_unshift($tpl->_repositories, dirname($this->_source->getRealPath()));
761
                $tpl->setTemplate($file);
762
                $tpl->prepare();
763
764
                // keep it small (typically only 1 or 2 external files are used)
765
                if (count($this->externalMacroTemplatesCache) > 10) {
766
                    $this->externalMacroTemplatesCache = array();
767
                }
768
                $this->externalMacroTemplatesCache[$file] = $tpl;
769
            }
770
771
            $fun = $tpl->getFunctionName() . '_' . strtr($macroName, "-", "_");
772
            if (!function_exists($fun)) {
773
                throw new PHPTAL_MacroMissingException("Macro '$macroName' is not defined in $file", $this->_source->getRealPath());
774
            }
775
776
            $fun($tpl, $this);
777
778
        } else {
779
            // call local macro
780
            $fun = $local_tpl->getFunctionName() . '_' . strtr($path, "-", "_");
781
            if (!function_exists($fun)) {
782
                throw new PHPTAL_MacroMissingException("Macro '$path' is not defined", $local_tpl->_source->getRealPath());
783
            }
784
            $fun( $local_tpl, $this);
785
        }
786
    }
787
788
    /**
789
     * ensure that getCodePath will return up-to-date path
790
     */
791
    private function setCodeFile()
792
    {
793
        $this->findTemplate();
794
        $this->_codeFile = $this->getPhpCodeDestination() . $this->getFunctionName() . '.' . $this->getPhpCodeExtension();
795
    }
796
797
    protected function resetPrepared()
798
    {
799
        $this->_prepared = false;
800
        $this->_functionName = null;
801
        $this->_codeFile = null;
802
    }
803
804
    /**
805
     * Prepare template without executing it.
806
     */
807
    public function prepare()
808
    {
809
        // clear just in case settings changed and cache is out of date
810
        $this->externalMacroTemplatesCache = array();
811
812
        // find the template source file and update function name
813
        $this->setCodeFile();
814
815
        if (!function_exists($this->getFunctionName())) {
816
            // parse template if php generated code does not exists or template
817
            // source file modified since last generation or force reparse is set
818
            if ($this->getForceReparse() || !file_exists($this->getCodePath())) {
819
820
                // i'm not sure where that belongs, but not in normal path of execution
821
                // because some sites have _a lot_ of files in temp
822
                if ($this->getCachePurgeFrequency() && mt_rand()%$this->getCachePurgeFrequency() == 0) {
823
                    $this->cleanUpGarbage();
824
                }
825
826
                $result = $this->parse();
827
828
                if (!file_put_contents($this->getCodePath(), $result)) {
829
                    throw new PHPTAL_IOException('Unable to open '.$this->getCodePath().' for writing');
830
                }
831
832
                // the awesome thing about eval() is that parse errors don't stop PHP.
833
                // when PHP dies during eval, fatal error is printed and
834
                // can be captured with output buffering
835
                ob_start();
836
                try {
837
                    eval("?>\n".$result);
838
                }
839
                catch(ParseError $parseError) {
0 ignored issues
show
catch (\ParseError $pars...Line(), $parseError); } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
840
                    ob_end_clean();
841
                    throw new PHPTAL_TemplateException(
842
                        'Parse error: ' . $parseError->getMessage(),
843
                        $this->getCodePath(),
844
                        $parseError->getLine(),
845
                        $parseError
846
                    );
847
                }
848
                catch(Exception $e) {
849
                    ob_end_clean();
850
                    throw $e;
851
                }
852
853
                if (!function_exists($this->getFunctionName())) {
854
                    $msg = str_replace('eval()\'d code', $this->getCodePath(), ob_get_clean());
855
856
                    // greedy .* ensures last match
857
                    if (preg_match('/.*on line (\d+)$/m', $msg, $m)) $line=$m[1]; else $line=0;
858
                    throw new PHPTAL_TemplateException(trim($msg), $this->getCodePath(), $line);
859
                }
860
                ob_end_clean();
861
862
            } else {
863
                // eval trick is used only on first run,
864
                // just in case it causes any problems with opcode accelerators
865
                require $this->getCodePath();
866
            }
867
        }
868
869
        $this->_prepared = true;
870
        return $this;
871
    }
872
873
    /**
874
     * get how long compiled templates and phptal:cache files are kept, in days
875
     */
876
    public function getCacheLifetime()
877
    {
878
        return $this->_cacheLifetime;
879
    }
880
881
    /**
882
     * set how long compiled templates and phptal:cache files are kept
883
     *
884
     * @param $days number of days
885
     */
886
    public function setCacheLifetime($days)
887
    {
888
        $this->_cacheLifetime = max(0.5, $days);
889
        return $this;
890
    }
891
892
    /**
893
     * PHPTAL will scan cache and remove old files on every nth compile
894
     * Set to 0 to disable cleanups
895
     */
896
    public function setCachePurgeFrequency($n)
897
    {
898
        $this->_cachePurgeFrequency = (int)$n;
899
        return $this;
900
    }
901
902
    /**
903
     * how likely cache cleaning can happen
904
     * @see self::setCachePurgeFrequency()
905
     */
906
    public function getCachePurgeFrequency()
907
    {
908
        return $this->_cachePurgeFrequency;
909
    }
910
911
912
    /**
913
     * Removes all compiled templates from cache that
914
     * are older than getCacheLifetime() days
915
     */
916
    public function cleanUpGarbage()
917
    {
918
        $cacheFilesExpire = time() - $this->getCacheLifetime() * 3600 * 24;
919
920
        // relies on templates sorting order being related to their modification dates
921
        $upperLimit = $this->getPhpCodeDestination() . $this->getFunctionNamePrefix($cacheFilesExpire) . '_';
922
        $lowerLimit = $this->getPhpCodeDestination() . $this->getFunctionNamePrefix(0);
923
924
        // second * gets phptal:cache
925
        $cacheFiles = glob($this->getPhpCodeDestination() . 'tpl_????????_*.' . $this->getPhpCodeExtension() . '*');
926
927
        if ($cacheFiles) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheFiles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
928
            foreach ($cacheFiles as $index => $file) {
929
930
                // comparison here skips filenames that are certainly too new
931
                if (strcmp($file, $upperLimit) <= 0 || substr($file, 0, strlen($lowerLimit)) === $lowerLimit) {
932
                    $time = filemtime($file);
933
                    if ($time && $time < $cacheFilesExpire) {
934
                        @unlink($file);
0 ignored issues
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...
935
                    }
936
                }
937
            }
938
        }
939
    }
940
941
    /**
942
     * Removes content cached with phptal:cache for currently set template
943
     * Must be called after setSource/setTemplate.
944
     */
945
    public function cleanUpCache()
946
    {
947
        $filename = $this->getCodePath();
948
        $cacheFiles = glob($filename . '?*');
949
        if ($cacheFiles) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheFiles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
950
            foreach ($cacheFiles as $file) {
951
                if (substr($file, 0, strlen($filename)) !== $filename) continue; // safety net
952
                @unlink($file);
0 ignored issues
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...
953
            }
954
        }
955
        $this->_prepared = false;
956
    }
957
958
    /**
959
     * Returns the path of the intermediate PHP code file.
960
     *
961
     * The returned file may be used to cleanup (unlink) temporary files
962
     * generated by temporary templates or more simply for debug.
963
     *
964
     * @return string
965
     */
966
    public function getCodePath()
967
    {
968
        if (!$this->_codeFile) $this->setCodeFile();
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_codeFile of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
969
        return $this->_codeFile;
970
    }
971
972
    /**
973
     * Returns the generated template function name.
974
     * @return string
975
     */
976
    public function getFunctionName()
977
    {
978
       // function name is used as base for caching, so it must be unique for
979
       // every combination of settings that changes code in compiled template
980
981
       if (!$this->_functionName) {
982
983
            // just to make tempalte name recognizable
984
            $basename = preg_replace('/\.[a-z]{3,5}$/', '', basename($this->_source->getRealPath()));
985
            $basename = substr(trim(preg_replace('/[^a-zA-Z0-9]+/', '_', $basename), "_"), 0, 20);
986
987
            $hash = md5(PHPTAL_VERSION . PHP_VERSION
988
                    . $this->_source->getRealPath()
989
                    . $this->getEncoding()
990
                    . $this->getPrefiltersCacheId()
991
                    . $this->getOutputMode(),
992
                    true
993
                    );
994
995
            // uses base64 rather than hex to make filename shorter.
996
            // there is loss of some bits due to name constraints and case-insensivity,
997
            // but that's still over 110 bits in addition to basename and timestamp.
998
            $hash = strtr(rtrim(base64_encode($hash),"="),"+/=","_A_");
999
1000
            $this->_functionName = $this->getFunctionNamePrefix($this->_source->getLastModifiedTime()) .
1001
                                   $basename . '__' . $hash;
1002
        }
1003
        return $this->_functionName;
1004
    }
1005
1006
    /**
1007
     * Returns prefix used for function name.
1008
     * Function name is also base name for the template.
1009
     *
1010
     * @param int $timestamp unix timestamp with template modification date
1011
     *
1012
     * @return string
1013
     */
1014
    private function getFunctionNamePrefix($timestamp)
1015
    {
1016
        // tpl_ prefix and last modified time must not be changed,
1017
        // because cache cleanup relies on that
1018
        return 'tpl_' . sprintf("%08x", $timestamp) .'_';
1019
    }
1020
1021
    /**
1022
     * Returns template translator.
1023
     * @return PHPTAL_TranslationService
1024
     */
1025
    public function getTranslator()
1026
    {
1027
        return $this->_translator;
1028
    }
1029
1030
    /**
1031
     * Returns array of exceptions caught by tal:on-error attribute.
1032
     *
1033
     * @return array<Exception>
1034
     */
1035
    public function getErrors()
1036
    {
1037
        return $this->_errors;
1038
    }
1039
1040
    /**
1041
     * Public for phptal templates, private for user.
1042
     *
1043
     * @return void
1044
     * @access private
1045
     */
1046
    public function addError(Exception $error)
1047
    {
1048
        $this->_errors[] =  $error;
1049
    }
1050
1051
    /**
1052
     * Returns current context object.
1053
     * Use only in Triggers.
1054
     *
1055
     * @return PHPTAL_Context
1056
     */
1057
    public function getContext()
1058
    {
1059
        return $this->_context;
1060
    }
1061
1062
    /**
1063
     * only for use in generated template code
1064
     *
1065
     * @access private
1066
     */
1067
    public function getGlobalContext()
1068
    {
1069
        return $this->_globalContext;
1070
    }
1071
1072
    /**
1073
     * only for use in generated template code
1074
     *
1075
     * @access private
1076
     */
1077
    final public function pushContext()
1078
    {
1079
        $this->_context = $this->_context->pushContext();
1080
        return $this->_context;
1081
    }
1082
1083
    /**
1084
     * only for use in generated template code
1085
     *
1086
     * @access private
1087
     */
1088
    final public function popContext()
1089
    {
1090
        $this->_context = $this->_context->popContext();
1091
        return $this->_context;
1092
    }
1093
1094
    /**
1095
     * Parse currently set template, prefilter and generate PHP code.
1096
     *
1097
     * @return string (compiled PHP code)
1098
     */
1099
    protected function parse()
1100
    {
1101
        $data = $this->_source->getData();
1102
1103
        $prefilters = $this->getPreFilterInstances();
1104
        foreach($prefilters as $prefilter) {
1105
            $data = $prefilter->filter($data);
1106
        }
1107
1108
        $realpath = $this->_source->getRealPath();
1109
        $parser = new PHPTAL_Dom_SaxXmlParser($this->_encoding);
1110
1111
        $builder = new PHPTAL_Dom_PHPTALDocumentBuilder();
1112
        $tree = $parser->parseString($builder, $data, $realpath)->getResult();
1113
1114
        foreach($prefilters as $prefilter) {
1115
            if ($prefilter instanceof PHPTAL_PreFilter) {
1116
                if ($prefilter->filterDOM($tree) !== NULL) {
1117
                    throw new PHPTAL_ConfigurationException("Don't return value from filterDOM()");
1118
                }
1119
            }
1120
        }
1121
1122
        $state = new PHPTAL_Php_State($this);
1123
1124
        $codewriter = new PHPTAL_Php_CodeWriter($state);
1125
        $codewriter->doTemplateFile($this->getFunctionName(), $tree);
1126
1127
        return $codewriter->getResult();
1128
    }
1129
1130
    /**
1131
     * Search template source location.
1132
     * @return void
1133
     */
1134
    protected function findTemplate()
1135
    {
1136
        if ($this->_path == false) {
0 ignored issues
show
It seems like you are loosely comparing $this->_path of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1137
            throw new PHPTAL_ConfigurationException('No template file specified');
1138
        }
1139
1140
        // template source already defined
1141
        if ($this->_source) {
1142
            return;
1143
        }
1144
1145
        if (!$this->resolvers && !$this->_repositories) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->resolvers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->_repositories of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1146
            $this->_source = new PHPTAL_FileSource($this->_path);
1147
        } else {
1148
            foreach ($this->resolvers as $resolver) {
1149
                $source = $resolver->resolve($this->_path);
1150
                if ($source) {
1151
                    $this->_source = $source;
1152
                    return;
1153
                }
1154
            }
1155
1156
            $resolver = new PHPTAL_FileSourceResolver($this->_repositories);
1157
            $this->_source = $resolver->resolve($this->_path);
1158
        }
1159
1160
        if (!$this->_source) {
1161
            throw new PHPTAL_IOException('Unable to locate template file '.$this->_path);
1162
        }
1163
    }
1164
1165
    /**
1166
     * Removed
1167
     *
1168
     * @deprecated
1169
     * @return void
1170
     */
1171
    final public static function setIncludePath()
1172
    {
1173
    }
1174
1175
    /**
1176
     * Restore include path to state before PHPTAL modified it.
1177
     *
1178
     * @deprecated
1179
     * @return void
1180
     */
1181
    final public static function restoreIncludePath()
1182
    {
1183
    }
1184
1185
    /**
1186
     * Suitable for callbacks from SPL autoload
1187
     *
1188
     * @param string $class class name to load
1189
     *
1190
     * @return void
1191
     */
1192
    final public static function autoload($class)
1193
    {
1194
        if (version_compare(PHP_VERSION, '5.3', '>=') && __NAMESPACE__) {
1195
            $class = str_replace(__NAMESPACE__, 'PHPTAL', $class);
1196
            $class = strtr($class, '\\', '_');
1197
        }
1198
1199
        if (substr($class, 0, 7) !== 'PHPTAL_') return;
1200
1201
        $path = dirname(__FILE__) . strtr("_".$class, "_", DIRECTORY_SEPARATOR) . '.php';
1202
1203
        require $path;
1204
    }
1205
1206
    /**
1207
     * Sets up PHPTAL's autoloader.
1208
     *
1209
     * If you have to use your own autoloader to load PHPTAL files,
1210
     * use spl_autoload_unregister(array('PHPTAL','autoload'));
1211
     *
1212
     * @return void
1213
     */
1214
    final public static function autoloadRegister()
1215
    {
1216
        // spl_autoload_register disables oldschool autoload
1217
        // even if it was added using spl_autoload_register!
1218
        // this is intended to preserve old autoloader
1219
1220
        $uses_autoload = function_exists('__autoload')
1221
            && (!($tmp = spl_autoload_functions()) || ($tmp[0] === '__autoload'));
1222
1223
        // Prepending PHPTAL's autoloader helps if there are other autoloaders
1224
        // that throw/die when file is not found. Only >5.3 though.
1225
        if (version_compare(PHP_VERSION, '5.3', '>=')) {
1226
            @spl_autoload_register(array(__CLASS__,'autoload'), false, true);
0 ignored issues
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...
1227
        } else {
1228
            spl_autoload_register(array(__CLASS__,'autoload'));
1229
        }
1230
1231
        if ($uses_autoload) {
1232
            spl_autoload_register('__autoload');
1233
        }
1234
    }
1235
}
1236