Template::handleNocacheFlag()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 3
nc 3
nop 1
dl 0
loc 4
rs 10
c 1
b 0
f 0
1
<?php
2
/**
3
 * Smarty Internal Plugin Smarty Template Compiler Base
4
 * This file contains the basic classes and methods for compiling Smarty templates with lexer/parser
5
 *
6
7
8
 * @author     Uwe Tews
9
 */
10
11
namespace Smarty\Compiler;
12
13
use Smarty\Compile\BlockCompiler;
14
use Smarty\Compile\DefaultHandlerBlockCompiler;
15
use Smarty\Compile\DefaultHandlerFunctionCallCompiler;
16
use Smarty\Compile\ModifierCompiler;
17
use Smarty\Compile\ObjectMethodBlockCompiler;
18
use Smarty\Compile\ObjectMethodCallCompiler;
19
use Smarty\Compile\FunctionCallCompiler;
20
use Smarty\Compile\PrintExpressionCompiler;
21
use Smarty\Lexer\TemplateLexer;
22
use Smarty\Parser\TemplateParser;
23
use Smarty\Smarty;
24
use Smarty\Compile\Tag\ExtendsTag;
25
use Smarty\CompilerException;
26
use Smarty\Exception;
27
use function array_merge;
28
use function is_array;
29
use function strlen;
30
use function substr;
31
32
/**
33
 * Class SmartyTemplateCompiler
34
 *
35
36
37
 */
38
class Template extends BaseCompiler {
39
40
	/**
41
	 * counter for prefix variable number
42
	 *
43
	 * @var int
44
	 */
45
	public static $prefixVariableNumber = 0;
46
47
	/**
48
	 * Parser object
49
	 *
50
	 * @var \Smarty\Parser\TemplateParser
51
	 */
52
	private $parser = null;
53
54
	/**
55
	 * hash for nocache sections
56
	 *
57
	 * @var mixed
58
	 */
59
	public $nocache_hash = null;
60
61
	/**
62
	 * suppress generation of nocache code
63
	 *
64
	 * @var bool
65
	 */
66
	public $suppressNocacheProcessing = false;
67
68
	/**
69
	 * caching enabled (copied from template object)
70
	 *
71
	 * @var int
72
	 */
73
	public $caching = 0;
74
75
	/**
76
	 * tag stack
77
	 *
78
	 * @var array
79
	 */
80
	private $_tag_stack = [];
81
82
	/**
83
	 * tag stack count
84
	 *
85
	 * @var array
86
	 */
87
	private $_tag_stack_count = [];
88
89
	/**
90
	 * current template
91
	 *
92
	 * @var \Smarty\Template
93
	 */
94
	private $template = null;
95
96
	/**
97
	 * merged included sub template data
98
	 *
99
	 * @var array
100
	 */
101
	public $mergedSubTemplatesData = [];
102
103
	/**
104
	 * merged sub template code
105
	 *
106
	 * @var array
107
	 */
108
	public $mergedSubTemplatesCode = [];
109
110
	/**
111
	 * source line offset for error messages
112
	 *
113
	 * @var int
114
	 */
115
	public $trace_line_offset = 0;
116
117
	/**
118
	 * trace uid
119
	 *
120
	 * @var string
121
	 */
122
	public $trace_uid = '';
123
124
	/**
125
	 * trace file path
126
	 *
127
	 * @var string
128
	 */
129
	public $trace_filepath = '';
130
131
	/**
132
	 * Template functions
133
	 *
134
	 * @var array
135
	 */
136
	public $tpl_function = [];
137
138
	/**
139
	 * compiled template or block function code
140
	 *
141
	 * @var string
142
	 */
143
	public $blockOrFunctionCode = '';
144
145
	/**
146
	 * flags for used modifier plugins
147
	 *
148
	 * @var array
149
	 */
150
	public $modifier_plugins = [];
151
152
	/**
153
	 * parent compiler object for merged subtemplates and template functions
154
	 *
155
	 * @var \Smarty\Compiler\Template
156
	 */
157
	private $parent_compiler = null;
158
159
	/**
160
	 * Flag true when compiling nocache section
161
	 *
162
	 * @var bool
163
	 */
164
	public $nocache = false;
165
166
	/**
167
	 * Flag true when tag is compiled as nocache
168
	 *
169
	 * @var bool
170
	 */
171
	public $tag_nocache = false;
172
173
	/**
174
	 * Compiled tag prefix code
175
	 *
176
	 * @var array
177
	 */
178
	public $prefix_code = [];
179
180
	/**
181
	 * Prefix code  stack
182
	 *
183
	 * @var array
184
	 */
185
	public $prefixCodeStack = [];
186
187
	/**
188
	 * A variable string was compiled
189
	 *
190
	 * @var bool
191
	 */
192
	public $has_variable_string = false;
193
194
	/**
195
	 * Stack for {setfilter} {/setfilter}
196
	 *
197
	 * @var array
198
	 */
199
	public $variable_filter_stack = [];
200
201
	/**
202
	 * Nesting count of looping tags like {foreach}, {for}, {section}, {while}
203
	 *
204
	 * @var int
205
	 */
206
	public $loopNesting = 0;
207
208
	/**
209
	 * Strip preg pattern
210
	 *
211
	 * @var string
212
	 */
213
	public $stripRegEx = '![\t ]*[\r\n]+[\t ]*!';
214
215
	/**
216
	 * General storage area for tag compiler plugins
217
	 *
218
	 * @var array
219
	 */
220
	public $_cache = array();
221
222
	/**
223
	 * Lexer preg pattern for left delimiter
224
	 *
225
	 * @var string
226
	 */
227
	private $ldelPreg = '[{]';
228
229
	/**
230
	 * Lexer preg pattern for right delimiter
231
	 *
232
	 * @var string
233
	 */
234
	private $rdelPreg = '[}]';
235
236
	/**
237
	 * Length of right delimiter
238
	 *
239
	 * @var int
240
	 */
241
	private $rdelLength = 0;
242
243
	/**
244
	 * Length of left delimiter
245
	 *
246
	 * @var int
247
	 */
248
	private $ldelLength = 0;
249
250
	/**
251
	 * Lexer preg pattern for user literals
252
	 *
253
	 * @var string
254
	 */
255
	private $literalPreg = '';
256
257
	/**
258
	 * array of callbacks called when the normal compile process of template is finished
259
	 *
260
	 * @var array
261
	 */
262
	public $postCompileCallbacks = [];
263
264
	/**
265
	 * prefix code
266
	 *
267
	 * @var string
268
	 */
269
	public $prefixCompiledCode = '';
270
271
	/**
272
	 * postfix code
273
	 *
274
	 * @var string
275
	 */
276
	public $postfixCompiledCode = '';
277
	/**
278
	 * @var ObjectMethodBlockCompiler
279
	 */
280
	private $objectMethodBlockCompiler;
281
	/**
282
	 * @var DefaultHandlerBlockCompiler
283
	 */
284
	private $defaultHandlerBlockCompiler;
285
	/**
286
	 * @var BlockCompiler
287
	 */
288
	private $blockCompiler;
289
	/**
290
	 * @var DefaultHandlerFunctionCallCompiler
291
	 */
292
	private $defaultHandlerFunctionCallCompiler;
293
	/**
294
	 * @var FunctionCallCompiler
295
	 */
296
	private $functionCallCompiler;
297
	/**
298
	 * @var ObjectMethodCallCompiler
299
	 */
300
	private $objectMethodCallCompiler;
301
	/**
302
	 * @var ModifierCompiler
303
	 */
304
	private $modifierCompiler;
305
	/**
306
	 * @var PrintExpressionCompiler
307
	 */
308
	private $printExpressionCompiler;
309
310
	/**
311
	 * Depth of nested {nocache}{/nocache} blocks. If outside, this is 0. If inside, this is 1 or higher (if nested).
312
	 * @var int
313
	 */
314
	private $noCacheStackDepth = 0;
315
316
	/**
317
	 * disabled auto-escape (when set to true, the next variable output is not auto-escaped)
318
	 *
319
	 * @var boolean
320
	 */
321
	private $raw_output = false;
322
323
	/**
324
	 * Initialize compiler
325
	 *
326
	 * @param Smarty $smarty global instance
327
	 */
328
	public function __construct(Smarty $smarty) {
329
		$this->smarty = $smarty;
330
		$this->nocache_hash = str_replace(
331
			[
332
				'.',
333
				',',
334
			],
335
			'_',
336
			uniqid(mt_rand(), true)
337
		);
338
339
		$this->modifierCompiler = new ModifierCompiler();
340
		$this->functionCallCompiler = new FunctionCallCompiler();
341
		$this->defaultHandlerFunctionCallCompiler = new DefaultHandlerFunctionCallCompiler();
342
		$this->blockCompiler = new BlockCompiler();
343
		$this->defaultHandlerBlockCompiler = new DefaultHandlerBlockCompiler();
344
		$this->objectMethodBlockCompiler = new ObjectMethodBlockCompiler();
345
		$this->objectMethodCallCompiler = new ObjectMethodCallCompiler();
346
		$this->printExpressionCompiler = new PrintExpressionCompiler();
347
	}
348
349
	/**
350
	 * Method to compile a Smarty template
351
	 *
352
	 * @param \Smarty\Template $template template object to compile
353
	 *
354
	 * @return string code
355
	 * @throws Exception
356
	 */
357
	public function compileTemplate(\Smarty\Template $template) {
358
		return $template->createCodeFrame(
359
			$this->compileTemplateSource($template),
360
			$this->smarty->runPostFilters($this->blockOrFunctionCode, $this->template) .
0 ignored issues
show
Bug introduced by
It seems like $this->template can also be of type null; however, parameter $template of Smarty\Smarty::runPostFilters() does only seem to accept Smarty\Template, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

360
			$this->smarty->runPostFilters($this->blockOrFunctionCode, /** @scrutinizer ignore-type */ $this->template) .
Loading history...
361
			join('', $this->mergedSubTemplatesCode),
362
			false,
363
			$this
364
		);
365
	}
366
367
	/**
368
	 * Compile template source and run optional post filter
369
	 *
370
	 * @param \Smarty\Template $template
371
	 * @param Template|null $parent_compiler
372
	 *
373
	 * @return string
374
	 * @throws CompilerException
375
	 * @throws Exception
376
	 */
377
	public function compileTemplateSource(\Smarty\Template $template, ?\Smarty\Compiler\Template $parent_compiler = null) {
378
		try {
379
			// save template object in compiler class
380
			$this->template = $template;
381
			if ($this->smarty->debugging) {
382
				$this->smarty->getDebug()->start_compile($this->template);
383
			}
384
			$this->parent_compiler = $parent_compiler ? $parent_compiler : $this;
385
386
			if (empty($template->getCompiled()->nocache_hash)) {
387
				$template->getCompiled()->nocache_hash = $this->nocache_hash;
388
			} else {
389
				$this->nocache_hash = $template->getCompiled()->nocache_hash;
390
			}
391
			$this->caching = $template->caching;
392
393
			// flag for nocache sections
394
			$this->nocache = false;
395
			$this->tag_nocache = false;
396
			// reset has nocache code flag
397
			$this->template->getCompiled()->setNocacheCode(false);
398
399
			$this->has_variable_string = false;
400
			$this->prefix_code = [];
401
			// add file dependency
402
			if ($this->template->getSource()->handler->checkTimestamps()) {
403
				$this->parent_compiler->getTemplate()->getCompiled()->file_dependency[$this->template->getSource()->uid] =
404
					[
405
						$this->template->getSource()->getResourceName(),
406
						$this->template->getSource()->getTimeStamp(),
407
						$this->template->getSource()->type,
408
					];
409
			}
410
			// get template source
411
			if (!empty($this->template->getSource()->components)) {
412
413
				$_compiled_code = '<?php $_smarty_tpl->getInheritance()->init($_smarty_tpl, true); ?>';
414
415
				$i = 0;
416
				$reversed_components = array_reverse($this->template->getSource()->components);
417
				foreach ($reversed_components as $source) {
418
					$i++;
419
					if ($i === count($reversed_components)) {
420
						$_compiled_code .= '<?php $_smarty_tpl->getInheritance()->endChild($_smarty_tpl); ?>';
421
					}
422
					$_compiled_code .= $this->compileTag(
423
						'include',
424
						[
425
							var_export($source->resource, true),
426
							['scope' => 'parent'],
427
						]
428
					);
429
				}
430
				$_compiled_code = $this->smarty->runPostFilters($_compiled_code, $this->template);
431
			} else {
432
				// get template source
433
				$_content = $this->template->getSource()->getContent();
434
				$_compiled_code = $this->smarty->runPostFilters(
435
					$this->doCompile(
436
						$this->smarty->runPreFilters($_content, $this->template),
437
						true
438
					),
439
					$this->template
440
				);
441
			}
442
443
		} catch (\Exception $e) {
444
			if ($this->smarty->debugging) {
445
				$this->smarty->getDebug()->end_compile($this->template);
446
			}
447
			$this->_tag_stack = [];
448
			// free memory
449
			$this->parent_compiler = null;
450
			$this->template = null;
451
			$this->parser = null;
452
			throw $e;
453
		}
454
		if ($this->smarty->debugging) {
455
			$this->smarty->getDebug()->end_compile($this->template);
456
		}
457
		$this->parent_compiler = null;
458
		$this->parser = null;
459
		return $_compiled_code;
460
	}
461
462
	/**
463
	 * Compile Tag
464
	 * This is a call back from the lexer/parser
465
	 *
466
	 * Save current prefix code
467
	 * Compile tag
468
	 * Merge tag prefix code with saved one
469
	 * (required nested tags in attributes)
470
	 *
471
	 * @param string $tag tag name
472
	 * @param array $args array with tag attributes
473
	 * @param array $parameter array with compilation parameter
474
	 *
475
	 * @return string compiled code
476
	 * @throws Exception
477
	 * @throws CompilerException
478
	 */
479
	public function compileTag($tag, $args, $parameter = []) {
480
		$this->prefixCodeStack[] = $this->prefix_code;
481
		$this->prefix_code = [];
482
		$result = $this->compileTag2($tag, $args, $parameter);
483
		$this->prefix_code = array_merge($this->prefix_code, array_pop($this->prefixCodeStack));
484
		return $result;
485
	}
486
487
	/**
488
	 * Compiles code for modifier execution
489
	 *
490
	 * @param $modifierlist
491
	 * @param $value
492
	 *
493
	 * @return string compiled code
494
	 * @throws CompilerException
495
	 * @throws Exception
496
	 */
497
	public function compileModifier($modifierlist, $value) {
498
		return $this->modifierCompiler->compile([], $this, ['modifierlist' => $modifierlist, 'value' => $value]);
499
	}
500
501
	/**
502
	 * compile variable
503
	 *
504
	 * @param string $variable
505
	 *
506
	 * @return string
507
	 */
508
	public function triggerTagNoCache($variable): void {
509
		if (!strpos($variable, '(')) {
510
			// not a variable variable
511
			$var = trim($variable, '\'');
512
			$this->tag_nocache = $this->tag_nocache |
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
Documentation Bug introduced by
The property $tag_nocache was declared of type boolean, but $this->tag_nocache | $th...ue, false)->isNocache() is of type integer. 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...
513
				$this->template->getVariable(
514
					$var,
515
					true,
516
					false
517
				)->isNocache();
518
		}
519
	}
520
521
	/**
522
	 * compile config variable
523
	 *
524
	 * @param string $variable
525
	 *
526
	 * @return string
527
	 */
528
	public function compileConfigVariable($variable) {
529
		// return '$_smarty_tpl->config_vars[' . $variable . ']';
530
		return '$_smarty_tpl->getConfigVariable(' . $variable . ')';
531
	}
532
533
	/**
534
	 * This method is called from parser to process a text content section if strip is enabled
535
	 * - remove text from inheritance child templates as they may generate output
536
	 *
537
	 * @param string $text
538
	 *
539
	 * @return string
540
	 */
541
	public function processText($text) {
542
543
		if (strpos($text, '<') === false) {
544
			return preg_replace($this->stripRegEx, '', $text);
545
		}
546
547
		$store = [];
548
		$_store = 0;
549
550
		// capture html elements not to be messed with
551
		$_offset = 0;
552
		if (preg_match_all(
553
			'#(<script[^>]*>.*?</script[^>]*>)|(<textarea[^>]*>.*?</textarea[^>]*>)|(<pre[^>]*>.*?</pre[^>]*>)#is',
554
			$text,
555
			$matches,
556
			PREG_OFFSET_CAPTURE | PREG_SET_ORDER
557
		)
558
		) {
559
			foreach ($matches as $match) {
560
				$store[] = $match[0][0];
561
				$_length = strlen($match[0][0]);
562
				$replace = '@!@SMARTY:' . $_store . ':SMARTY@!@';
563
				$text = substr_replace($text, $replace, $match[0][1] - $_offset, $_length);
564
				$_offset += $_length - strlen($replace);
565
				$_store++;
566
			}
567
		}
568
		$expressions = [// replace multiple spaces between tags by a single space
569
			'#(:SMARTY@!@|>)[\040\011]+(?=@!@SMARTY:|<)#s' => '\1 \2',
570
			// remove newline between tags
571
			'#(:SMARTY@!@|>)[\040\011]*[\n]\s*(?=@!@SMARTY:|<)#s' => '\1\2',
572
			// remove multiple spaces between attributes (but not in attribute values!)
573
			'#(([a-z0-9]\s*=\s*("[^"]*?")|(\'[^\']*?\'))|<[a-z0-9_]+)\s+([a-z/>])#is' => '\1 \5',
574
			'#>[\040\011]+$#Ss' => '> ',
575
			'#>[\040\011]*[\n]\s*$#Ss' => '>',
576
			$this->stripRegEx => '',
577
		];
578
		$text = preg_replace(array_keys($expressions), array_values($expressions), $text);
579
		$_offset = 0;
580
		if (preg_match_all(
581
			'#@!@SMARTY:([0-9]+):SMARTY@!@#is',
582
			$text,
583
			$matches,
584
			PREG_OFFSET_CAPTURE | PREG_SET_ORDER
585
		)
586
		) {
587
			foreach ($matches as $match) {
588
				$_length = strlen($match[0][0]);
589
				$replace = $store[$match[1][0]];
590
				$text = substr_replace($text, $replace, $match[0][1] + $_offset, $_length);
591
				$_offset += strlen($replace) - $_length;
592
				$_store++;
593
			}
594
		}
595
		return $text;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $text also could return the type array which is incompatible with the documented return type string.
Loading history...
596
	}
597
598
	/**
599
	 * lazy loads internal compile plugin for tag compile objects cached for reuse.
600
	 *
601
	 * class name format:  \Smarty\Compile\TagName
602
	 *
603
	 * @param string $tag tag name
604
	 *
605
	 * @return ?\Smarty\Compile\CompilerInterface tag compiler object or null if not found or untrusted by security policy
606
	 */
607
	public function getTagCompiler($tag): ?\Smarty\Compile\CompilerInterface {
608
        $tag = strtolower($tag);
609
610
		if (isset($this->smarty->security_policy) && !$this->smarty->security_policy->isTrustedTag($tag, $this)) {
611
			return null;
612
		}
613
614
		foreach ($this->smarty->getExtensions() as $extension) {
615
			if ($compiler = $extension->getTagCompiler($tag)) {
616
				return $compiler;
617
			}
618
		}
619
620
		return null;
621
	}
622
623
	/**
624
	 * lazy loads internal compile plugin for modifier compile objects cached for reuse.
625
	 *
626
	 * @param string $modifier tag name
627
	 *
628
	 * @return bool|\Smarty\Compile\Modifier\ModifierCompilerInterface tag compiler object or false if not found or untrusted by security policy
629
	 */
630
	public function getModifierCompiler($modifier) {
631
632
		if (isset($this->smarty->security_policy) && !$this->smarty->security_policy->isTrustedModifier($modifier, $this)) {
633
			return false;
634
		}
635
636
		foreach ($this->smarty->getExtensions() as $extension) {
637
			if ($modifierCompiler = $extension->getModifierCompiler($modifier)) {
638
				return $modifierCompiler;
639
			}
640
		}
641
642
		return false;
643
	}
644
645
	/**
646
	 * Check for plugins by default plugin handler
647
	 *
648
	 * @param string $tag name of tag
649
	 * @param string $plugin_type type of plugin
650
	 *
651
	 * @return callback|null
652
	 * @throws \Smarty\CompilerException
653
	 */
654
	public function getPluginFromDefaultHandler($tag, $plugin_type) {
655
656
		$defaultPluginHandlerFunc = $this->smarty->getDefaultPluginHandlerFunc();
657
658
		if (!is_callable($defaultPluginHandlerFunc)) {
659
			return null;
660
		}
661
662
663
		$callback = null;
664
		$script = null;
665
		$cacheable = true;
666
667
		$result = \call_user_func_array(
668
			$defaultPluginHandlerFunc,
669
			[
670
				$tag,
671
				$plugin_type,
672
				null, // This used to pass $this->template, but this parameter has been removed in 5.0
673
				&$callback,
674
				&$script,
675
				&$cacheable,
676
			]
677
		);
678
		if ($result) {
679
			$this->tag_nocache = $this->tag_nocache || !$cacheable;
680
			if ($script !== null) {
0 ignored issues
show
introduced by
The condition $script !== null is always false.
Loading history...
681
				if (is_file($script)) {
682
					include_once $script;
683
				} else {
684
					$this->trigger_template_error("Default plugin handler: Returned script file '{$script}' for '{$tag}' not found");
685
				}
686
			}
687
			if (is_callable($callback)) {
688
				return $callback;
689
			} else {
690
				$this->trigger_template_error("Default plugin handler: Returned callback for '{$tag}' not callable");
691
			}
692
		}
693
		return null;
694
	}
695
696
	/**
697
	 * Append code segments and remove unneeded ?> <?php transitions
698
	 *
699
	 * @param string $left
700
	 * @param string $right
701
	 *
702
	 * @return string
703
	 */
704
	public function appendCode(string $left, string $right): string
705
	{
706
		if (preg_match('/\s*\?>\s?$/D', $left) && preg_match('/^<\?php\s+/', $right)) {
707
			$left = preg_replace('/\s*\?>\s?$/D', "\n", $left);
708
			$left .= preg_replace('/^<\?php\s+/', '', $right);
709
		} else {
710
			$left .= $right;
711
		}
712
		return $left;
713
	}
714
715
	/**
716
	 * Inject inline code for nocache template sections
717
	 * This method gets the content of each template element from the parser.
718
	 * If the content is compiled code, and it should be not be cached the code is injected
719
	 * into the rendered output.
720
	 *
721
	 * @param string $content content of template element
722
	 *
723
	 * @return string  content
724
	 */
725
	public function processNocacheCode($content) {
726
727
		// If the template is not evaluated, and we have a nocache section and/or a nocache tag
728
		// generate replacement code
729
		if (!empty($content)
730
			&& !($this->template->getSource()->handler->recompiled)
731
			&& $this->caching
732
			&& $this->isNocacheActive()
733
		) {
734
			$this->template->getCompiled()->setNocacheCode(true);
735
			$_output = addcslashes($content, '\'\\');
736
			$_output =
737
				"<?php echo '" . $this->getNocacheBlockStartMarker() . $_output . $this->getNocacheBlockEndMarker() . "';?>\n";
738
		} else {
739
			$_output = $content;
740
		}
741
742
		$this->modifier_plugins = [];
743
		$this->suppressNocacheProcessing = false;
744
		$this->tag_nocache = false;
745
		return $_output;
746
	}
747
748
749
	private function getNocacheBlockStartMarker(): string {
750
		return "/*%%SmartyNocache:{$this->nocache_hash}%%*/";
751
	}
752
753
	private function getNocacheBlockEndMarker(): string {
754
		return "/*/%%SmartyNocache:{$this->nocache_hash}%%*/";
755
	}
756
757
758
	/**
759
	 * Get Id
760
	 *
761
	 * @param string $input
762
	 *
763
	 * @return bool|string
764
	 */
765
	public function getId($input) {
766
		if (preg_match('~^([\'"]*)([0-9]*[a-zA-Z_]\w*)\1$~', $input, $match)) {
767
			return $match[2];
768
		}
769
		return false;
770
	}
771
772
	/**
773
	 * Set nocache flag in variable or create new variable
774
	 *
775
	 * @param string $varName
776
	 */
777
	public function setNocacheInVariable($varName) {
778
		// create nocache var to make it know for further compiling
779
		if ($_var = $this->getId($varName)) {
780
			if ($this->template->hasVariable($_var)) {
781
				$this->template->getVariable($_var)->setNocache(true);
0 ignored issues
show
Bug introduced by
It seems like $_var can also be of type true; however, parameter $varName of Smarty\Data::getVariable() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

781
				$this->template->getVariable(/** @scrutinizer ignore-type */ $_var)->setNocache(true);
Loading history...
782
			} else {
783
				$this->template->assign($_var, null, true);
784
			}
785
		}
786
	}
787
788
	/**
789
	 * display compiler error messages without dying
790
	 * If parameter $args is empty it is a parser detected syntax error.
791
	 * In this case the parser is called to obtain information about expected tokens.
792
	 * If parameter $args contains a string this is used as error message
793
	 *
794
	 * @param string $args individual error message or null
795
	 * @param string $line line-number
796
	 * @param null|bool $tagline if true the line number of last tag
797
	 *
798
	 * @throws \Smarty\CompilerException when an unexpected token is found
799
	 */
800
	public function trigger_template_error($args = null, $line = null, $tagline = null) {
801
		$lex = $this->parser->lex;
802
		if ($tagline === true) {
803
			// get line number of Tag
804
			$line = $lex->taglineno;
805
		} elseif (!isset($line)) {
806
			// get template source line which has error
807
			$line = $lex->line;
808
		} else {
809
			$line = (int)$line;
810
		}
811
		if (in_array(
812
			$this->template->getSource()->type,
813
			[
814
				'eval',
815
				'string',
816
			]
817
		)
818
		) {
819
			$templateName = $this->template->getSource()->type . ':' . trim(
820
					preg_replace(
821
						'![\t\r\n]+!',
822
						' ',
823
						strlen($lex->data) > 40 ?
824
							substr($lex->data, 0, 40) .
825
							'...' : $lex->data
826
					)
827
				);
828
		} else {
829
			$templateName = $this->template->getSource()->getFullResourceName();
830
		}
831
		//        $line += $this->trace_line_offset;
832
		$match = preg_split("/\n/", $lex->data);
833
		$error_text =
834
			'Syntax error in template "' . (empty($this->trace_filepath) ? $templateName : $this->trace_filepath) .
835
			'"  on line ' . ($line + $this->trace_line_offset) . ' "' .
836
			trim(preg_replace('![\t\r\n]+!', ' ', $match[$line - 1])) . '" ';
837
		if (isset($args)) {
838
			// individual error message
839
			$error_text .= $args;
840
		} else {
841
			$expect = [];
842
			// expected token from parser
843
			$error_text .= ' - Unexpected "' . $lex->value . '"';
844
			if (count($this->parser->yy_get_expected_tokens($this->parser->yymajor)) <= 4) {
845
				foreach ($this->parser->yy_get_expected_tokens($this->parser->yymajor) as $token) {
846
					$exp_token = $this->parser->yyTokenName[$token];
847
					if (isset($lex->smarty_token_names[$exp_token])) {
848
						// token type from lexer
849
						$expect[] = '"' . $lex->smarty_token_names[$exp_token] . '"';
850
					} else {
851
						// otherwise internal token name
852
						$expect[] = $this->parser->yyTokenName[$token];
853
					}
854
				}
855
				$error_text .= ', expected one of: ' . implode(' , ', $expect);
856
			}
857
		}
858
		if ($this->smarty->_parserdebug) {
859
			$this->parser->errorRunDown();
860
			echo ob_get_clean();
861
			flush();
862
		}
863
		$e = new CompilerException(
864
			$error_text,
865
			0,
866
			$this->template->getSource()->getFilepath() ?? $this->template->getSource()->getFullResourceName(),
867
			$line
868
		);
869
		$e->source = trim(preg_replace('![\t\r\n]+!', ' ', $match[$line - 1]));
870
		$e->desc = $args;
871
		$e->template = $this->template->getSource()->getFullResourceName();
872
		throw $e;
873
	}
874
875
	/**
876
	 * Return var_export() value with all white spaces removed
877
	 *
878
	 * @param mixed $value
879
	 *
880
	 * @return string
881
	 */
882
	public function getVarExport($value) {
883
		return preg_replace('/\s/', '', var_export($value, true));
884
	}
885
886
	/**
887
	 *  enter double quoted string
888
	 *  - save tag stack count
889
	 */
890
	public function enterDoubleQuote() {
891
		array_push($this->_tag_stack_count, $this->getTagStackCount());
892
	}
893
894
	/**
895
	 * Return tag stack count
896
	 *
897
	 * @return int
898
	 */
899
	public function getTagStackCount() {
900
		return count($this->_tag_stack);
901
	}
902
903
	/**
904
	 * @param $lexerPreg
905
	 *
906
	 * @return mixed
907
	 */
908
	public function replaceDelimiter($lexerPreg) {
909
		return str_replace(
910
			['SMARTYldel', 'SMARTYliteral', 'SMARTYrdel', 'SMARTYautoliteral', 'SMARTYal'],
911
			[
912
				$this->ldelPreg, $this->literalPreg, $this->rdelPreg,
913
				$this->smarty->getAutoLiteral() ? '{1,}' : '{9}',
914
				$this->smarty->getAutoLiteral() ? '' : '\\s*',
915
			],
916
			$lexerPreg
917
		);
918
	}
919
920
	/**
921
	 * Build lexer regular expressions for left and right delimiter and user defined literals
922
	 */
923
	public function initDelimiterPreg() {
924
		$ldel = $this->smarty->getLeftDelimiter();
925
		$this->ldelLength = strlen($ldel);
926
		$this->ldelPreg = '';
927
		foreach (str_split($ldel, 1) as $chr) {
928
			$this->ldelPreg .= '[' . preg_quote($chr, '/') . ']';
929
		}
930
		$rdel = $this->smarty->getRightDelimiter();
931
		$this->rdelLength = strlen($rdel);
932
		$this->rdelPreg = '';
933
		foreach (str_split($rdel, 1) as $chr) {
934
			$this->rdelPreg .= '[' . preg_quote($chr, '/') . ']';
935
		}
936
		$literals = $this->smarty->getLiterals();
937
		if (!empty($literals)) {
938
			foreach ($literals as $key => $literal) {
939
				$literalPreg = '';
940
				foreach (str_split($literal, 1) as $chr) {
941
					$literalPreg .= '[' . preg_quote($chr, '/') . ']';
942
				}
943
				$literals[$key] = $literalPreg;
944
			}
945
			$this->literalPreg = '|' . implode('|', $literals);
946
		} else {
947
			$this->literalPreg = '';
948
		}
949
	}
950
951
	/**
952
	 *  leave double quoted string
953
	 *  - throw exception if block in string was not closed
954
	 *
955
	 * @throws \Smarty\CompilerException
956
	 */
957
	public function leaveDoubleQuote() {
958
		if (array_pop($this->_tag_stack_count) !== $this->getTagStackCount()) {
959
			$tag = $this->getOpenBlockTag();
960
			$this->trigger_template_error(
961
				"unclosed '{{$tag}}' in doubled quoted string",
962
				null,
963
				true
964
			);
965
		}
966
	}
967
968
	/**
969
	 * Get left delimiter preg
970
	 *
971
	 * @return string
972
	 */
973
	public function getLdelPreg() {
974
		return $this->ldelPreg;
975
	}
976
977
	/**
978
	 * Get right delimiter preg
979
	 *
980
	 * @return string
981
	 */
982
	public function getRdelPreg() {
983
		return $this->rdelPreg;
984
	}
985
986
	/**
987
	 * Get length of left delimiter
988
	 *
989
	 * @return int
990
	 */
991
	public function getLdelLength() {
992
		return $this->ldelLength;
993
	}
994
995
	/**
996
	 * Get length of right delimiter
997
	 *
998
	 * @return int
999
	 */
1000
	public function getRdelLength() {
1001
		return $this->rdelLength;
1002
	}
1003
1004
	/**
1005
	 * Get name of current open block tag
1006
	 *
1007
	 * @return string|boolean
1008
	 */
1009
	public function getOpenBlockTag() {
1010
		$tagCount = $this->getTagStackCount();
1011
		if ($tagCount) {
1012
			return $this->_tag_stack[$tagCount - 1][0];
1013
		} else {
1014
			return false;
1015
		}
1016
	}
1017
1018
	/**
1019
	 * Check if $value contains variable elements
1020
	 *
1021
	 * @param mixed $value
1022
	 *
1023
	 * @return bool|int
1024
	 */
1025
	public function isVariable($value) {
1026
		if (is_string($value)) {
1027
			return preg_match('/[$(]/', $value);
1028
		}
1029
		if (is_bool($value) || is_numeric($value)) {
1030
			return false;
1031
		}
1032
		if (is_array($value)) {
1033
			foreach ($value as $k => $v) {
1034
				if ($this->isVariable($k) || $this->isVariable($v)) {
1035
					return true;
1036
				}
1037
			}
1038
			return false;
1039
		}
1040
		return false;
1041
	}
1042
1043
	/**
1044
	 * Get new prefix variable name
1045
	 *
1046
	 * @return string
1047
	 */
1048
	public function getNewPrefixVariable() {
1049
		++self::$prefixVariableNumber;
1050
		return $this->getPrefixVariable();
1051
	}
1052
1053
	/**
1054
	 * Get current prefix variable name
1055
	 *
1056
	 * @return string
1057
	 */
1058
	public function getPrefixVariable() {
1059
		return '$_prefixVariable' . self::$prefixVariableNumber;
1060
	}
1061
1062
	/**
1063
	 * append  code to prefix buffer
1064
	 *
1065
	 * @param string $code
1066
	 */
1067
	public function appendPrefixCode($code) {
1068
		$this->prefix_code[] = $code;
1069
	}
1070
1071
	/**
1072
	 * get prefix code string
1073
	 *
1074
	 * @return string
1075
	 */
1076
	public function getPrefixCode() {
1077
		$code = '';
1078
		$prefixArray = array_merge($this->prefix_code, array_pop($this->prefixCodeStack));
1079
		$this->prefixCodeStack[] = [];
1080
		foreach ($prefixArray as $c) {
1081
			$code = $this->appendCode($code, (string) $c);
1082
		}
1083
		$this->prefix_code = [];
1084
		return $code;
1085
	}
1086
1087
	public function cStyleComment($string) {
1088
		return '/*' . str_replace('*/', '* /', $string) . '*/';
1089
	}
1090
1091
	public function compileChildBlock() {
1092
		return $this->blockCompiler->compileChild($this);
1093
	}
1094
1095
	public function compileParentBlock() {
1096
		return $this->blockCompiler->compileParent($this);
1097
	}
1098
1099
	/**
1100
	 * Compile Tag
1101
	 *
1102
	 * @param string $tag tag name
1103
	 * @param array $args array with tag attributes
1104
	 * @param array $parameter array with compilation parameter
1105
	 *
1106
	 * @return string compiled code
1107
	 * @throws Exception
1108
	 * @throws CompilerException
1109
	 */
1110
	private function compileTag2($tag, $args, $parameter) {
1111
		// $args contains the attributes parsed and compiled by the lexer/parser
1112
1113
		$this->handleNocacheFlag($args);
1114
1115
		// compile built-in tags
1116
		if ($tagCompiler = $this->getTagCompiler($tag)) {
1117
			if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($tag, $this)) {
1118
				$this->tag_nocache = $this->tag_nocache | !$tagCompiler->isCacheable();
0 ignored issues
show
Documentation Bug introduced by
The property $tag_nocache was declared of type boolean, but $this->tag_nocache | ! $...Compiler->isCacheable() is of type integer. 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...
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
1119
				$_output = $tagCompiler->compile($args, $this, $parameter);
1120
				if (!empty($parameter['modifierlist'])) {
1121
					throw new CompilerException('No modifiers allowed on ' . $tag);
1122
				}
1123
				return $_output;
1124
			}
1125
		}
1126
1127
		// call to function previously defined by {function} tag
1128
		if ($this->canCompileTemplateFunctionCall($tag)) {
1129
1130
			if (!empty($parameter['modifierlist'])) {
1131
				throw new CompilerException('No modifiers allowed on ' . $tag);
1132
			}
1133
1134
			$args['_attr']['name'] = "'{$tag}'";
1135
			$tagCompiler = $this->getTagCompiler('call');
1136
			return $tagCompiler === null ? false : $tagCompiler->compile($args, $this, $parameter);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $tagCompiler === ...rgs, $this, $parameter) could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1137
		}
1138
1139
		// remaining tastes: (object-)function, (object-function-)block, custom-compiler
1140
		// opening and closing tags for these are handled with the same handler
1141
		$base_tag = $this->getBaseTag($tag);
1142
1143
		// check if tag is a registered object
1144
		if (isset($this->smarty->registered_objects[$base_tag]) && isset($parameter['object_method'])) {
1145
			return $this->compileRegisteredObjectMethodCall($base_tag, $args, $parameter, $tag);
1146
		}
1147
1148
		// check if tag is a function
1149
		if ($this->smarty->getFunctionHandler($base_tag)) {
1150
			if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($base_tag, $this)) {
1151
				return (new \Smarty\Compile\PrintExpressionCompiler())->compile(
1152
					['nofilter'], // functions are never auto-escaped
1153
					$this,
1154
					['value' =>	$this->compileFunctionCall($base_tag, $args, $parameter)]
1155
				);
1156
			}
1157
		}
1158
1159
		// check if tag is a block
1160
		if ($this->smarty->getBlockHandler($base_tag)) {
1161
			if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($base_tag, $this)) {
1162
				return $this->blockCompiler->compile($args, $this, $parameter, $tag, $base_tag);
1163
			}
1164
		}
1165
1166
		// the default plugin handler is a handler of last resort, it may also handle not specifically registered tags.
1167
		if ($callback = $this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_COMPILER)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $callback is correct as $this->getPluginFromDefa...marty::PLUGIN_COMPILER) targeting Smarty\Compiler\Template...ginFromDefaultHandler() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1168
			if (!empty($parameter['modifierlist'])) {
1169
				throw new CompilerException('No modifiers allowed on ' . $base_tag);
1170
			}
1171
			$tagCompiler = new \Smarty\Compile\Tag\BCPluginWrapper($callback);
1172
			return $tagCompiler->compile($args, $this, $parameter);
1173
		}
1174
1175
		if ($this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_FUNCTION)) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getPluginFromDefa...marty::PLUGIN_FUNCTION) targeting Smarty\Compiler\Template...ginFromDefaultHandler() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1176
			return $this->defaultHandlerFunctionCallCompiler->compile($args, $this, $parameter, $tag, $base_tag);
1177
		}
1178
1179
		if ($this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_BLOCK)) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getPluginFromDefa...y\Smarty::PLUGIN_BLOCK) targeting Smarty\Compiler\Template...ginFromDefaultHandler() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1180
			return $this->defaultHandlerBlockCompiler->compile($args, $this, $parameter, $tag, $base_tag);
1181
		}
1182
1183
		$this->trigger_template_error("unknown tag '{$tag}'", null, true);
1184
	}
1185
1186
	/**
1187
	 * Sets $this->tag_nocache if attributes contain the 'nocache' flag.
1188
	 *
1189
	 * @param array $attributes
1190
	 *
1191
	 * @return void
1192
	 */
1193
	private function handleNocacheFlag(array $attributes) {
1194
		foreach ($attributes as $value) {
1195
			if (is_string($value) && trim($value, '\'" ') == 'nocache') {
1196
				$this->tag_nocache = true;
1197
			}
1198
		}
1199
	}
1200
1201
	private function getBaseTag($tag) {
1202
		if (strlen($tag) < 6 || substr($tag, -5) !== 'close') {
1203
			return $tag;
1204
		} else {
1205
			return substr($tag, 0, -5);
1206
		}
1207
	}
1208
1209
	/**
1210
	 * Compiles the output of a variable or expression.
1211
	 *
1212
	 * @param $value
1213
	 * @param $attributes
1214
	 * @param $modifiers
1215
	 *
1216
	 * @return string
1217
	 * @throws Exception
1218
	 */
1219
	public function compilePrintExpression($value, $attributes = [], $modifiers = null) {
1220
		$this->handleNocacheFlag($attributes);
1221
		return $this->printExpressionCompiler->compile($attributes, $this, [
1222
			'value'=> $value,
1223
			'modifierlist' => $modifiers,
1224
		]);
1225
	}
1226
1227
	/**
1228
	 * method to compile a Smarty template
1229
	 *
1230
	 * @param mixed $_content template source
1231
	 * @param bool $isTemplateSource
1232
	 *
1233
	 * @return bool true if compiling succeeded, false if it failed
1234
	 * @throws \Smarty\CompilerException
1235
	 */
1236
	protected function doCompile($_content, $isTemplateSource = false) {
1237
		/* here is where the compiling takes place. Smarty
1238
		  tags in the templates are replaces with PHP code,
1239
		  then written to compiled files. */
1240
		// init the lexer/parser to compile the template
1241
		$this->parser = new TemplateParser(
1242
				new TemplateLexer(
1243
					str_replace(
1244
						[
1245
							"\r\n",
1246
							"\r",
1247
						],
1248
						"\n",
1249
						$_content
1250
					),
1251
					$this
1252
				),
1253
				$this
1254
			);
1255
		if ($isTemplateSource && $this->template->caching) {
1256
			$this->parser->insertPhpCode("<?php\n\$_smarty_tpl->getCompiled()->nocache_hash = '{$this->nocache_hash}';\n?>\n");
1257
		}
1258
		if ($this->smarty->_parserdebug) {
1259
			$this->parser->PrintTrace();
1260
			$this->parser->lex->PrintTrace();
1261
		}
1262
		// get tokens from lexer and parse them
1263
		while ($this->parser->lex->yylex()) {
1264
			if ($this->smarty->_parserdebug) {
1265
				echo "Line {$this->parser->lex->line} Parsing  {$this->parser->yyTokenName[$this->parser->lex->token]} Token " .
1266
					$this->parser->lex->value;
1267
			}
1268
			$this->parser->doParse($this->parser->lex->token, $this->parser->lex->value);
1269
		}
1270
		// finish parsing process
1271
		$this->parser->doParse(0, 0);
1272
		// check for unclosed tags
1273
		if ($this->getTagStackCount() > 0) {
1274
			// get stacked info
1275
			[$openTag, $_data] = array_pop($this->_tag_stack);
1276
			$this->trigger_template_error(
1277
				"unclosed " . $this->smarty->getLeftDelimiter() . $openTag .
1278
				$this->smarty->getRightDelimiter() . " tag"
1279
			);
1280
		}
1281
		// call post compile callbacks
1282
		foreach ($this->postCompileCallbacks as $cb) {
1283
			$callbackFunction = $cb[0];
1284
			$parameters = $cb;
1285
			$parameters[0] = $this;
1286
			$callbackFunction(...$parameters);
1287
		}
1288
		// return compiled code
1289
		return $this->prefixCompiledCode . $this->parser->retvalue . $this->postfixCompiledCode;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->prefixComp...is->postfixCompiledCode returns the type string which is incompatible with the documented return type boolean.
Loading history...
1290
	}
1291
1292
	/**
1293
	 * Register a post compile callback
1294
	 * - when the callback is called after template compiling the compiler object will be inserted as first parameter
1295
	 *
1296
	 * @param callback $callback
1297
	 * @param array $parameter optional parameter array
1298
	 * @param string $key optional key for callback
1299
	 * @param bool $replace if true replace existing keyed callback
1300
	 */
1301
	public function registerPostCompileCallback($callback, $parameter = [], $key = null, $replace = false) {
1302
		array_unshift($parameter, $callback);
1303
		if (isset($key)) {
1304
			if ($replace || !isset($this->postCompileCallbacks[$key])) {
1305
				$this->postCompileCallbacks[$key] = $parameter;
1306
			}
1307
		} else {
1308
			$this->postCompileCallbacks[] = $parameter;
1309
		}
1310
	}
1311
1312
	/**
1313
	 * Remove a post compile callback
1314
	 *
1315
	 * @param string $key callback key
1316
	 */
1317
	public function unregisterPostCompileCallback($key) {
1318
		unset($this->postCompileCallbacks[$key]);
1319
	}
1320
1321
	/**
1322
	 * @param string $tag
1323
	 *
1324
	 * @return bool
1325
	 * @throws Exception
1326
	 */
1327
	private function canCompileTemplateFunctionCall(string $tag): bool {
1328
		return
1329
			isset($this->parent_compiler->tpl_function[$tag])
1330
			|| (
1331
				$this->template->getSmarty()->hasRuntime('TplFunction')
1332
				&& ($this->template->getSmarty()->getRuntime('TplFunction')->getTplFunction($this->template, $tag) !== false)
1333
			);
1334
	}
1335
1336
	/**
1337
	 * @throws CompilerException
1338
	 */
1339
	private function compileRegisteredObjectMethodCall(string $base_tag, array $args, array $parameter, string $tag) {
1340
1341
		$method = $parameter['object_method'];
1342
		$allowedAsBlockFunction = in_array($method, $this->smarty->registered_objects[$base_tag][3]);
1343
1344
		if ($base_tag === $tag) {
1345
			// opening tag
1346
1347
			$allowedAsNormalFunction = empty($this->smarty->registered_objects[$base_tag][1])
1348
				|| in_array($method, $this->smarty->registered_objects[$base_tag][1]);
1349
1350
			if ($allowedAsBlockFunction) {
1351
				return $this->objectMethodBlockCompiler->compile($args, $this, $parameter, $tag, $method);
1352
			} elseif ($allowedAsNormalFunction) {
1353
				return $this->objectMethodCallCompiler->compile($args, $this, $parameter, $tag, $method);
1354
			}
1355
1356
			$this->trigger_template_error(
1357
				'not allowed method "' . $method . '" in registered object "' .
1358
				$tag . '"',
1359
				null,
1360
				true
1361
			);
1362
		}
1363
1364
		// closing tag
1365
		if ($allowedAsBlockFunction) {
1366
			return $this->objectMethodBlockCompiler->compile($args, $this, $parameter, $tag, $method);
1367
		}
1368
1369
		$this->trigger_template_error(
1370
			'not allowed closing tag method "' . $method .
1371
			'" in registered object "' . $base_tag . '"',
1372
			null,
1373
			true
1374
		);
1375
	}
1376
1377
	public function compileFunctionCall(string $base_tag, array $args, array $parameter = []) {
1378
		return $this->functionCallCompiler->compile($args, $this, $parameter, $base_tag, $base_tag);
1379
	}
1380
1381
	public function compileModifierInExpression(string $function, array $_attr) {
1382
		$value = array_shift($_attr);
1383
		return $this->compileModifier([array_merge([$function], $_attr)], $value);
1384
	}
1385
1386
	/**
1387
	 * @return TemplateParser|null
1388
	 */
1389
	public function getParser(): ?TemplateParser {
1390
		return $this->parser;
1391
	}
1392
1393
	/**
1394
	 * @param TemplateParser|null $parser
1395
	 */
1396
	public function setParser(?TemplateParser $parser): void {
1397
		$this->parser = $parser;
1398
	}
1399
1400
	/**
1401
	 * @return \Smarty\Template|null
1402
	 */
1403
	public function getTemplate(): ?\Smarty\Template {
1404
		return $this->template;
1405
	}
1406
1407
	/**
1408
	 * @param \Smarty\Template|null $template
1409
	 */
1410
	public function setTemplate(?\Smarty\Template $template): void {
1411
		$this->template = $template;
1412
	}
1413
1414
	/**
1415
	 * @return Template|null
1416
	 */
1417
	public function getParentCompiler(): ?Template {
1418
		return $this->parent_compiler;
1419
	}
1420
1421
	/**
1422
	 * @param Template|null $parent_compiler
1423
	 */
1424
	public function setParentCompiler(?Template $parent_compiler): void {
1425
		$this->parent_compiler = $parent_compiler;
1426
	}
1427
1428
1429
	/**
1430
	 * Push opening tag name on stack
1431
	 * Optionally additional data can be saved on stack
1432
	 *
1433
	 * @param string $openTag the opening tag's name
1434
	 * @param mixed $data optional data saved
1435
	 */
1436
	public function openTag($openTag, $data = null) {
1437
		$this->_tag_stack[] = [$openTag, $data];
1438
		if ($openTag == 'nocache') {
1439
			$this->noCacheStackDepth++;
1440
		}
1441
	}
1442
1443
	/**
1444
	 * Pop closing tag
1445
	 * Raise an error if this stack-top doesn't match with expected opening tags
1446
	 *
1447
	 * @param array|string $expectedTag the expected opening tag names
1448
	 *
1449
	 * @return mixed        any type the opening tag's name or saved data
1450
	 * @throws CompilerException
1451
	 */
1452
	public function closeTag($expectedTag) {
1453
		if ($this->getTagStackCount() > 0) {
1454
			// get stacked info
1455
			[$_openTag, $_data] = array_pop($this->_tag_stack);
1456
			// open tag must match with the expected ones
1457
			if (in_array($_openTag, (array)$expectedTag)) {
1458
1459
				if ($_openTag == 'nocache') {
1460
					$this->noCacheStackDepth--;
1461
				}
1462
1463
				if (is_null($_data)) {
1464
					// return opening tag
1465
					return $_openTag;
1466
				} else {
1467
					// return restored data
1468
					return $_data;
1469
				}
1470
			}
1471
			// wrong nesting of tags
1472
			$this->trigger_template_error("unclosed '" . $this->getTemplate()->getLeftDelimiter() . "{$_openTag}" .
1473
				$this->getTemplate()->getRightDelimiter() . "' tag");
1474
			return;
1475
		}
1476
		// wrong nesting of tags
1477
		$this->trigger_template_error('unexpected closing tag', null, true);
1478
	}
1479
1480
	/**
1481
	 * Returns true if we are in a {nocache}...{/nocache} block, but false if inside {block} tag inside a {nocache} block...
1482
	 * @return bool
1483
	 */
1484
	public function isNocacheActive(): bool {
1485
		return !$this->suppressNocacheProcessing && ($this->noCacheStackDepth > 0 || $this->tag_nocache);
1486
	}
1487
1488
	/**
1489
	 * Returns the full tag stack, used in the compiler for {break}
1490
	 * @return array
1491
	 */
1492
	public function getTagStack(): array {
1493
		return $this->_tag_stack;
1494
	}
1495
1496
	/**
1497
	 * Should the next variable output be raw (true) or auto-escaped (false)
1498
	 * @return bool
1499
	 */
1500
	public function isRawOutput(): bool {
1501
		return $this->raw_output;
1502
	}
1503
1504
	/**
1505
	 * Should the next variable output be raw (true) or auto-escaped (false)
1506
	 * @param bool $raw_output
1507
	 * @return void
1508
	 */
1509
	public function setRawOutput(bool $raw_output): void {
1510
		$this->raw_output = $raw_output;
1511
	}
1512
}
1513