summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/BlockHandler/Base.php19
-rw-r--r--src/BlockHandler/BlockHandlerInterface.php10
-rw-r--r--src/BlockHandler/BlockPluginWrapper.php19
-rw-r--r--src/BlockHandler/TextFormat.php113
-rw-r--r--src/Cacheresource/Base.php156
-rw-r--r--src/Cacheresource/Custom.php303
-rw-r--r--src/Cacheresource/File.php338
-rw-r--r--src/Cacheresource/KeyValueStore.php541
-rw-r--r--src/Compile/Base.php233
-rw-r--r--src/Compile/BlockCompiler.php229
-rw-r--r--src/Compile/CompilerInterface.php26
-rw-r--r--src/Compile/DefaultHandlerBlockCompiler.php29
-rw-r--r--src/Compile/DefaultHandlerFunctionCallCompiler.php46
-rw-r--r--src/Compile/FunctionCallCompiler.php82
-rw-r--r--src/Compile/Modifier/BCPluginWrapper.php19
-rw-r--r--src/Compile/Modifier/Base.php49
-rw-r--r--src/Compile/Modifier/CatModifierCompiler.php27
-rw-r--r--src/Compile/Modifier/CountCharactersModifierCompiler.php23
-rw-r--r--src/Compile/Modifier/CountParagraphsModifierCompiler.php21
-rw-r--r--src/Compile/Modifier/CountSentencesModifierCompiler.php21
-rw-r--r--src/Compile/Modifier/CountWordsModifierCompiler.php21
-rw-r--r--src/Compile/Modifier/DefaultModifierCompiler.php27
-rw-r--r--src/Compile/Modifier/EmptyModifierCompiler.php19
-rw-r--r--src/Compile/Modifier/EscapeModifierCompiler.php57
-rw-r--r--src/Compile/Modifier/FromCharsetModifierCompiler.php21
-rw-r--r--src/Compile/Modifier/IndentModifierCompiler.php25
-rw-r--r--src/Compile/Modifier/IssetModifierCompiler.php25
-rw-r--r--src/Compile/Modifier/LowerModifierCompiler.php20
-rw-r--r--src/Compile/Modifier/ModifierCompilerInterface.php17
-rw-r--r--src/Compile/Modifier/Nl2brModifierCompiler.php18
-rw-r--r--src/Compile/Modifier/NoPrintModifierCompiler.php18
-rw-r--r--src/Compile/Modifier/RoundModifierCompiler.php19
-rw-r--r--src/Compile/Modifier/StrRepeatModifierCompiler.php18
-rw-r--r--src/Compile/Modifier/StringFormatModifierCompiler.php19
-rw-r--r--src/Compile/Modifier/StripModifierCompiler.php25
-rw-r--r--src/Compile/Modifier/StripTagsModifierCompiler.php23
-rw-r--r--src/Compile/Modifier/StrlenModifierCompiler.php19
-rw-r--r--src/Compile/Modifier/ToCharsetModifierCompiler.php21
-rw-r--r--src/Compile/Modifier/UnescapeModifierCompiler.php34
-rw-r--r--src/Compile/Modifier/UpperModifierCompiler.php19
-rw-r--r--src/Compile/Modifier/WordWrapModifierCompiler.php28
-rw-r--r--src/Compile/ModifierCompiler.php93
-rw-r--r--src/Compile/ObjectMethodBlockCompiler.php44
-rw-r--r--src/Compile/ObjectMethodCallCompiler.php75
-rw-r--r--src/Compile/PrintExpressionCompiler.php96
-rw-r--r--src/Compile/SpecialVariableCompiler.php133
-rw-r--r--src/Compile/Tag/Append.php58
-rw-r--r--src/Compile/Tag/Assign.php95
-rw-r--r--src/Compile/Tag/BCPluginWrapper.php30
-rw-r--r--src/Compile/Tag/Block.php91
-rw-r--r--src/Compile/Tag/BlockClose.php111
-rw-r--r--src/Compile/Tag/BreakTag.php123
-rw-r--r--src/Compile/Tag/Call.php80
-rw-r--r--src/Compile/Tag/Capture.php72
-rw-r--r--src/Compile/Tag/CaptureClose.php42
-rw-r--r--src/Compile/Tag/ConfigLoad.php78
-rw-r--r--src/Compile/Tag/ContinueTag.php27
-rw-r--r--src/Compile/Tag/Debug.php44
-rw-r--r--src/Compile/Tag/ElseIfTag.php85
-rw-r--r--src/Compile/Tag/ElseTag.php28
-rw-r--r--src/Compile/Tag/EvalTag.php73
-rw-r--r--src/Compile/Tag/ExtendsTag.php147
-rw-r--r--src/Compile/Tag/ForClose.php50
-rw-r--r--src/Compile/Tag/ForElse.php29
-rw-r--r--src/Compile/Tag/ForTag.php100
-rw-r--r--src/Compile/Tag/ForeachClose.php54
-rw-r--r--src/Compile/Tag/ForeachElse.php34
-rw-r--r--src/Compile/Tag/ForeachSection.php206
-rw-r--r--src/Compile/Tag/ForeachTag.php285
-rw-r--r--src/Compile/Tag/FunctionClose.php163
-rw-r--r--src/Compile/Tag/FunctionTag.php72
-rw-r--r--src/Compile/Tag/IfClose.php47
-rw-r--r--src/Compile/Tag/IfTag.php69
-rw-r--r--src/Compile/Tag/IncludeTag.php188
-rw-r--r--src/Compile/Tag/Inheritance.php54
-rw-r--r--src/Compile/Tag/Ldelim.php40
-rw-r--r--src/Compile/Tag/Nocache.php37
-rw-r--r--src/Compile/Tag/NocacheClose.php38
-rw-r--r--src/Compile/Tag/Rdelim.php35
-rw-r--r--src/Compile/Tag/Section.php398
-rw-r--r--src/Compile/Tag/SectionClose.php47
-rw-r--r--src/Compile/Tag/SectionElse.php28
-rw-r--r--src/Compile/Tag/Setfilter.php41
-rw-r--r--src/Compile/Tag/SetfilterClose.php44
-rw-r--r--src/Compile/Tag/WhileClose.php44
-rw-r--r--src/Compile/Tag/WhileTag.php71
-rw-r--r--src/Compiler/BaseCompiler.php23
-rw-r--r--src/Compiler/CodeFrame.php126
-rw-r--r--src/Compiler/Configfile.php175
-rw-r--r--src/Compiler/Template.php1480
-rw-r--r--src/CompilerException.php73
-rw-r--r--src/Data.php496
-rw-r--r--src/Debug.php380
-rw-r--r--src/ErrorHandler.php97
-rw-r--r--src/Exception.php16
-rw-r--r--src/Extension/BCPluginsAdapter.php229
-rw-r--r--src/Extension/Base.php41
-rw-r--r--src/Extension/CallbackWrapper.php35
-rw-r--r--src/Extension/CoreExtension.php49
-rw-r--r--src/Extension/DefaultExtension.php677
-rw-r--r--src/Extension/ExtensionInterface.php21
-rw-r--r--src/Filter/FilterInterface.php9
-rw-r--r--src/Filter/FilterPluginWrapper.php15
-rw-r--r--src/Filter/Output/TrimWhitespace.php91
-rw-r--r--src/FunctionHandler/BCPluginWrapper.php21
-rw-r--r--src/FunctionHandler/Base.php21
-rw-r--r--src/FunctionHandler/Count.php36
-rw-r--r--src/FunctionHandler/Counter.php63
-rw-r--r--src/FunctionHandler/Cycle.php92
-rw-r--r--src/FunctionHandler/Fetch.php205
-rw-r--r--src/FunctionHandler/FunctionHandlerInterface.php10
-rw-r--r--src/FunctionHandler/HtmlBase.php107
-rw-r--r--src/FunctionHandler/HtmlCheckboxes.php191
-rw-r--r--src/FunctionHandler/HtmlImage.php151
-rw-r--r--src/FunctionHandler/HtmlOptions.php225
-rw-r--r--src/FunctionHandler/HtmlRadios.php176
-rw-r--r--src/FunctionHandler/HtmlSelectDate.php383
-rw-r--r--src/FunctionHandler/HtmlSelectTime.php336
-rw-r--r--src/FunctionHandler/HtmlTable.php163
-rw-r--r--src/FunctionHandler/InArray.php30
-rw-r--r--src/FunctionHandler/IsArray.php21
-rw-r--r--src/FunctionHandler/Mailto.php143
-rw-r--r--src/FunctionHandler/Math.php142
-rw-r--r--src/FunctionHandler/Strlen.php28
-rw-r--r--src/FunctionHandler/Time.php21
-rw-r--r--src/Lexer/ConfigfileLexer.php707
-rw-r--r--src/Lexer/ConfigfileLexer.plex321
-rw-r--r--src/Lexer/TemplateLexer.php1083
-rw-r--r--src/Lexer/TemplateLexer.plex677
-rw-r--r--src/ParseTree/Base.php45
-rw-r--r--src/ParseTree/Code.php45
-rw-r--r--src/ParseTree/Dq.php97
-rw-r--r--src/ParseTree/DqContent.php44
-rw-r--r--src/ParseTree/Tag.php70
-rw-r--r--src/ParseTree/Template.php172
-rw-r--r--src/ParseTree/Text.php59
-rw-r--r--src/Parser/ConfigfileParser.php972
-rw-r--r--src/Parser/ConfigfileParser.y352
-rw-r--r--src/Parser/TemplateParser.php3003
-rw-r--r--src/Parser/TemplateParser.y1305
-rw-r--r--src/Resource/BasePlugin.php145
-rw-r--r--src/Resource/CustomPlugin.php105
-rw-r--r--src/Resource/ExtendsPlugin.php112
-rw-r--r--src/Resource/FilePlugin.php180
-rw-r--r--src/Resource/RecompiledPlugin.php50
-rw-r--r--src/Resource/StreamPlugin.php71
-rw-r--r--src/Resource/StringEval.php85
-rw-r--r--src/Resource/StringPlugin.php94
-rw-r--r--src/Runtime/Block.php92
-rw-r--r--src/Runtime/CaptureRuntime.php163
-rw-r--r--src/Runtime/DefaultPluginHandlerRuntime.php73
-rw-r--r--src/Runtime/ForeachRuntime.php162
-rw-r--r--src/Runtime/InheritanceRuntime.php243
-rw-r--r--src/Runtime/TplFunctionRuntime.php173
-rw-r--r--src/Security.php558
-rw-r--r--src/Smarty.php2236
-rw-r--r--src/Template.php732
-rw-r--r--src/Template/Cached.php428
-rw-r--r--src/Template/Compiled.php302
-rw-r--r--src/Template/Config.php36
-rw-r--r--src/Template/GeneratedPhpFile.php159
-rw-r--r--src/Template/Source.php285
-rw-r--r--src/TemplateBase.php439
-rw-r--r--src/TestInstall.php211
-rw-r--r--src/UndefinedVariable.php19
-rw-r--r--src/Variable.php118
-rw-r--r--src/debug.tpl173
-rw-r--r--src/functions.php253
168 files changed, 29843 insertions, 0 deletions
diff --git a/src/BlockHandler/Base.php b/src/BlockHandler/Base.php
new file mode 100644
index 00000000..e194f67b
--- /dev/null
+++ b/src/BlockHandler/Base.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Smarty\BlockHandler;
+
+use Smarty\Template;
+
+abstract class Base implements BlockHandlerInterface {
+
+ /**
+ * @var bool
+ */
+ protected $cacheable = true;
+
+ abstract public function handle($params, $content, Template $template, &$repeat);
+
+ public function isCacheable(): bool {
+ return $this->cacheable;
+ }
+} \ No newline at end of file
diff --git a/src/BlockHandler/BlockHandlerInterface.php b/src/BlockHandler/BlockHandlerInterface.php
new file mode 100644
index 00000000..9aa744ec
--- /dev/null
+++ b/src/BlockHandler/BlockHandlerInterface.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Smarty\BlockHandler;
+
+use Smarty\Template;
+
+interface BlockHandlerInterface {
+ public function handle($params, $content, Template $template, &$repeat);
+ public function isCacheable(): bool;
+} \ No newline at end of file
diff --git a/src/BlockHandler/BlockPluginWrapper.php b/src/BlockHandler/BlockPluginWrapper.php
new file mode 100644
index 00000000..47005945
--- /dev/null
+++ b/src/BlockHandler/BlockPluginWrapper.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Smarty\BlockHandler;
+
+use Smarty\Template;
+
+class BlockPluginWrapper extends Base {
+
+ private $callback;
+
+ public function __construct($callback, bool $cacheable = true) {
+ $this->callback = $callback;
+ $this->cacheable = $cacheable;
+ }
+
+ public function handle($params, $content, Template $template, &$repeat) {
+ return call_user_func_array($this->callback, [$params, $content, &$template, &$repeat]);
+ }
+} \ No newline at end of file
diff --git a/src/BlockHandler/TextFormat.php b/src/BlockHandler/TextFormat.php
new file mode 100644
index 00000000..9cd804bb
--- /dev/null
+++ b/src/BlockHandler/TextFormat.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Smarty\BlockHandler;
+
+use Smarty\Smarty;
+use Smarty\Template;
+
+/**
+ * Smarty {textformat}{/textformat} block plugin
+ * Type: block function
+ * Name: textformat
+ * Purpose: format text a certain way with preset styles
+ * or custom wrap/indent settings
+ * Params:
+ *
+ * - style - string (email)
+ * - indent - integer (0)
+ * - wrap - integer (80)
+ * - wrap_char - string ("\n")
+ * - indent_char - string (" ")
+ * - wrap_boundary - boolean (true)
+ *
+ * @link https://www.smarty.net/manual/en/language.function.textformat.php {textformat}
+ * (Smarty online manual)
+ *
+ * @param array $params parameters
+ * @param string $content contents of the block
+ * @param Template $template template object
+ * @param boolean &$repeat repeat flag
+ *
+ * @return string content re-formatted
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @throws \Smarty\Exception
+ */
+class TextFormat implements BlockHandlerInterface {
+
+ public function handle($params, $content, Template $template, &$repeat) {
+ if (is_null($content)) {
+ return;
+ }
+ $style = null;
+ $indent = 0;
+ $indent_first = 0;
+ $indent_char = ' ';
+ $wrap = 80;
+ $wrap_char = "\n";
+ $wrap_cut = false;
+ $assign = null;
+ foreach ($params as $_key => $_val) {
+ switch ($_key) {
+ case 'style':
+ case 'indent_char':
+ case 'wrap_char':
+ case 'assign':
+ $$_key = (string)$_val;
+ break;
+ case 'indent':
+ case 'indent_first':
+ case 'wrap':
+ $$_key = (int)$_val;
+ break;
+ case 'wrap_cut':
+ $$_key = (bool)$_val;
+ break;
+ default:
+ trigger_error("textformat: unknown attribute '{$_key}'");
+ }
+ }
+ if ($style === 'email') {
+ $wrap = 72;
+ }
+ // split into paragraphs
+ $_paragraphs = preg_split('![\r\n]{2}!', $content);
+ foreach ($_paragraphs as &$_paragraph) {
+ if (!$_paragraph) {
+ continue;
+ }
+ // convert mult. spaces & special chars to single space
+ $_paragraph =
+ preg_replace(
+ array(
+ '!\s+!' . Smarty::$_UTF8_MODIFIER,
+ '!(^\s+)|(\s+$)!' . Smarty::$_UTF8_MODIFIER
+ ),
+ array(
+ ' ',
+ ''
+ ),
+ $_paragraph
+ );
+ // indent first line
+ if ($indent_first > 0) {
+ $_paragraph = str_repeat($indent_char, $indent_first) . $_paragraph;
+ }
+ // wordwrap sentences
+ $_paragraph = smarty_mb_wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut);
+ // indent lines
+ if ($indent > 0) {
+ $_paragraph = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraph);
+ }
+ }
+ $_output = implode($wrap_char . $wrap_char, $_paragraphs);
+ if ($assign) {
+ $template->assign($assign, $_output);
+ } else {
+ return $_output;
+ }
+ }
+
+ public function isCacheable(): bool {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/Cacheresource/Base.php b/src/Cacheresource/Base.php
new file mode 100644
index 00000000..54a14192
--- /dev/null
+++ b/src/Cacheresource/Base.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace Smarty\Cacheresource;
+
+use Smarty\Exception;
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Cached;
+
+/**
+ * Cache Handler API
+ * @author Rodney Rehm
+ */
+abstract class Base
+{
+
+ /**
+ * populate Cached Object with metadata from Resource
+ *
+ * @param Cached $cached cached object
+ * @param Template $_template template object
+ *
+ * @return void
+ */
+ abstract public function populate(Cached $cached, Template $_template);
+
+ /**
+ * populate Cached Object with timestamp and exists from Resource
+ *
+ * @param Cached $cached
+ *
+ * @return void
+ */
+ abstract public function populateTimestamp(Cached $cached);
+
+ /**
+ * Read the cached template and process header
+ *
+ * @param Template $_template template object
+ * @param Cached|null $cached cached object
+ * @param boolean $update flag if called because cache update
+ *
+ * @return boolean true or false if the cached content does not exist
+ */
+ abstract public function process(
+ Template $_template,
+ Cached $cached = null,
+ $update = false
+ );
+
+ /**
+ * Write the rendered template output to cache
+ *
+ * @param Template $_template template object
+ * @param string $content content to cache
+ *
+ * @return boolean success
+ */
+ abstract public function storeCachedContent(Template $_template, $content);
+
+ /**
+ * Read cached template from cache
+ *
+ * @param Template $_template template object
+ *
+ * @return string content
+ */
+ abstract public function retrieveCachedContent(Template $_template);
+
+ /**
+ * Empty cache
+ *
+ * @param Smarty $smarty Smarty object
+ * @param integer $exp_time expiration time (number of seconds, not timestamp)
+ *
+ * @return integer number of cache files deleted
+ */
+ abstract public function clearAll(Smarty $smarty, $exp_time = null);
+
+ /**
+ * Empty cache for a specific template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param integer $exp_time expiration time (number of seconds, not timestamp)
+ *
+ * @return integer number of cache files deleted
+ */
+ abstract public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time);
+
+ /**
+ * @param Smarty $smarty
+ * @param Cached $cached
+ *
+ * @return bool|null
+ */
+ public function locked(Smarty $smarty, Cached $cached)
+ {
+ // theoretically locking_timeout should be checked against time_limit (max_execution_time)
+ $start = microtime(true);
+ $hadLock = null;
+ while ($this->hasLock($smarty, $cached)) {
+ $hadLock = true;
+ if (microtime(true) - $start > $smarty->locking_timeout) {
+ // abort waiting for lock release
+ return false;
+ }
+ sleep(1);
+ }
+ return $hadLock;
+ }
+
+ /**
+ * Check is cache is locked for this template
+ *
+ * @param Smarty $smarty
+ * @param Cached $cached
+ *
+ * @return bool
+ */
+ public function hasLock(Smarty $smarty, Cached $cached)
+ {
+ // check if lock exists
+ return false;
+ }
+
+ /**
+ * Lock cache for this template
+ *
+ * @param Smarty $smarty
+ * @param Cached $cached
+ *
+ * @return bool
+ */
+ public function acquireLock(Smarty $smarty, Cached $cached)
+ {
+ // create lock
+ return true;
+ }
+
+ /**
+ * Unlock cache for this template
+ *
+ * @param Smarty $smarty
+ * @param Cached $cached
+ *
+ * @return bool
+ */
+ public function releaseLock(Smarty $smarty, Cached $cached)
+ {
+ // release lock
+ return true;
+ }
+}
diff --git a/src/Cacheresource/Custom.php b/src/Cacheresource/Custom.php
new file mode 100644
index 00000000..c049246c
--- /dev/null
+++ b/src/Cacheresource/Custom.php
@@ -0,0 +1,303 @@
+<?php
+
+namespace Smarty\Cacheresource;
+
+/**
+ * Smarty Internal Plugin
+ *
+
+
+ */
+
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Cached;
+
+/**
+ * Cache Handler API
+ *
+
+
+ * @author Rodney Rehm
+ */
+abstract class Custom extends Base
+{
+ /**
+ * fetch cached content and its modification time from data source
+ *
+ * @param string $id unique cache content identifier
+ * @param string $name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param string $content cached content
+ * @param integer $mtime cache modification timestamp (epoch)
+ *
+ * @return void
+ */
+ abstract protected function fetch($id, $name, $cache_id, $compile_id, &$content, &$mtime);
+
+ /**
+ * Fetch cached content's modification timestamp from data source
+ * {@internal implementing this method is optional.
+ * Only implement it if modification times can be accessed faster than loading the complete cached content.}}
+ *
+ * @param string $id unique cache content identifier
+ * @param string $name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ *
+ * @return integer|boolean timestamp (epoch) the template was modified, or false if not found
+ */
+ protected function fetchTimestamp($id, $name, $cache_id, $compile_id)
+ {
+ return false;
+ }
+
+ /**
+ * Save content to cache
+ *
+ * @param string $id unique cache content identifier
+ * @param string $name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param integer|null $exp_time seconds till expiration or null
+ * @param string $content content to cache
+ *
+ * @return boolean success
+ */
+ abstract protected function save($id, $name, $cache_id, $compile_id, $exp_time, $content);
+
+ /**
+ * Delete content from cache
+ *
+ * @param string|null $name template name
+ * @param string|null $cache_id cache id
+ * @param string|null $compile_id compile id
+ * @param integer|null $exp_time seconds till expiration time in seconds or null
+ *
+ * @return integer number of deleted caches
+ */
+ abstract protected function delete($name, $cache_id, $compile_id, $exp_time);
+
+ /**
+ * populate Cached Object with metadata from Resource
+ *
+ * @param \Smarty\Template\Cached $cached cached object
+ * @param Template $_template template object
+ *
+ * @return void
+ */
+ public function populate(\Smarty\Template\Cached $cached, Template $_template)
+ {
+ $_cache_id = isset($cached->cache_id) ? preg_replace('![^\w\|]+!', '_', $cached->cache_id) : null;
+ $_compile_id = isset($cached->compile_id) ? preg_replace('![^\w]+!', '_', $cached->compile_id) : null;
+ $path = $cached->getSource()->uid . $_cache_id . $_compile_id;
+ $cached->filepath = sha1($path);
+ if ($_template->getSmarty()->cache_locking) {
+ $cached->lock_id = sha1('lock.' . $path);
+ }
+ $this->populateTimestamp($cached);
+ }
+
+ /**
+ * populate Cached Object with timestamp and exists from Resource
+ *
+ * @param \Smarty\Template\Cached $cached
+ *
+ * @return void
+ */
+ public function populateTimestamp(\Smarty\Template\Cached $cached)
+ {
+ $mtime =
+ $this->fetchTimestamp($cached->filepath, $cached->getSource()->name, $cached->cache_id, $cached->compile_id);
+ if ($mtime !== null) {
+ $cached->timestamp = $mtime;
+ $cached->exists = !!$cached->timestamp;
+ return;
+ }
+ $timestamp = null;
+ $this->fetch(
+ $cached->filepath,
+ $cached->getSource()->name,
+ $cached->cache_id,
+ $cached->compile_id,
+ $cached->content,
+ $timestamp
+ );
+ $cached->timestamp = isset($timestamp) ? $timestamp : false;
+ $cached->exists = !!$cached->timestamp;
+ }
+
+ /**
+ * Read the cached template and process the header
+ *
+ * @param Template $_smarty_tpl do not change variable name, is used by compiled template
+ * @param Cached|null $cached cached object
+ * @param boolean $update flag if called because cache update
+ *
+ * @return boolean true or false if the cached content does not exist
+ */
+ public function process(
+ Template $_smarty_tpl,
+ \Smarty\Template\Cached $cached = null,
+ $update = false
+ ) {
+ if (!$cached) {
+ $cached = $_smarty_tpl->getCached();
+ }
+ $content = $cached->content ? $cached->content : null;
+ $timestamp = $cached->timestamp ? $cached->timestamp : null;
+ if ($content === null || !$timestamp) {
+ $this->fetch(
+ $_smarty_tpl->getCached()->filepath,
+ $_smarty_tpl->getSource()->name,
+ $_smarty_tpl->cache_id,
+ $_smarty_tpl->compile_id,
+ $content,
+ $timestamp
+ );
+ }
+ if (isset($content)) {
+ eval('?>' . $content);
+ $cached->content = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Write the rendered template output to cache
+ *
+ * @param Template $_template template object
+ * @param string $content content to cache
+ *
+ * @return boolean success
+ */
+ public function storeCachedContent(Template $_template, $content)
+ {
+ return $this->save(
+ $_template->getCached()->filepath,
+ $_template->getSource()->name,
+ $_template->cache_id,
+ $_template->compile_id,
+ $_template->cache_lifetime,
+ $content
+ );
+ }
+
+ /**
+ * Read cached template from cache
+ *
+ * @param Template $_template template object
+ *
+ * @return string|boolean content
+ */
+ public function retrieveCachedContent(Template $_template)
+ {
+ $content = $_template->getCached()->content ?: null;
+ if ($content === null) {
+ $timestamp = null;
+ $this->fetch(
+ $_template->getCached()->filepath,
+ $_template->getSource()->name,
+ $_template->cache_id,
+ $_template->compile_id,
+ $content,
+ $timestamp
+ );
+ }
+ if (isset($content)) {
+ return $content;
+ }
+ return false;
+ }
+
+ /**
+ * Empty cache
+ *
+ * @param \Smarty\Smarty $smarty Smarty object
+ * @param null $exp_time expiration time (number of seconds, not timestamp)
+ *
+ * @return integer number of cache files deleted
+ */
+ public function clearAll(\Smarty\Smarty $smarty, $exp_time = null)
+ {
+ return $this->delete(null, null, null, $exp_time);
+ }
+
+ /**
+ * Empty cache for a specific template
+ *
+ * @param \Smarty\Smarty $smarty Smarty object
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param integer $exp_time expiration time (number of seconds, not timestamp)
+ *
+ * @return int number of cache files deleted
+ * @throws \Smarty\Exception
+ */
+ public function clear(\Smarty\Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
+ {
+ $cache_name = null;
+ if (isset($resource_name)) {
+ $source = \Smarty\Template\Source::load(null, $smarty, $resource_name);
+ if ($source->exists) {
+ $cache_name = $source->name;
+ } else {
+ return 0;
+ }
+ }
+ return $this->delete($cache_name, $cache_id, $compile_id, $exp_time);
+ }
+
+ /**
+ * Check is cache is locked for this template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param Cached $cached cached object
+ *
+ * @return boolean true or false if cache is locked
+ */
+ public function hasLock(\Smarty\Smarty $smarty, \Smarty\Template\Cached $cached)
+ {
+ $id = $cached->lock_id;
+ $name = $cached->getSource()->name . '.lock';
+ $mtime = $this->fetchTimestamp($id, $name, $cached->cache_id, $cached->compile_id);
+ if ($mtime === null) {
+ $this->fetch($id, $name, $cached->cache_id, $cached->compile_id, $content, $mtime);
+ }
+ return $mtime && ($t = time()) - $mtime < $smarty->locking_timeout;
+ }
+
+ /**
+ * Lock cache for this template
+ *
+ * @param \Smarty\Smarty $smarty Smarty object
+ * @param \Smarty\Template\Cached $cached cached object
+ *
+ * @return bool|void
+ */
+ public function acquireLock(\Smarty\Smarty $smarty, \Smarty\Template\Cached $cached)
+ {
+ $cached->is_locked = true;
+ $id = $cached->lock_id;
+ $name = $cached->getSource()->name . '.lock';
+ $this->save($id, $name, $cached->cache_id, $cached->compile_id, $smarty->locking_timeout, '');
+ }
+
+ /**
+ * Unlock cache for this template
+ *
+ * @param \Smarty\Smarty $smarty Smarty object
+ * @param \Smarty\Template\Cached $cached cached object
+ *
+ * @return bool|void
+ */
+ public function releaseLock(\Smarty\Smarty $smarty, \Smarty\Template\Cached $cached)
+ {
+ $cached->is_locked = false;
+ $name = $cached->getSource()->name . '.lock';
+ $this->delete($name, $cached->cache_id, $cached->compile_id, null);
+ }
+}
diff --git a/src/Cacheresource/File.php b/src/Cacheresource/File.php
new file mode 100644
index 00000000..538f0103
--- /dev/null
+++ b/src/Cacheresource/File.php
@@ -0,0 +1,338 @@
+<?php
+
+namespace Smarty\Cacheresource;
+
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Cached;
+
+/**
+ * Smarty Internal Plugin CacheResource File
+ *
+
+
+ * @author Uwe Tews
+ * @author Rodney Rehm
+ */
+
+/**
+ * This class does contain all necessary methods for the HTML cache on file system
+ * Implements the file system as resource for the HTML cache Version using nocache inserts.
+ */
+class File extends Base
+{
+ /**
+ * populate Cached Object with metadata from Resource
+ *
+ * @param Cached $cached cached object
+ * @param Template $_template template object
+ *
+ * @return void
+ */
+ public function populate(Cached $cached, Template $_template)
+ {
+ $source = $_template->getSource();
+ $smarty = $_template->getSmarty();
+ $_compile_dir_sep = $smarty->use_sub_dirs ? DIRECTORY_SEPARATOR : '^';
+ $_filepath = $source->uid;
+ $cached->filepath = $smarty->getCacheDir();
+ if (isset($_template->cache_id)) {
+ $cached->filepath .= preg_replace(
+ array(
+ '![^\w|]+!',
+ '![|]+!'
+ ),
+ array(
+ '_',
+ $_compile_dir_sep
+ ),
+ $_template->cache_id
+ ) . $_compile_dir_sep;
+ }
+ if (isset($_template->compile_id)) {
+ $cached->filepath .= preg_replace('![^\w]+!', '_', $_template->compile_id) . $_compile_dir_sep;
+ }
+ // if use_sub_dirs, break file into directories
+ if ($smarty->use_sub_dirs) {
+ $cached->filepath .= $_filepath[ 0 ] . $_filepath[ 1 ] . DIRECTORY_SEPARATOR . $_filepath[ 2 ] .
+ $_filepath[ 3 ] .
+ DIRECTORY_SEPARATOR .
+ $_filepath[ 4 ] . $_filepath[ 5 ] . DIRECTORY_SEPARATOR;
+ }
+ $cached->filepath .= $_filepath . '_' . $source->getBasename();
+
+ if ($smarty->cache_locking) {
+ $cached->lock_id = $cached->filepath . '.lock';
+ }
+ $cached->filepath .= '.php';
+ $cached->timestamp = $cached->exists = is_file($cached->filepath);
+ if ($cached->exists) {
+ $cached->timestamp = filemtime($cached->filepath);
+ }
+ }
+
+ /**
+ * populate Cached Object with timestamp and exists from Resource
+ *
+ * @param Cached $cached cached object
+ *
+ * @return void
+ */
+ public function populateTimestamp(Cached $cached)
+ {
+ $cached->timestamp = $cached->exists = is_file($cached->filepath);
+ if ($cached->exists) {
+ $cached->timestamp = filemtime($cached->filepath);
+ }
+ }
+
+ /**
+ * Read the cached template and process its header
+ *
+ * @param Template $_smarty_tpl do not change variable name, is used by compiled template
+ * @param Cached|null $cached cached object
+ * @param bool $update flag if called because cache update
+ *
+ * @return boolean true or false if the cached content does not exist
+ */
+ public function process(
+ Template $_smarty_tpl,
+ Cached $cached = null,
+ $update = false
+ ) {
+ $_smarty_tpl->getCached()->setValid(false);
+ if ($update && defined('HHVM_VERSION')) {
+ eval('?>' . file_get_contents($_smarty_tpl->getCached()->filepath));
+ return true;
+ } else {
+ return @include $_smarty_tpl->getCached()->filepath;
+ }
+ }
+
+ /**
+ * Write the rendered template output to cache
+ *
+ * @param Template $_template template object
+ * @param string $content content to cache
+ *
+ * @return bool success
+ * @throws \Smarty\Exception
+ */
+ public function storeCachedContent(Template $_template, $content)
+ {
+ if ($_template->getSmarty()->writeFile($_template->getCached()->filepath, $content) === true) {
+ if (function_exists('opcache_invalidate')
+ && (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api'))) < 1
+ ) {
+ opcache_invalidate($_template->getCached()->filepath, true);
+ } elseif (function_exists('apc_compile_file')) {
+ apc_compile_file($_template->getCached()->filepath);
+ }
+ $cached = $_template->getCached();
+ $cached->timestamp = $cached->exists = is_file($cached->filepath);
+ if ($cached->exists) {
+ $cached->timestamp = filemtime($cached->filepath);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Read cached template from cache
+ *
+ * @param Template $_template template object
+ *
+ * @return string content
+ */
+ public function retrieveCachedContent(Template $_template)
+ {
+ if (is_file($_template->getCached()->filepath)) {
+ return file_get_contents($_template->getCached()->filepath);
+ }
+ return false;
+ }
+
+ /**
+ * Empty cache
+ *
+ * @param Smarty $smarty
+ * @param integer $exp_time expiration time (number of seconds, not timestamp)
+ *
+ * @return integer number of cache files deleted
+ */
+ public function clearAll(Smarty $smarty, $exp_time = null)
+ {
+ return $this->clear($smarty, null, null, null, $exp_time);
+ }
+
+ /**
+ * Empty cache for a specific template
+ *
+ * @param Smarty $smarty
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param integer $exp_time expiration time (number of seconds, not timestamp)
+ *
+ * @return integer number of cache files deleted
+ */
+ public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
+ {
+ $_cache_id = isset($cache_id) ? preg_replace('![^\w\|]+!', '_', $cache_id) : null;
+ $_compile_id = isset($compile_id) ? preg_replace('![^\w]+!', '_', $compile_id) : null;
+ $_dir_sep = $smarty->use_sub_dirs ? '/' : '^';
+ $_compile_id_offset = $smarty->use_sub_dirs ? 3 : 0;
+ $_dir = $smarty->getCacheDir();
+ if ($_dir === '/') { //We should never want to delete this!
+ return 0;
+ }
+ $_dir_length = strlen($_dir);
+ if (isset($_cache_id)) {
+ $_cache_id_parts = explode('|', $_cache_id);
+ $_cache_id_parts_count = count($_cache_id_parts);
+ if ($smarty->use_sub_dirs) {
+ foreach ($_cache_id_parts as $id_part) {
+ $_dir .= $id_part . '/';
+ }
+ }
+ }
+ if (isset($resource_name)) {
+ $_save_stat = $smarty->caching;
+ $smarty->caching = \Smarty\Smarty::CACHING_LIFETIME_CURRENT;
+ $tpl = $smarty->doCreateTemplate($resource_name);
+ $smarty->caching = $_save_stat;
+ // remove from template cache
+ if ($tpl->getSource()->exists) {
+ $_resourcename_parts = basename(str_replace('^', '/', $tpl->getCached()->filepath));
+ } else {
+ return 0;
+ }
+ }
+ $_count = 0;
+ $_time = time();
+ if (file_exists($_dir)) {
+ $_cacheDirs = new RecursiveDirectoryIterator($_dir);
+ $_cache = new RecursiveIteratorIterator($_cacheDirs, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ($_cache as $_file) {
+ if (substr(basename($_file->getPathname()), 0, 1) === '.') {
+ continue;
+ }
+ $_filepath = (string)$_file;
+ // directory ?
+ if ($_file->isDir()) {
+ if (!$_cache->isDot()) {
+ // delete folder if empty
+ @rmdir($_file->getPathname());
+ }
+ } else {
+ // delete only php files
+ if (substr($_filepath, -4) !== '.php') {
+ continue;
+ }
+ $_parts = explode($_dir_sep, str_replace('\\', '/', substr($_filepath, $_dir_length)));
+ $_parts_count = count($_parts);
+ // check name
+ if (isset($resource_name)) {
+ if ($_parts[ $_parts_count - 1 ] !== $_resourcename_parts) {
+ continue;
+ }
+ }
+ // check compile id
+ if (isset($_compile_id) && (!isset($_parts[ $_parts_count - 2 - $_compile_id_offset ])
+ || $_parts[ $_parts_count - 2 - $_compile_id_offset ] !== $_compile_id)
+ ) {
+ continue;
+ }
+ // check cache id
+ if (isset($_cache_id)) {
+ // count of cache id parts
+ $_parts_count = (isset($_compile_id)) ? $_parts_count - 2 - $_compile_id_offset :
+ $_parts_count - 1 - $_compile_id_offset;
+ if ($_parts_count < $_cache_id_parts_count) {
+ continue;
+ }
+ for ($i = 0; $i < $_cache_id_parts_count; $i++) {
+ if ($_parts[ $i ] !== $_cache_id_parts[ $i ]) {
+ continue 2;
+ }
+ }
+ }
+ if (is_file($_filepath)) {
+ // expired ?
+ if (isset($exp_time)) {
+ if ($exp_time < 0) {
+ preg_match('#\'cache_lifetime\' =>\s*(\d*)#', file_get_contents($_filepath), $match);
+ if ($_time < (filemtime($_filepath) + $match[ 1 ])) {
+ continue;
+ }
+ } else {
+ if ($_time - filemtime($_filepath) < $exp_time) {
+ continue;
+ }
+ }
+ }
+ $_count += @unlink($_filepath) ? 1 : 0;
+ if (function_exists('opcache_invalidate')
+ && (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
+ ) {
+ opcache_invalidate($_filepath, true);
+ } elseif (function_exists('apc_delete_file')) {
+ apc_delete_file($_filepath);
+ }
+ }
+ }
+ }
+ }
+ return $_count;
+ }
+
+ /**
+ * Check is cache is locked for this template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param Cached $cached cached object
+ *
+ * @return boolean true or false if cache is locked
+ */
+ public function hasLock(Smarty $smarty, Cached $cached)
+ {
+ clearstatcache(true, $cached->lock_id ?? '');
+ if (null !== $cached->lock_id && is_file($cached->lock_id)) {
+ $t = filemtime($cached->lock_id);
+ return $t && (time() - $t < $smarty->locking_timeout);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Lock cache for this template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param Cached $cached cached object
+ *
+ * @return void
+ */
+ public function acquireLock(Smarty $smarty, Cached $cached)
+ {
+ $cached->is_locked = true;
+ touch($cached->lock_id);
+ }
+
+ /**
+ * Unlock cache for this template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param Cached $cached cached object
+ *
+ * @return void
+ */
+ public function releaseLock(Smarty $smarty, Cached $cached)
+ {
+ $cached->is_locked = false;
+ @unlink($cached->lock_id);
+ }
+}
diff --git a/src/Cacheresource/KeyValueStore.php b/src/Cacheresource/KeyValueStore.php
new file mode 100644
index 00000000..733d2776
--- /dev/null
+++ b/src/Cacheresource/KeyValueStore.php
@@ -0,0 +1,541 @@
+<?php
+
+namespace Smarty\Cacheresource;
+
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Cached;
+
+/**
+ * Smarty Internal Plugin
+ *
+
+
+ */
+
+/**
+ * Smarty Cache Handler Base for Key/Value Storage Implementations
+ * This class implements the functionality required to use simple key/value stores
+ * for hierarchical cache groups. key/value stores like memcache or APC do not support
+ * wildcards in keys, therefore a cache group cannot be cleared like "a|*" - which
+ * is no problem to filesystem and RDBMS implementations.
+ * This implementation is based on the concept of invalidation. While one specific cache
+ * can be identified and cleared, any range of caches cannot be identified. For this reason
+ * each level of the cache group hierarchy can have its own value in the store. These values
+ * are nothing but microtimes, telling us when a particular cache group was cleared for the
+ * last time. These keys are evaluated for every cache read to determine if the cache has
+ * been invalidated since it was created and should hence be treated as inexistent.
+ * Although deep hierarchies are possible, they are not recommended. Try to keep your
+ * cache groups as shallow as possible. Anything up 3-5 parents should be ok. So
+ * »a|b|c« is a good depth where »a|b|c|d|e|f|g|h|i|j|k« isn't. Try to join correlating
+ * cache groups: if your cache groups look somewhat like »a|b|$page|$items|$whatever«
+ * consider using »a|b|c|$page-$items-$whatever« instead.
+ *
+
+
+ * @author Rodney Rehm
+ */
+abstract class KeyValueStore extends Base
+{
+ /**
+ * cache for contents
+ *
+ * @var array
+ */
+ protected $contents = array();
+
+ /**
+ * cache for timestamps
+ *
+ * @var array
+ */
+ protected $timestamps = array();
+
+ /**
+ * populate Cached Object with meta data from Resource
+ *
+ * @param Cached $cached cached object
+ * @param Template $_template template object
+ *
+ * @return void
+ */
+ public function populate(Cached $cached, Template $_template)
+ {
+ $cached->filepath = $_template->getSource()->uid . '#' . $this->sanitize($cached->getSource()->resource) . '#' .
+ $this->sanitize($cached->cache_id) . '#' . $this->sanitize($cached->compile_id);
+ $this->populateTimestamp($cached);
+ }
+
+ /**
+ * populate Cached Object with timestamp and exists from Resource
+ *
+ * @param Cached $cached cached object
+ *
+ * @return void
+ */
+ public function populateTimestamp(Cached $cached)
+ {
+ if (!$this->fetch(
+ $cached->filepath,
+ $cached->getSource()->name,
+ $cached->cache_id,
+ $cached->compile_id,
+ $content,
+ $timestamp,
+ $cached->getSource()->uid
+ )
+ ) {
+ return;
+ }
+ $cached->content = $content;
+ $cached->timestamp = (int)$timestamp;
+ $cached->exists = !!$cached->timestamp;
+ }
+
+ /**
+ * Read the cached template and process the header
+ *
+ * @param Template $_smarty_tpl do not change variable name, is used by compiled template
+ * @param Cached|null $cached cached object
+ * @param boolean $update flag if called because cache update
+ *
+ * @return boolean true or false if the cached content does not exist
+ */
+ public function process(
+ Template $_smarty_tpl,
+ Cached $cached = null,
+ $update = false
+ ) {
+ if (!$cached) {
+ $cached = $_smarty_tpl->getCached();
+ }
+ $content = $cached->content ?: null;
+ $timestamp = $cached->timestamp ?: null;
+ if ($content === null || !$timestamp) {
+ if (!$this->fetch(
+ $_smarty_tpl->getCached()->filepath,
+ $_smarty_tpl->getSource()->name,
+ $_smarty_tpl->cache_id,
+ $_smarty_tpl->compile_id,
+ $content,
+ $timestamp,
+ $_smarty_tpl->getSource()->uid
+ )
+ ) {
+ return false;
+ }
+ }
+ if (isset($content)) {
+ eval('?>' . $content);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Write the rendered template output to cache
+ *
+ * @param Template $_template template object
+ * @param string $content content to cache
+ *
+ * @return boolean success
+ */
+ public function storeCachedContent(Template $_template, $content)
+ {
+ $this->addMetaTimestamp($content);
+ return $this->write(array($_template->getCached()->filepath => $content), $_template->cache_lifetime);
+ }
+
+ /**
+ * Read cached template from cache
+ *
+ * @param Template $_template template object
+ *
+ * @return string|false content
+ */
+ public function retrieveCachedContent(Template $_template)
+ {
+ $content = $_template->getCached()->content ?: null;
+ $timestamp = null;
+ if ($content === null) {
+ if (!$this->fetch(
+ $_template->getCached()->filepath,
+ $_template->getSource()->name,
+ $_template->cache_id,
+ $_template->compile_id,
+ $content,
+ $timestamp,
+ $_template->getSource()->uid
+ )
+ ) {
+ return false;
+ }
+ }
+ if (isset($content)) {
+ return $content;
+ }
+ return false;
+ }
+
+ /**
+ * Empty cache
+ * {@internal the $exp_time argument is ignored altogether }}
+ *
+ * @param Smarty $smarty Smarty object
+ * @param integer $exp_time expiration time [being ignored]
+ *
+ * @return integer number of cache files deleted [always -1]
+ * @uses purge() to clear the whole store
+ * @uses invalidate() to mark everything outdated if purge() is inapplicable
+ */
+ public function clearAll(Smarty $smarty, $exp_time = null)
+ {
+ if (!$this->purge()) {
+ $this->invalidate(null);
+ }
+ return -1;
+ }
+
+ /**
+ * Empty cache for a specific template
+ * {@internal the $exp_time argument is ignored altogether}}
+ *
+ * @param Smarty $smarty Smarty object
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param integer $exp_time expiration time [being ignored]
+ *
+ * @return int number of cache files deleted [always -1]
+ * @throws \Smarty\Exception
+ * @uses buildCachedFilepath() to generate the CacheID
+ * @uses invalidate() to mark CacheIDs parent chain as outdated
+ * @uses delete() to remove CacheID from cache
+ */
+ public function clear(Smarty $smarty, $resource_name, $cache_id, $compile_id, $exp_time)
+ {
+ $uid = $this->getTemplateUid($smarty, $resource_name);
+ $cid = $uid . '#' . $this->sanitize($resource_name) . '#' . $this->sanitize($cache_id) . '#' .
+ $this->sanitize($compile_id);
+ $this->delete(array($cid));
+ $this->invalidate($cid, $resource_name, $cache_id, $compile_id, $uid);
+ return -1;
+ }
+
+ /**
+ * Get template's unique ID
+ *
+ * @param Smarty $smarty Smarty object
+ * @param string $resource_name template name
+ *
+ * @return string filepath of cache file
+ * @throws \Smarty\Exception
+ */
+ protected function getTemplateUid(Smarty $smarty, $resource_name)
+ {
+ if (isset($resource_name)) {
+ $source = \Smarty\Template\Source::load(null, $smarty, $resource_name);
+ if ($source->exists) {
+ return $source->uid;
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Sanitize CacheID components
+ *
+ * @param string $string CacheID component to sanitize
+ *
+ * @return string sanitized CacheID component
+ */
+ protected function sanitize($string)
+ {
+ $string = trim((string)$string, '|');
+ if (!$string) {
+ return '';
+ }
+ return preg_replace('#[^\w\|]+#S', '_', $string);
+ }
+
+ /**
+ * Fetch and prepare a cache object.
+ *
+ * @param string $cid CacheID to fetch
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param string $content cached content
+ * @param integer &$timestamp cached timestamp (epoch)
+ * @param string $resource_uid resource's uid
+ *
+ * @return boolean success
+ */
+ protected function fetch(
+ $cid,
+ $resource_name = null,
+ $cache_id = null,
+ $compile_id = null,
+ &$content = null,
+ &$timestamp = null,
+ $resource_uid = null
+ ) {
+ $t = $this->read(array($cid));
+ $content = !empty($t[ $cid ]) ? $t[ $cid ] : null;
+ $timestamp = null;
+ if ($content && ($timestamp = $this->getMetaTimestamp($content))) {
+ $invalidated =
+ $this->getLatestInvalidationTimestamp($cid, $resource_name, $cache_id, $compile_id, $resource_uid);
+ if ($invalidated > $timestamp) {
+ $timestamp = null;
+ $content = null;
+ }
+ }
+ return !!$content;
+ }
+
+ /**
+ * Add current microtime to the beginning of $cache_content
+ * {@internal the header uses 8 Bytes, the first 4 Bytes are the seconds, the second 4 Bytes are the microseconds}}
+ *
+ * @param string &$content the content to be cached
+ */
+ protected function addMetaTimestamp(&$content)
+ {
+ $mt = explode(' ', microtime());
+ $ts = pack('NN', $mt[ 1 ], (int)($mt[ 0 ] * 100000000));
+ $content = $ts . $content;
+ }
+
+ /**
+ * Extract the timestamp the $content was cached
+ *
+ * @param string &$content the cached content
+ *
+ * @return float the microtime the content was cached
+ */
+ protected function getMetaTimestamp(&$content)
+ {
+ extract(unpack('N1s/N1m/a*content', $content));
+ /**
+ * @var int $s
+ * @var int $m
+ */
+ return $s + ($m / 100000000);
+ }
+
+ /**
+ * Invalidate CacheID
+ *
+ * @param string $cid CacheID
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param string $resource_uid source's uid
+ *
+ * @return void
+ */
+ protected function invalidate(
+ $cid = null,
+ $resource_name = null,
+ $cache_id = null,
+ $compile_id = null,
+ $resource_uid = null
+ ) {
+ $now = microtime(true);
+ $key = null;
+ // invalidate everything
+ if (!$resource_name && !$cache_id && !$compile_id) {
+ $key = 'IVK#ALL';
+ } // invalidate all caches by template
+ else {
+ if ($resource_name && !$cache_id && !$compile_id) {
+ $key = 'IVK#TEMPLATE#' . $resource_uid . '#' . $this->sanitize($resource_name);
+ } // invalidate all caches by cache group
+ else {
+ if (!$resource_name && $cache_id && !$compile_id) {
+ $key = 'IVK#CACHE#' . $this->sanitize($cache_id);
+ } // invalidate all caches by compile id
+ else {
+ if (!$resource_name && !$cache_id && $compile_id) {
+ $key = 'IVK#COMPILE#' . $this->sanitize($compile_id);
+ } // invalidate by combination
+ else {
+ $key = 'IVK#CID#' . $cid;
+ }
+ }
+ }
+ }
+ $this->write(array($key => $now));
+ }
+
+ /**
+ * Determine the latest timestamp known to the invalidation chain
+ *
+ * @param string $cid CacheID to determine latest invalidation timestamp of
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param string $resource_uid source's filepath
+ *
+ * @return float the microtime the CacheID was invalidated
+ */
+ protected function getLatestInvalidationTimestamp(
+ $cid,
+ $resource_name = null,
+ $cache_id = null,
+ $compile_id = null,
+ $resource_uid = null
+ ) {
+ // abort if there are no InvalidationKeys to check
+ if (!($_cid = $this->listInvalidationKeys($cid, $resource_name, $cache_id, $compile_id, $resource_uid))) {
+ return 0;
+ }
+ // there are no InValidationKeys
+ if (!($values = $this->read($_cid))) {
+ return 0;
+ }
+ // make sure we're dealing with floats
+ $values = array_map('floatval', $values);
+ return max($values);
+ }
+
+ /**
+ * Translate a CacheID into the list of applicable InvalidationKeys.
+ * Splits 'some|chain|into|an|array' into array( '#clearAll#', 'some', 'some|chain', 'some|chain|into', ... )
+ *
+ * @param string $cid CacheID to translate
+ * @param string $resource_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param string $resource_uid source's filepath
+ *
+ * @return array list of InvalidationKeys
+ * @uses $invalidationKeyPrefix to prepend to each InvalidationKey
+ */
+ protected function listInvalidationKeys(
+ $cid,
+ $resource_name = null,
+ $cache_id = null,
+ $compile_id = null,
+ $resource_uid = null
+ ) {
+ $t = array('IVK#ALL');
+ $_name = $_compile = '#';
+ if ($resource_name) {
+ $_name .= $resource_uid . '#' . $this->sanitize($resource_name);
+ $t[] = 'IVK#TEMPLATE' . $_name;
+ }
+ if ($compile_id) {
+ $_compile .= $this->sanitize($compile_id);
+ $t[] = 'IVK#COMPILE' . $_compile;
+ }
+ $_name .= '#';
+ $cid = trim((string)$cache_id, '|');
+ if (!$cid) {
+ return $t;
+ }
+ $i = 0;
+ while (true) {
+ // determine next delimiter position
+ $i = strpos($cid, '|', $i);
+ // add complete CacheID if there are no more delimiters
+ if ($i === false) {
+ $t[] = 'IVK#CACHE#' . $cid;
+ $t[] = 'IVK#CID' . $_name . $cid . $_compile;
+ $t[] = 'IVK#CID' . $_name . $_compile;
+ break;
+ }
+ $part = substr($cid, 0, $i);
+ // add slice to list
+ $t[] = 'IVK#CACHE#' . $part;
+ $t[] = 'IVK#CID' . $_name . $part . $_compile;
+ // skip past delimiter position
+ $i++;
+ }
+ return $t;
+ }
+
+ /**
+ * Check is cache is locked for this template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param Cached $cached cached object
+ *
+ * @return boolean true or false if cache is locked
+ */
+ public function hasLock(Smarty $smarty, Cached $cached)
+ {
+ $key = 'LOCK#' . $cached->filepath;
+ $data = $this->read(array($key));
+ return $data && time() - $data[ $key ] < $smarty->locking_timeout;
+ }
+
+ /**
+ * Lock cache for this template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param Cached $cached cached object
+ *
+ * @return bool|void
+ */
+ public function acquireLock(Smarty $smarty, Cached $cached)
+ {
+ $cached->is_locked = true;
+ $key = 'LOCK#' . $cached->filepath;
+ $this->write(array($key => time()), $smarty->locking_timeout);
+ }
+
+ /**
+ * Unlock cache for this template
+ *
+ * @param Smarty $smarty Smarty object
+ * @param Cached $cached cached object
+ *
+ * @return void
+ */
+ public function releaseLock(Smarty $smarty, Cached $cached)
+ {
+ $cached->is_locked = false;
+ $key = 'LOCK#' . $cached->filepath;
+ $this->delete(array($key));
+ }
+
+ /**
+ * Read values for a set of keys from cache
+ *
+ * @param array $keys list of keys to fetch
+ *
+ * @return array list of values with the given keys used as indexes
+ */
+ abstract protected function read(array $keys);
+
+ /**
+ * Save values for a set of keys to cache
+ *
+ * @param array $keys list of values to save
+ * @param int $expire expiration time
+ *
+ * @return boolean true on success, false on failure
+ */
+ abstract protected function write(array $keys, $expire = null);
+
+ /**
+ * Remove values from cache
+ *
+ * @param array $keys list of keys to delete
+ *
+ * @return boolean true on success, false on failure
+ */
+ abstract protected function delete(array $keys);
+
+ /**
+ * Remove *all* values from cache
+ *
+ * @return boolean true on success, false on failure
+ */
+ protected function purge()
+ {
+ return false;
+ }
+}
diff --git a/src/Compile/Base.php b/src/Compile/Base.php
new file mode 100644
index 00000000..adee289a
--- /dev/null
+++ b/src/Compile/Base.php
@@ -0,0 +1,233 @@
+<?php
+/**
+ * Smarty Internal Compile Plugin Base
+ * @author Uwe Tews
+ */
+namespace Smarty\Compile;
+
+use Smarty\Compiler\Template;
+use Smarty\Data;
+use Smarty\Exception;
+
+/**
+ * This class does extend all internal compile plugins
+ *
+
+
+ */
+abstract class Base implements CompilerInterface {
+
+ /**
+ * Array of names of required attribute required by tag
+ *
+ * @var array
+ */
+ protected $required_attributes = [];
+
+ /**
+ * Array of names of optional attribute required by tag
+ * use array('_any') if there is no restriction of attributes names
+ *
+ * @var array
+ */
+ protected $optional_attributes = [];
+
+ /**
+ * Shorttag attribute order defined by its names
+ *
+ * @var array
+ */
+ protected $shorttag_order = [];
+
+ /**
+ * Array of names of valid option flags
+ *
+ * @var array
+ */
+ protected $option_flags = ['nocache'];
+ /**
+ * @var bool
+ */
+ protected $cacheable = true;
+
+ public function isCacheable(): bool {
+ return $this->cacheable;
+ }
+
+ /**
+ * Converts attributes into parameter array strings
+ *
+ * @param array $_attr
+ *
+ * @return array
+ */
+ protected function formatParamsArray(array $_attr): array {
+ $_paramsArray = [];
+ foreach ($_attr as $_key => $_value) {
+ $_paramsArray[] = var_export($_key, true) . "=>" . $_value;
+ }
+ return $_paramsArray;
+ }
+
+ /**
+ * This function checks if the attributes passed are valid
+ * The attributes passed for the tag to compile are checked against the list of required and
+ * optional attributes. Required attributes must be present. Optional attributes are check against
+ * the corresponding list. The keyword '_any' specifies that any attribute will be accepted
+ * as valid
+ *
+ * @param object $compiler compiler object
+ * @param array $attributes attributes applied to the tag
+ *
+ * @return array of mapped attributes for further processing
+ */
+ protected function getAttributes($compiler, $attributes) {
+ $_indexed_attr = [];
+ $options = array_fill_keys($this->option_flags, true);
+ foreach ($attributes as $key => $mixed) {
+ // shorthand ?
+ if (!is_array($mixed)) {
+ // options flag ?
+ if (isset($options[trim($mixed, '\'"')])) {
+ $_indexed_attr[trim($mixed, '\'"')] = true;
+ // shorthand attribute ?
+ } elseif (isset($this->shorttag_order[$key])) {
+ $_indexed_attr[$this->shorttag_order[$key]] = $mixed;
+ } else {
+ // too many shorthands
+ $compiler->trigger_template_error('too many shorthand attributes', null, true);
+ }
+ // named attribute
+ } else {
+ foreach ($mixed as $k => $v) {
+ // options flag?
+ if (isset($options[$k])) {
+ if (is_bool($v)) {
+ $_indexed_attr[$k] = $v;
+ } else {
+ if (is_string($v)) {
+ $v = trim($v, '\'" ');
+ }
+
+ // Mapping array for boolean option value
+ static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false];
+
+ if (isset($optionMap[$v])) {
+ $_indexed_attr[$k] = $optionMap[$v];
+ } else {
+ $compiler->trigger_template_error(
+ "illegal value '" . var_export($v, true) .
+ "' for options flag '{$k}'",
+ null,
+ true
+ );
+ }
+ }
+ // must be named attribute
+ } else {
+ $_indexed_attr[$k] = $v;
+ }
+ }
+ }
+ }
+ // check if all required attributes present
+ foreach ($this->required_attributes as $attr) {
+ if (!isset($_indexed_attr[$attr])) {
+ $compiler->trigger_template_error("missing '{$attr}' attribute", null, true);
+ }
+ }
+ // check for not allowed attributes
+ if ($this->optional_attributes !== ['_any']) {
+ $allowedAttributes = array_fill_keys(
+ array_merge(
+ $this->required_attributes,
+ $this->optional_attributes,
+ $this->option_flags
+ ),
+ true
+ );
+ foreach ($_indexed_attr as $key => $dummy) {
+ if (!isset($allowedAttributes[$key]) && $key !== 0) {
+ $compiler->trigger_template_error("unexpected '{$key}' attribute", null, true);
+ }
+ }
+ }
+ // default 'false' for all options flags not set
+ foreach ($this->option_flags as $flag) {
+ if (!isset($_indexed_attr[$flag])) {
+ $_indexed_attr[$flag] = false;
+ }
+ }
+
+ return $_indexed_attr;
+ }
+
+ /**
+ * Push opening tag name on stack
+ * Optionally additional data can be saved on stack
+ *
+ * @param Template $compiler compiler object
+ * @param string $openTag the opening tag's name
+ * @param mixed $data optional data saved
+ */
+ protected function openTag(Template $compiler, $openTag, $data = null) {
+ $compiler->openTag($openTag, $data);
+ }
+
+ /**
+ * Pop closing tag
+ * Raise an error if this stack-top doesn't match with expected opening tags
+ *
+ * @param Template $compiler compiler object
+ * @param array|string $expectedTag the expected opening tag names
+ *
+ * @return mixed any type the opening tag's name or saved data
+ */
+ protected function closeTag(Template $compiler, $expectedTag) {
+ return $compiler->closeTag($expectedTag);
+ }
+
+ /**
+ * @param mixed $scope
+ * @param array $invalidScopes
+ *
+ * @return int
+ * @throws Exception
+ */
+ protected function convertScope($scope): int {
+
+ static $scopes = [
+ 'local' => Data::SCOPE_LOCAL, // current scope
+ 'parent' => Data::SCOPE_PARENT, // parent scope (definition unclear)
+ 'tpl_root' => Data::SCOPE_TPL_ROOT, // highest template (keep going up until parent is not a template)
+ 'root' => Data::SCOPE_ROOT, // highest scope (definition unclear)
+ 'global' => Data::SCOPE_GLOBAL, // smarty object
+
+ 'smarty' => Data::SCOPE_SMARTY, // @deprecated alias of 'global'
+ ];
+
+ $_scopeName = trim($scope, '\'"');
+ if (is_numeric($_scopeName) && in_array($_scopeName, $scopes)) {
+ return (int) $_scopeName;
+ }
+
+ if (isset($scopes[$_scopeName])) {
+ return $scopes[$_scopeName];
+ }
+
+ $err = var_export($_scopeName, true);
+ throw new Exception("illegal value '{$err}' for \"scope\" attribute");
+ }
+
+ /**
+ * Compiles code for the tag
+ *
+ * @param array $args array with attributes from parser
+ * @param Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return bool|string compiled code or true if no code has been compiled
+ * @throws \Smarty\CompilerException
+ */
+ abstract public function compile($args, Template $compiler, $parameter = array(), $tag = null, $function = null);
+}
diff --git a/src/Compile/BlockCompiler.php b/src/Compile/BlockCompiler.php
new file mode 100644
index 00000000..089e618d
--- /dev/null
+++ b/src/Compile/BlockCompiler.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Block Plugin
+ * Compiles code for the execution of block plugin
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile;
+
+use Smarty\Compiler\Template;
+use Smarty\CompilerException;
+use Smarty\Exception;
+use Smarty\Smarty;
+
+/**
+ * Smarty Internal Plugin Compile Block Plugin Class
+ *
+ */
+class BlockCompiler extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $optional_attributes = ['_any'];
+
+ /**
+ * nesting level
+ *
+ * @var int
+ */
+ private $nesting = 0;
+
+
+ /**
+ * Compiles code for the execution of block plugin
+ *
+ * @param array $args array with attributes from parser
+ * @param Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ * @param string $tag name of block plugin
+ * @param string $function PHP function name
+ *
+ * @return string compiled code
+ * @throws CompilerException
+ * @throws Exception
+ */
+ public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ if (!isset($tag[5]) || substr($tag, -5) !== 'close') {
+ $output = $this->compileOpeningTag($compiler, $args, $tag, $function);
+ } else {
+ $output = $this->compileClosingTag($compiler, $tag, $parameter, $function);
+ }
+ return $output;
+ }
+
+ /**
+ * Compiles code for the {$smarty.block.child} property
+ *
+ * @param Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws CompilerException
+ */
+ public function compileChild(\Smarty\Compiler\Template $compiler) {
+
+ if (!isset($compiler->_cache['blockNesting'])) {
+ $compiler->trigger_template_error(
+ "'{\$smarty.block.child}' used outside {block} tags ",
+ $compiler->getParser()->lex->taglineno
+ );
+ }
+ $compiler->_cache['blockParams'][$compiler->_cache['blockNesting']]['callsChild'] = true;
+ $compiler->has_code = true;
+ $compiler->suppressNocacheProcessing = true;
+
+ $output = "<?php \n";
+ $output .= '$_smarty_tpl->getInheritance()->callChild($_smarty_tpl, $this' . ");\n";
+ $output .= "?>\n";
+ return $output;
+ }
+
+ /**
+ * Compiles code for the {$smarty.block.parent} property
+ *
+ * @param Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws CompilerException
+ */
+ public function compileParent(\Smarty\Compiler\Template $compiler) {
+
+ if (!isset($compiler->_cache['blockNesting'])) {
+ $compiler->trigger_template_error(
+ "'{\$smarty.block.parent}' used outside {block} tags ",
+ $compiler->getParser()->lex->taglineno
+ );
+ }
+ $compiler->has_code = true;
+ $compiler->suppressNocacheProcessing = true;
+
+ $output = "<?php \n";
+ $output .= '$_smarty_tpl->getInheritance()->callParent($_smarty_tpl, $this' . ");\n";
+ $output .= "?>\n";
+ return $output;
+ }
+
+ /**
+ * Returns true if this block is cacheable.
+ *
+ * @param Smarty $smarty
+ * @param $function
+ *
+ * @return bool
+ */
+ protected function blockIsCacheable(\Smarty\Smarty $smarty, $function): bool {
+ return $smarty->getBlockHandler($function)->isCacheable();
+ }
+
+ /**
+ * Returns the code used for the isset check
+ *
+ * @param string $tag tag name
+ * @param string $function base tag or method name
+ *
+ * @return string
+ */
+ protected function getIsCallableCode($tag, $function): string {
+ return "\$_smarty_tpl->getSmarty()->getBlockHandler(" . var_export($function, true) . ")";
+ }
+
+ /**
+ * Returns the full code used to call the callback
+ *
+ * @param string $tag tag name
+ * @param string $function base tag or method name
+ *
+ * @return string
+ */
+ protected function getFullCallbackCode($tag, $function): string {
+ return "\$_smarty_tpl->getSmarty()->getBlockHandler(" . var_export($function, true) . ")->handle";
+ }
+
+ /**
+ * @param Template $compiler
+ * @param array $args
+ * @param string|null $tag
+ * @param string|null $function
+ *
+ * @return string
+ */
+ private function compileOpeningTag(Template $compiler, array $args, ?string $tag, ?string $function): string {
+
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ $this->nesting++;
+ unset($_attr['nocache']);
+ $_params = 'array(' . implode(',', $this->formatParamsArray($_attr)) . ')';
+
+ if (!$this->blockIsCacheable($compiler->getSmarty(), $function)) {
+ $compiler->tag_nocache = true;
+ }
+
+ if ($compiler->tag_nocache) {
+ // push a {nocache} tag onto the stack to prevent caching of this block
+ $this->openTag($compiler, 'nocache');
+ }
+
+ $this->openTag($compiler, $tag, [$_params, $compiler->tag_nocache]);
+
+ // compile code
+ $output = "<?php \$_block_repeat=true;
+if (!" . $this->getIsCallableCode($tag, $function) .") {\nthrow new \\Smarty\\Exception('block tag \'{$tag}\' not callable or registered');\n}\n
+echo " . $this->getFullCallbackCode($tag, $function) . "({$_params}, null, \$_smarty_tpl, \$_block_repeat);
+while (\$_block_repeat) {
+ ob_start();
+?>";
+
+ return $output;
+ }
+
+ /**
+ * @param Template $compiler
+ * @param string $tag
+ * @param array $parameter
+ * @param string|null $function
+ *
+ * @return string
+ * @throws CompilerException
+ * @throws Exception
+ */
+ private function compileClosingTag(Template $compiler, string $tag, array $parameter, ?string $function): string {
+
+ // closing tag of block plugin, restore nocache
+ $base_tag = substr($tag, 0, -5);
+ [$_params, $nocache_pushed] = $this->closeTag($compiler, $base_tag);
+
+ // compile code
+ if (!isset($parameter['modifier_list'])) {
+ $mod_pre = $mod_post = $mod_content = '';
+ $mod_content2 = 'ob_get_clean()';
+ } else {
+ $mod_content2 = "\$_block_content{$this->nesting}";
+ $mod_content = "\$_block_content{$this->nesting} = ob_get_clean();\n";
+ $mod_pre = "ob_start();\n";
+ $mod_post = 'echo ' . $compiler->compileModifier($parameter['modifier_list'], 'ob_get_clean()')
+ . ";\n";
+ }
+ $output = "<?php {$mod_content}\$_block_repeat=false;\n{$mod_pre}";
+ $callback = $this->getFullCallbackCode($base_tag, $function);
+ $output .= "echo {$callback}({$_params}, {$mod_content2}, \$_smarty_tpl, \$_block_repeat);\n";
+ $output .= "{$mod_post}}\n?>";
+
+ if ($nocache_pushed) {
+ // pop the pushed virtual nocache tag
+ $this->closeTag($compiler, 'nocache');
+ $compiler->tag_nocache = true;
+ }
+
+ return $output;
+ }
+
+}
diff --git a/src/Compile/CompilerInterface.php b/src/Compile/CompilerInterface.php
new file mode 100644
index 00000000..0fdbb9eb
--- /dev/null
+++ b/src/Compile/CompilerInterface.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Smarty\Compile;
+
+/**
+ * This class does extend all internal compile plugins
+ *
+
+
+ */
+interface CompilerInterface {
+
+ /**
+ * Compiles code for the tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return bool|string compiled code or true if no code has been compiled
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null);
+
+ public function isCacheable(): bool;
+} \ No newline at end of file
diff --git a/src/Compile/DefaultHandlerBlockCompiler.php b/src/Compile/DefaultHandlerBlockCompiler.php
new file mode 100644
index 00000000..394a6e76
--- /dev/null
+++ b/src/Compile/DefaultHandlerBlockCompiler.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Smarty\Compile;
+
+class DefaultHandlerBlockCompiler extends BlockCompiler {
+ /**
+ * @inheritDoc
+ */
+ protected function getIsCallableCode($tag, $function): string {
+ return "\$_smarty_tpl->getSmarty()->getRuntime('DefaultPluginHandler')->hasPlugin(" .
+ var_export($function, true) . ", 'block')";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getFullCallbackCode($tag, $function): string {
+ return "\$_smarty_tpl->getSmarty()->getRuntime('DefaultPluginHandler')->getCallback(" .
+ var_export($function, true) . ", 'block')";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function blockIsCacheable(\Smarty\Smarty $smarty, $function): bool {
+ return true;
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/DefaultHandlerFunctionCallCompiler.php b/src/Compile/DefaultHandlerFunctionCallCompiler.php
new file mode 100644
index 00000000..ff2f131c
--- /dev/null
+++ b/src/Compile/DefaultHandlerFunctionCallCompiler.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Smarty\Compile;
+
+use Smarty\Compiler\Template;
+
+class DefaultHandlerFunctionCallCompiler extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ public $optional_attributes = ['_any'];
+
+ /**
+ * Compiles code for the execution of a registered function
+ *
+ * @param array $args array with attributes from parser
+ * @param Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ * @param string $tag name of tag
+ * @param string $function name of function
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ unset($_attr['nocache']);
+
+ $_paramsArray = $this->formatParamsArray($_attr);
+ $_params = 'array(' . implode(',', $_paramsArray) . ')';
+
+ $output = "\$_smarty_tpl->getSmarty()->getRuntime('DefaultPluginHandler')->getCallback(" . var_export($function, true) .
+ ",'function')($_params, \$_smarty_tpl)";
+
+ if (!empty($parameter['modifierlist'])) {
+ $output = $compiler->compileModifier($parameter['modifierlist'], $output);
+ }
+ return "<?php echo {$output};?>\n";
+ }
+} \ No newline at end of file
diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php
new file mode 100644
index 00000000..89b91021
--- /dev/null
+++ b/src/Compile/FunctionCallCompiler.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Registered Function
+ * Compiles code for the execution of a registered function
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile;
+
+use Smarty\Compiler\Template;
+use Smarty\CompilerException;
+
+/**
+ * Smarty Internal Plugin Compile Registered Function Class
+ *
+
+
+ */
+class FunctionCallCompiler extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ public $optional_attributes = ['_any'];
+
+ /**
+ * Shorttag attribute order defined by its names
+ *
+ * @var array
+ */
+ protected $shorttag_order = ['var1', 'var2', 'var3'];
+
+ /**
+ * Compiles code for the execution of a registered function
+ *
+ * @param array $args array with attributes from parser
+ * @param Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ * @param string $tag name of tag
+ * @param string $function name of function
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ unset($_attr['nocache']);
+
+ $_paramsArray = $this->formatParamsArray($_attr);
+ $_params = 'array(' . implode(',', $_paramsArray) . ')';
+
+ try {
+ $value = array_shift($_attr);
+ $output = $compiler->compileModifier([array_merge([$function], $_attr)], $value);
+ } catch (\Smarty\CompilerException $e) {
+ if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) {
+
+ // not cacheable?
+ $compiler->tag_nocache = $compiler->tag_nocache || !$functionHandler->isCacheable();
+ $output = "\$_smarty_tpl->getSmarty()->getFunctionHandler(" . var_export($function, true) . ")";
+ $output .= "->handle($_params, \$_smarty_tpl)";
+ } else {
+ throw $e;
+ }
+ }
+
+ if (!empty($parameter['modifierlist'])) {
+ $output = $compiler->compileModifier($parameter['modifierlist'], $output);
+ }
+
+ return $output;
+ }
+}
diff --git a/src/Compile/Modifier/BCPluginWrapper.php b/src/Compile/Modifier/BCPluginWrapper.php
new file mode 100644
index 00000000..0147651f
--- /dev/null
+++ b/src/Compile/Modifier/BCPluginWrapper.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Smarty\Compile\Modifier;
+
+class BCPluginWrapper extends Base {
+
+ private $callback;
+
+ public function __construct($callback) {
+ $this->callback = $callback;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return call_user_func($this->callback, $params, $compiler);
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/Base.php b/src/Compile/Modifier/Base.php
new file mode 100644
index 00000000..2ae57228
--- /dev/null
+++ b/src/Compile/Modifier/Base.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Smarty\Compile\Modifier;
+
+use Smarty\Exception;
+
+abstract class Base implements ModifierCompilerInterface {
+
+ /**
+ * Compiles code for the modifier
+ *
+ * @param array $params array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ abstract public function compile($params, \Smarty\Compiler\Template $compiler);
+
+ /**
+ * evaluate compiler parameter
+ *
+ * @param array $params parameter array as given to the compiler function
+ * @param integer $index array index of the parameter to convert
+ * @param mixed $default value to be returned if the parameter is not present
+ *
+ * @return mixed evaluated value of parameter or $default
+ * @throws Exception if parameter is not a literal (but an expression, variable, …)
+ * @author Rodney Rehm
+ */
+ protected function literal_compiler_param($params, $index, $default = null)
+ {
+ // not set, go default
+ if (!isset($params[ $index ])) {
+ return $default;
+ }
+ // test if param is a literal
+ if (!preg_match('/^([\'"]?)[a-zA-Z0-9-]+(\\1)$/', $params[ $index ])) {
+ throw new Exception(
+ '$param[' . $index .
+ '] is not a literal and is thus not evaluatable at compile time'
+ );
+ }
+ $t = null;
+ eval("\$t = " . $params[ $index ] . ";");
+ return $t;
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/CatModifierCompiler.php b/src/Compile/Modifier/CatModifierCompiler.php
new file mode 100644
index 00000000..f7cc2589
--- /dev/null
+++ b/src/Compile/Modifier/CatModifierCompiler.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Smarty\Compile\Modifier;
+
+/**
+ * Smarty cat modifier plugin
+ * Type: modifier
+ * Name: cat
+ * Date: Feb 24, 2003
+ * Purpose: catenate a value to a variable
+ * Input: string to catenate
+ * Example: {$var|cat:"foo"}
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.cat.php cat
+ * (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class CatModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return '(' . implode(').(', $params) . ')';
+ }
+
+}
+
+
diff --git a/src/Compile/Modifier/CountCharactersModifierCompiler.php b/src/Compile/Modifier/CountCharactersModifierCompiler.php
new file mode 100644
index 00000000..0afad80b
--- /dev/null
+++ b/src/Compile/Modifier/CountCharactersModifierCompiler.php
@@ -0,0 +1,23 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty count_characters modifier plugin
+ * Type: modifier
+ * Name: count_characters
+ * Purpose: count the number of characters in a text
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.count.characters.php count_characters (Smarty online
+ * manual)
+ * @author Uwe Tews
+ */
+
+class CountCharactersModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ if (!isset($params[ 1 ]) || $params[ 1 ] !== 'true') {
+ return 'preg_match_all(\'/[^\s]/' . \Smarty\Smarty::$_UTF8_MODIFIER . '\',' . $params[ 0 ] . ', $tmp)';
+ }
+ return 'mb_strlen((string) ' . $params[ 0 ] . ', \'' . addslashes(\Smarty\Smarty::$_CHARSET) . '\')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/CountParagraphsModifierCompiler.php b/src/Compile/Modifier/CountParagraphsModifierCompiler.php
new file mode 100644
index 00000000..f67e64a3
--- /dev/null
+++ b/src/Compile/Modifier/CountParagraphsModifierCompiler.php
@@ -0,0 +1,21 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty count_paragraphs modifier plugin
+ * Type: modifier
+ * Name: count_paragraphs
+ * Purpose: count the number of paragraphs in a text
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.count.paragraphs.php
+ * count_paragraphs (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class CountParagraphsModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ // count \r or \n characters
+ return '(preg_match_all(\'#[\r\n]+#\', ' . $params[ 0 ] . ', $tmp)+1)';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/CountSentencesModifierCompiler.php b/src/Compile/Modifier/CountSentencesModifierCompiler.php
new file mode 100644
index 00000000..503d63f1
--- /dev/null
+++ b/src/Compile/Modifier/CountSentencesModifierCompiler.php
@@ -0,0 +1,21 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty count_sentences modifier plugin
+ * Type: modifier
+ * Name: count_sentences
+ * Purpose: count the number of sentences in a text
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.count.paragraphs.php
+ * count_sentences (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class CountSentencesModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ // find periods, question marks, exclamation marks with a word before but not after.
+ return 'preg_match_all("#\w[\.\?\!](\W|$)#S' . \Smarty\Smarty::$_UTF8_MODIFIER . '", ' . $params[ 0 ] . ', $tmp)';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/CountWordsModifierCompiler.php b/src/Compile/Modifier/CountWordsModifierCompiler.php
new file mode 100644
index 00000000..e1c648ab
--- /dev/null
+++ b/src/Compile/Modifier/CountWordsModifierCompiler.php
@@ -0,0 +1,21 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty count_words modifier plugin
+ * Type: modifier
+ * Name: count_words
+ * Purpose: count the number of words in a text
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.count.words.php count_words (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class CountWordsModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ // expression taken from http://de.php.net/manual/en/function.str-word-count.php#85592
+ return 'preg_match_all(\'/\p{L}[\p{L}\p{Mn}\p{Pd}\\\'\x{2019}]*/' . \Smarty\Smarty::$_UTF8_MODIFIER . '\', ' .
+ $params[ 0 ] . ', $tmp)';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/DefaultModifierCompiler.php b/src/Compile/Modifier/DefaultModifierCompiler.php
new file mode 100644
index 00000000..f802e4d5
--- /dev/null
+++ b/src/Compile/Modifier/DefaultModifierCompiler.php
@@ -0,0 +1,27 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty default modifier plugin
+ * Type: modifier
+ * Name: default
+ * Purpose: designate default value for empty variables
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.default.php default (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class DefaultModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ $output = $params[ 0 ];
+ if (!isset($params[ 1 ])) {
+ $params[ 1 ] = "''";
+ }
+ array_shift($params);
+ foreach ($params as $param) {
+ $output = '(($tmp = ' . $output . ' ?? null)===null||$tmp===\'\' ? ' . $param . ' ?? null : $tmp)';
+ }
+ return $output;
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/EmptyModifierCompiler.php b/src/Compile/Modifier/EmptyModifierCompiler.php
new file mode 100644
index 00000000..6bb6c11c
--- /dev/null
+++ b/src/Compile/Modifier/EmptyModifierCompiler.php
@@ -0,0 +1,19 @@
+<?php
+namespace Smarty\Compile\Modifier;
+use Smarty\CompilerException;
+
+/**
+ * Smarty empty modifier plugin
+ */
+class EmptyModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+
+ if (count($params) !== 1) {
+ throw new CompilerException("Invalid number of arguments for empty. empty expects exactly 1 parameter.");
+ }
+
+ return 'empty(' . $params[0] . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/EscapeModifierCompiler.php b/src/Compile/Modifier/EscapeModifierCompiler.php
new file mode 100644
index 00000000..54e6f34b
--- /dev/null
+++ b/src/Compile/Modifier/EscapeModifierCompiler.php
@@ -0,0 +1,57 @@
+<?php
+namespace Smarty\Compile\Modifier;
+
+use Smarty\Exception;
+
+/**
+ * Smarty escape modifier plugin
+ * Type: modifier
+ * Name: escape
+ * Purpose: escape string for output
+ *
+ * @link https://www.smarty.net/docsv2/en/language.modifier.escape count_characters (Smarty online manual)
+ * @author Rodney Rehm
+ */
+
+class EscapeModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ try {
+ $esc_type = $this->literal_compiler_param($params, 1, 'html');
+ $char_set = $this->literal_compiler_param($params, 2, \Smarty\Smarty::$_CHARSET);
+ $double_encode = $this->literal_compiler_param($params, 3, true);
+ if (!$char_set) {
+ $char_set = \Smarty\Smarty::$_CHARSET;
+ }
+ switch ($esc_type) {
+ case 'html':
+ return 'htmlspecialchars((string)' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ', ' .
+ var_export($double_encode, true) . ')';
+ // no break
+ case 'htmlall':
+ return 'htmlentities(mb_convert_encoding((string)' . $params[ 0 ] . ', \'UTF-8\', ' .
+ var_export($char_set, true) . '), ENT_QUOTES, \'UTF-8\', ' .
+ var_export($double_encode, true) . ')';
+ // no break
+ case 'url':
+ return 'rawurlencode((string)' . $params[ 0 ] . ')';
+ case 'urlpathinfo':
+ return 'str_replace("%2F", "/", rawurlencode((string)' . $params[ 0 ] . '))';
+ case 'quotes':
+ // escape unescaped single quotes
+ return 'preg_replace("%(?<!\\\\\\\\)\'%", "\\\'", (string)' . $params[ 0 ] . ')';
+ case 'javascript':
+ // escape quotes and backslashes, newlines, etc.
+ // see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
+ return 'strtr((string)' .
+ $params[ 0 ] .
+ ', array("\\\\" => "\\\\\\\\", "\'" => "\\\\\'", "\"" => "\\\\\"", "\\r" => "\\\\r",
+ "\\n" => "\\\n", "</" => "<\/", "<!--" => "<\!--", "<s" => "<\s", "<S" => "<\S",
+ "`" => "\\\\`", "\${" => "\\\\\\$\\{"))';
+ }
+ } catch (Exception $e) {
+ // pass through to regular plugin fallback
+ }
+ return '$_smarty_tpl->getSmarty()->getModifierCallback(\'escape\')(' . join(', ', $params) . ')';
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/FromCharsetModifierCompiler.php b/src/Compile/Modifier/FromCharsetModifierCompiler.php
new file mode 100644
index 00000000..606fedf9
--- /dev/null
+++ b/src/Compile/Modifier/FromCharsetModifierCompiler.php
@@ -0,0 +1,21 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty from_charset modifier plugin
+ * Type: modifier
+ * Name: from_charset
+ * Purpose: convert character encoding from $charset to internal encoding
+ *
+ * @author Rodney Rehm
+ */
+
+class FromCharsetModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ if (!isset($params[ 1 ])) {
+ $params[ 1 ] = '"ISO-8859-1"';
+ }
+ return 'mb_convert_encoding(' . $params[ 0 ] . ', "' . addslashes(\Smarty\Smarty::$_CHARSET) . '", ' . $params[ 1 ] . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/IndentModifierCompiler.php b/src/Compile/Modifier/IndentModifierCompiler.php
new file mode 100644
index 00000000..401e24a1
--- /dev/null
+++ b/src/Compile/Modifier/IndentModifierCompiler.php
@@ -0,0 +1,25 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty indent modifier plugin
+ * Type: modifier
+ * Name: indent
+ * Purpose: indent lines of text
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.indent.php indent (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class IndentModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ if (!isset($params[ 1 ])) {
+ $params[ 1 ] = 4;
+ }
+ if (!isset($params[ 2 ])) {
+ $params[ 2 ] = "' '";
+ }
+ return 'preg_replace(\'!^!m\',str_repeat(' . $params[ 2 ] . ',' . $params[ 1 ] . '),' . $params[ 0 ] . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/IssetModifierCompiler.php b/src/Compile/Modifier/IssetModifierCompiler.php
new file mode 100644
index 00000000..0962fbfa
--- /dev/null
+++ b/src/Compile/Modifier/IssetModifierCompiler.php
@@ -0,0 +1,25 @@
+<?php
+namespace Smarty\Compile\Modifier;
+use Smarty\CompilerException;
+
+/**
+ * Smarty isset modifier plugin
+ */
+class IssetModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+
+ $params = array_filter($params, function($v) { return !empty($v); });
+
+ if (count($params) < 1) {
+ throw new CompilerException("Invalid number of arguments for isset. isset expects at least one parameter.");
+ }
+
+ $tests = [];
+ foreach ($params as $param) {
+ $tests[] = 'null !== (' . $param . ' ?? null)';
+ }
+ return '(' . implode(' && ', $tests) . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/LowerModifierCompiler.php b/src/Compile/Modifier/LowerModifierCompiler.php
new file mode 100644
index 00000000..4186b1ec
--- /dev/null
+++ b/src/Compile/Modifier/LowerModifierCompiler.php
@@ -0,0 +1,20 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty lower modifier plugin
+ * Type: modifier
+ * Name: lower
+ * Purpose: convert string to lowercase
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.lower.php lower (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author Uwe Tews
+ */
+
+class LowerModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return 'mb_strtolower((string) ' . $params[ 0 ] . ', \'' . addslashes(\Smarty\Smarty::$_CHARSET) . '\')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/ModifierCompilerInterface.php b/src/Compile/Modifier/ModifierCompilerInterface.php
new file mode 100644
index 00000000..0a39b819
--- /dev/null
+++ b/src/Compile/Modifier/ModifierCompilerInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Smarty\Compile\Modifier;
+
+interface ModifierCompilerInterface {
+
+ /**
+ * Compiles code for the modifier
+ *
+ * @param array $params array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($params, \Smarty\Compiler\Template $compiler);
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/Nl2brModifierCompiler.php b/src/Compile/Modifier/Nl2brModifierCompiler.php
new file mode 100644
index 00000000..074e6772
--- /dev/null
+++ b/src/Compile/Modifier/Nl2brModifierCompiler.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty nl2br modifier plugin
+ * Type: modifier
+ * Name: nl2br
+ * Purpose: insert HTML line breaks before all newlines in a string
+ *
+ * @link https://www.smarty.net/docs/en/language.modifier.nl2br.tpl nl2br (Smarty online manual)
+ */
+
+class Nl2brModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return 'nl2br((string) ' . $params[0] . ', (bool) ' . ($params[1] ?? true) . ')';
+ }
+}
diff --git a/src/Compile/Modifier/NoPrintModifierCompiler.php b/src/Compile/Modifier/NoPrintModifierCompiler.php
new file mode 100644
index 00000000..c9401cfe
--- /dev/null
+++ b/src/Compile/Modifier/NoPrintModifierCompiler.php
@@ -0,0 +1,18 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty noprint modifier plugin
+ * Type: modifier
+ * Name: noprint
+ * Purpose: return an empty string
+ *
+ * @author Uwe Tews
+ */
+
+class NoPrintModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return "''";
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/RoundModifierCompiler.php b/src/Compile/Modifier/RoundModifierCompiler.php
new file mode 100644
index 00000000..33645ea0
--- /dev/null
+++ b/src/Compile/Modifier/RoundModifierCompiler.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty round modifier plugin
+ * Type: modifier
+ * Name: round
+ * Purpose: Returns the rounded value of num to specified precision (number of digits after the decimal point)
+ *
+ * @link https://www.smarty.net/docs/en/language.modifier.round.tpl round (Smarty online manual)
+ */
+
+class RoundModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return 'round((float) ' . $params[0] . ', (int) ' . ($params[1] ?? 0) . ', (int) ' . ($params[2] ?? PHP_ROUND_HALF_UP) . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/StrRepeatModifierCompiler.php b/src/Compile/Modifier/StrRepeatModifierCompiler.php
new file mode 100644
index 00000000..dbe4ba77
--- /dev/null
+++ b/src/Compile/Modifier/StrRepeatModifierCompiler.php
@@ -0,0 +1,18 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty str_repeat modifier plugin
+ * Type: modifier
+ * Name: str_repeat
+ * Purpose: returns string repeated times times
+ *
+ * @link https://www.smarty.net/docs/en/language.modifier.str_repeat.tpl str_repeat (Smarty online manual)
+ */
+
+class StrRepeatModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return 'str_repeat((string) ' . $params[0] . ', (int) ' . $params[1] . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/StringFormatModifierCompiler.php b/src/Compile/Modifier/StringFormatModifierCompiler.php
new file mode 100644
index 00000000..a0c23ae5
--- /dev/null
+++ b/src/Compile/Modifier/StringFormatModifierCompiler.php
@@ -0,0 +1,19 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty string_format modifier plugin
+ * Type: modifier
+ * Name: string_format
+ * Purpose: format strings via sprintf
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.string.format.php string_format (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class StringFormatModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return 'sprintf(' . $params[ 1 ] . ',' . $params[ 0 ] . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/StripModifierCompiler.php b/src/Compile/Modifier/StripModifierCompiler.php
new file mode 100644
index 00000000..78871640
--- /dev/null
+++ b/src/Compile/Modifier/StripModifierCompiler.php
@@ -0,0 +1,25 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty strip modifier plugin
+ * Type: modifier
+ * Name: strip
+ * Purpose: Replace all repeated spaces, newlines, tabs
+ * with a single space or supplied replacement string.
+ * Example: {$var|strip} {$var|strip:"&nbsp;"}
+ * Date: September 25th, 2002
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.strip.php strip (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class StripModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ if (!isset($params[ 1 ])) {
+ $params[ 1 ] = "' '";
+ }
+ return "preg_replace('!\s+!" . \Smarty\Smarty::$_UTF8_MODIFIER . "', {$params[1]},{$params[0]})";
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/StripTagsModifierCompiler.php b/src/Compile/Modifier/StripTagsModifierCompiler.php
new file mode 100644
index 00000000..e8885bc7
--- /dev/null
+++ b/src/Compile/Modifier/StripTagsModifierCompiler.php
@@ -0,0 +1,23 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty strip_tags modifier plugin
+ * Type: modifier
+ * Name: strip_tags
+ * Purpose: strip html tags from text
+ *
+ * @link https://www.smarty.net/docs/en/language.modifier.strip.tags.tpl strip_tags (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class StripTagsModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ if (!isset($params[ 1 ]) || $params[ 1 ] === true || trim($params[ 1 ], '"') === 'true') {
+ return "preg_replace('!<[^>]*?>!', ' ', (string) {$params[0]})";
+ } else {
+ return 'strip_tags((string) ' . $params[ 0 ] . ')';
+ }
+ }
+
+}
diff --git a/src/Compile/Modifier/StrlenModifierCompiler.php b/src/Compile/Modifier/StrlenModifierCompiler.php
new file mode 100644
index 00000000..07a44718
--- /dev/null
+++ b/src/Compile/Modifier/StrlenModifierCompiler.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty strlen modifier plugin
+ * Type: modifier
+ * Name: strlen
+ * Purpose: return the length of the given string
+ *
+ * @link https://www.smarty.net/docs/en/language.modifier.strlen.tpl strlen (Smarty online manual)
+ */
+
+class StrlenModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return 'strlen((string) ' . $params[0] . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/ToCharsetModifierCompiler.php b/src/Compile/Modifier/ToCharsetModifierCompiler.php
new file mode 100644
index 00000000..67a5709d
--- /dev/null
+++ b/src/Compile/Modifier/ToCharsetModifierCompiler.php
@@ -0,0 +1,21 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty to_charset modifier plugin
+ * Type: modifier
+ * Name: to_charset
+ * Purpose: convert character encoding from internal encoding to $charset
+ *
+ * @author Rodney Rehm
+ */
+
+class ToCharsetModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ if (!isset($params[ 1 ])) {
+ $params[ 1 ] = '"ISO-8859-1"';
+ }
+ return 'mb_convert_encoding(' . $params[ 0 ] . ', ' . $params[ 1 ] . ', "' . addslashes(\Smarty\Smarty::$_CHARSET) . '")';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/UnescapeModifierCompiler.php b/src/Compile/Modifier/UnescapeModifierCompiler.php
new file mode 100644
index 00000000..1844ceb9
--- /dev/null
+++ b/src/Compile/Modifier/UnescapeModifierCompiler.php
@@ -0,0 +1,34 @@
+<?php
+namespace Smarty\Compile\Modifier;
+
+/**
+ * Smarty unescape modifier plugin
+ * Type: modifier
+ * Name: unescape
+ * Purpose: unescape html entities
+ *
+ * @author Rodney Rehm
+ */
+
+class UnescapeModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ $esc_type = $this->literal_compiler_param($params, 1, 'html');
+
+ if (!isset($params[ 2 ])) {
+ $params[ 2 ] = '\'' . addslashes(\Smarty\Smarty::$_CHARSET) . '\'';
+ }
+
+ switch ($esc_type) {
+ case 'entity':
+ case 'htmlall':
+ return 'html_entity_decode(mb_convert_encoding(' . $params[ 0 ] . ', ' . $params[ 2 ] . ', \'UTF-8\'), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, ' . $params[ 2 ] . ')';
+ case 'html':
+ return 'htmlspecialchars_decode(' . $params[ 0 ] . ', ENT_QUOTES)';
+ case 'url':
+ return 'rawurldecode(' . $params[ 0 ] . ')';
+ default:
+ return $params[ 0 ];
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Modifier/UpperModifierCompiler.php b/src/Compile/Modifier/UpperModifierCompiler.php
new file mode 100644
index 00000000..9bef41ff
--- /dev/null
+++ b/src/Compile/Modifier/UpperModifierCompiler.php
@@ -0,0 +1,19 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty upper modifier plugin
+ * Type: modifier
+ * Name: lower
+ * Purpose: convert string to uppercase
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.upper.php lower (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class UpperModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ return 'mb_strtoupper((string) ' . $params[ 0 ] . ' ?? \'\', \'' . addslashes(\Smarty\Smarty::$_CHARSET) . '\')';
+ }
+
+}
diff --git a/src/Compile/Modifier/WordWrapModifierCompiler.php b/src/Compile/Modifier/WordWrapModifierCompiler.php
new file mode 100644
index 00000000..4a6d84b0
--- /dev/null
+++ b/src/Compile/Modifier/WordWrapModifierCompiler.php
@@ -0,0 +1,28 @@
+<?php
+namespace Smarty\Compile\Modifier;
+/**
+ * Smarty wordwrap modifier plugin
+ * Type: modifier
+ * Name: wordwrap
+ * Purpose: wrap a string of text at a given length
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.wordwrap.php wordwrap (Smarty online manual)
+ * @author Uwe Tews
+ */
+
+class WordWrapModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ if (!isset($params[ 1 ])) {
+ $params[ 1 ] = 80;
+ }
+ if (!isset($params[ 2 ])) {
+ $params[ 2 ] = '"\n"';
+ }
+ if (!isset($params[ 3 ])) {
+ $params[ 3 ] = 'false';
+ }
+ return 'smarty_mb_wordwrap(' . $params[ 0 ] . ',' . $params[ 1 ] . ',' . $params[ 2 ] . ',' . $params[ 3 ] . ')';
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/ModifierCompiler.php b/src/Compile/ModifierCompiler.php
new file mode 100644
index 00000000..d07763bf
--- /dev/null
+++ b/src/Compile/ModifierCompiler.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Modifier
+ * Compiles code for modifier execution
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile;
+
+use Smarty\Compile\Base;
+use Smarty\Compiler\Template;
+use Smarty\CompilerException;
+
+/**
+ * Smarty Internal Plugin Compile Modifier Class
+ *
+
+
+ */
+class ModifierCompiler extends Base {
+
+ /**
+ * Compiles code for modifier execution
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ $compiler->has_code = true;
+
+ $output = $parameter['value'];
+
+ // loop over list of modifiers
+ foreach ($parameter['modifierlist'] as $single_modifier) {
+ /* @var string $modifier */
+ $modifier = $single_modifier[0];
+ $single_modifier[0] = $output;
+ $params = implode(',', $single_modifier);
+
+ if (!is_object($compiler->getSmarty()->security_policy)
+ || $compiler->getSmarty()->security_policy->isTrustedModifier($modifier, $compiler)
+ ) {
+
+ if ($handler = $compiler->getModifierCompiler($modifier)) {
+ $output = $handler->compile($single_modifier, $compiler);
+
+ } elseif ($compiler->getSmarty()->getModifierCallback($modifier)) {
+ $output = sprintf(
+ '$_smarty_tpl->getSmarty()->getModifierCallback(%s)(%s)',
+ var_export($modifier, true),
+ $params
+ );
+ } elseif ($callback = $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIERCOMPILER)) {
+ $output = (new \Smarty\Compile\Modifier\BCPluginWrapper($callback))->compile($single_modifier, $compiler);
+ } elseif ($function = $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIER)) {
+ if (!is_array($function)) {
+ $output = "{$function}({$params})";
+ } else {
+ $operator = is_object($function[0]) ? '->' : '::';
+ $output = $function[0] . $operator . $function[1] . '(' . $params . ')';
+ }
+ } else {
+ $compiler->trigger_template_error("unknown modifier '{$modifier}'", null, true);
+ }
+ }
+ }
+ return $output;
+ }
+
+ /**
+ * Wether this class will be able to compile the given modifier.
+ * @param string $modifier
+ * @param Template $compiler
+ *
+ * @return bool
+ * @throws CompilerException
+ */
+ public function canCompileForModifier(string $modifier, \Smarty\Compiler\Template $compiler): bool {
+ return $compiler->getModifierCompiler($modifier)
+ || $compiler->getSmarty()->getModifierCallback($modifier)
+ || $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIERCOMPILER)
+ || $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIER);
+ }
+}
diff --git a/src/Compile/ObjectMethodBlockCompiler.php b/src/Compile/ObjectMethodBlockCompiler.php
new file mode 100644
index 00000000..6c05c422
--- /dev/null
+++ b/src/Compile/ObjectMethodBlockCompiler.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Object Block Function
+ * Compiles code for registered objects as block function
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile;
+
+/**
+ * Smarty Internal Plugin Compile Object Block Function Class
+ *
+
+
+ */
+class ObjectMethodBlockCompiler extends BlockCompiler {
+
+ /**
+ * @inheritDoc
+ */
+ protected function getIsCallableCode($tag, $function): string {
+ $callbackObject = "\$_smarty_tpl->getSmarty()->registered_objects['{$tag}'][0]";
+ return "(isset({$callbackObject}) && is_callable(array({$callbackObject}, '{$function}')))";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getFullCallbackCode($tag, $function): string {
+ $callbackObject = "\$_smarty_tpl->getSmarty()->registered_objects['{$tag}'][0]";
+ return "{$callbackObject}->{$function}";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function blockIsCacheable(\Smarty\Smarty $smarty, $function): bool {
+ return true;
+ }
+
+}
diff --git a/src/Compile/ObjectMethodCallCompiler.php b/src/Compile/ObjectMethodCallCompiler.php
new file mode 100644
index 00000000..f3ce6960
--- /dev/null
+++ b/src/Compile/ObjectMethodCallCompiler.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Object Function
+ * Compiles code for registered objects as function
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile;
+
+/**
+ * Smarty Internal Plugin Compile Object Function Class
+ *
+
+
+ */
+class ObjectMethodCallCompiler extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $optional_attributes = ['_any'];
+
+ /**
+ * Compiles code for the execution of function plugin
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ * @param string $tag name of function
+ * @param string $function name of method to call
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ unset($_attr['nocache']);
+ $_assign = null;
+ if (isset($_attr['assign'])) {
+ $_assign = $_attr['assign'];
+ unset($_attr['assign']);
+ }
+ // method or property ?
+ if (is_callable([$compiler->getSmarty()->registered_objects[$tag][0], $function])) {
+ // convert attributes into parameter array string
+ if ($compiler->getSmarty()->registered_objects[$tag][2]) {
+ $_paramsArray = $this->formatParamsArray($_attr);
+ $_params = 'array(' . implode(',', $_paramsArray) . ')';
+ $output = "\$_smarty_tpl->getSmarty()->registered_objects['{$tag}'][0]->{$function}({$_params},\$_smarty_tpl)";
+ } else {
+ $_params = implode(',', $_attr);
+ $output = "\$_smarty_tpl->getSmarty()->registered_objects['{$tag}'][0]->{$function}({$_params})";
+ }
+ } else {
+ // object property
+ $output = "\$_smarty_tpl->getSmarty()->registered_objects['{$tag}'][0]->{$function}";
+ }
+ if (!empty($parameter['modifierlist'])) {
+ $output = $compiler->compileModifier($parameter['modifierlist'], $output);
+ }
+ if (empty($_assign)) {
+ return "<?php echo {$output};?>\n";
+ } else {
+ return "<?php \$_smarty_tpl->assign({$_assign},{$output});?>\n";
+ }
+ }
+}
diff --git a/src/Compile/PrintExpressionCompiler.php b/src/Compile/PrintExpressionCompiler.php
new file mode 100644
index 00000000..99a03901
--- /dev/null
+++ b/src/Compile/PrintExpressionCompiler.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Print Expression
+ * Compiles any tag which will output an expression or variable
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile;
+
+use Smarty\Compile\Base;
+use Smarty\Compiler\BaseCompiler;
+
+/**
+ * Smarty Internal Plugin Compile Print Expression Class
+ *
+
+
+ */
+class PrintExpressionCompiler extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ public $optional_attributes = ['assign'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ protected $option_flags = ['nocache', 'nofilter'];
+
+ /**
+ * Compiles code for generating output from any expression
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ $compiler->has_code = true;
+
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ $output = $parameter['value'];
+ // tag modifier
+ if (!empty($parameter['modifierlist'])) {
+ $output = $compiler->compileModifier($parameter['modifierlist'], $output);
+ }
+ if (isset($_attr['assign'])) {
+ // assign output to variable
+ return "<?php \$_smarty_tpl->assign({$_attr['assign']},{$output});?>";
+ } else {
+ // display value
+ if (!$_attr['nofilter']) {
+ // default modifier
+ if ($compiler->getSmarty()->getDefaultModifiers()) {
+ $modifierlist = [];
+ foreach ($compiler->getSmarty()->getDefaultModifiers() as $key => $single_default_modifier) {
+ preg_match_all(
+ '/(\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|:|[^:]+)/',
+ $single_default_modifier,
+ $mod_array
+ );
+ for ($i = 0, $count = count($mod_array[0]); $i < $count; $i++) {
+ if ($mod_array[0][$i] !== ':') {
+ $modifierlist[$key][] = $mod_array[0][$i];
+ }
+ }
+ }
+
+ $output = $compiler->compileModifier($modifierlist, $output);
+ }
+
+ if ($compiler->getTemplate()->getSmarty()->escape_html) {
+ $output = "htmlspecialchars((string) {$output}, ENT_QUOTES, '" . addslashes(\Smarty\Smarty::$_CHARSET) . "')";
+ }
+
+ }
+ $output = "<?php echo {$output};?>\n";
+ }
+ return $output;
+ }
+
+}
diff --git a/src/Compile/SpecialVariableCompiler.php b/src/Compile/SpecialVariableCompiler.php
new file mode 100644
index 00000000..08f9aa69
--- /dev/null
+++ b/src/Compile/SpecialVariableCompiler.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Special Smarty Variable
+ * Compiles the special $smarty variables
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile;
+
+use Smarty\Compile\Base;
+use Smarty\Compile\Tag\Capture;
+use Smarty\Compile\Tag\ForeachTag;
+use Smarty\Compile\Tag\Section;
+use Smarty\Compiler\Template;
+use Smarty\CompilerException;
+
+/**
+ * Smarty Internal Plugin Compile special Smarty Variable Class
+ *
+
+
+ */
+class SpecialVariableCompiler extends Base {
+
+ /**
+ * Compiles code for the special $smarty variables
+ *
+ * @param array $args array with attributes from parser
+ * @param Template $compiler compiler object
+ * @param array $parameter
+ * @param null $tag
+ * @param null $function
+ *
+ * @return string compiled code
+ * @throws CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ $compiler->has_code = true;
+
+ $_index = preg_split("/\]\[/", substr($parameter, 1, strlen($parameter) - 2));
+ $variable = smarty_strtolower_ascii($compiler->getId($_index[0]));
+ if ($variable === false) {
+ $compiler->trigger_template_error("special \$Smarty variable name index can not be variable", null, true);
+ }
+ if (!isset($compiler->getSmarty()->security_policy)
+ || $compiler->getSmarty()->security_policy->isTrustedSpecialSmartyVar($variable, $compiler)
+ ) {
+ switch ($variable) {
+ case 'foreach':
+ return (new ForeachTag())->compileSpecialVariable($compiler, $_index);
+ case 'section':
+ return (new Section())->compileSpecialVariable($compiler, $_index);
+ case 'capture':
+ return (new Capture())->compileSpecialVariable($compiler, $_index);
+ case 'now':
+ return 'time()';
+ case 'cookies':
+ if (isset($compiler->getSmarty()->security_policy)
+ && !$compiler->getSmarty()->security_policy->allow_super_globals
+ ) {
+ $compiler->trigger_template_error("(secure mode) super globals not permitted");
+ break;
+ }
+ $compiled_ref = '$_COOKIE';
+ break;
+ case 'get':
+ case 'post':
+ case 'env':
+ case 'server':
+ case 'session':
+ case 'request':
+ if (isset($compiler->getSmarty()->security_policy)
+ && !$compiler->getSmarty()->security_policy->allow_super_globals
+ ) {
+ $compiler->trigger_template_error("(secure mode) super globals not permitted");
+ break;
+ }
+ $compiled_ref = '$_' . smarty_strtoupper_ascii($variable);
+ break;
+ case 'template':
+ return '$_smarty_tpl->template_resource';
+ case 'template_object':
+ if (isset($compiler->getSmarty()->security_policy)) {
+ $compiler->trigger_template_error("(secure mode) template_object not permitted");
+ break;
+ }
+ return '$_smarty_tpl';
+ case 'current_dir':
+ return '$_smarty_current_dir';
+ case 'version':
+ return "\\Smarty\\Smarty::SMARTY_VERSION";
+ case 'const':
+ if (isset($compiler->getSmarty()->security_policy)
+ && !$compiler->getSmarty()->security_policy->allow_constants
+ ) {
+ $compiler->trigger_template_error("(secure mode) constants not permitted");
+ break;
+ }
+ if (strpos($_index[1], '$') === false && strpos($_index[1], '\'') === false) {
+ return "(defined('{$_index[1]}') ? constant('{$_index[1]}') : null)";
+ } else {
+ return "(defined({$_index[1]}) ? constant({$_index[1]}) : null)";
+ }
+ // no break
+ case 'config':
+ if (isset($_index[2])) {
+ return "(is_array(\$tmp = \$_smarty_tpl->getConfigVariable($_index[1])) ? \$tmp[$_index[2]] : null)";
+ } else {
+ return "\$_smarty_tpl->getConfigVariable($_index[1])";
+ }
+ // no break
+ case 'ldelim':
+ return "\$_smarty_tpl->getLeftDelimiter()";
+ case 'rdelim':
+ return "\$_smarty_tpl->getRightDelimiter()";
+ default:
+ $compiler->trigger_template_error('$smarty.' . trim($_index[0], "'") . ' is not defined');
+ break;
+ }
+ if (isset($_index[1])) {
+ array_shift($_index);
+ foreach ($_index as $_ind) {
+ $compiled_ref = $compiled_ref . "[$_ind]";
+ }
+ }
+ return $compiled_ref;
+ }
+ }
+}
diff --git a/src/Compile/Tag/Append.php b/src/Compile/Tag/Append.php
new file mode 100644
index 00000000..86eda99e
--- /dev/null
+++ b/src/Compile/Tag/Append.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+/**
+ * Smarty Internal Plugin Compile Append
+ * Compiles the {append} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Append Class
+ *
+
+
+ */
+class Append extends Assign
+{
+
+ /**
+ * @inheritdoc
+ */
+ protected $optional_attributes = ['scope', 'index'];
+
+ /**
+ * Compiles code for the {append} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = array(), $tag = null, $function = null)
+ {
+
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+
+ // map to compile assign attributes
+ if (isset($_attr[ 'index' ])) {
+ $_params[ 'smarty_internal_index' ] = '[' . $_attr[ 'index' ] . ']';
+ unset($_attr[ 'index' ]);
+ } else {
+ $_params[ 'smarty_internal_index' ] = '[]';
+ }
+ $_new_attr = array();
+ foreach ($_attr as $key => $value) {
+ $_new_attr[] = array($key => $value);
+ }
+ // call compile assign
+ return parent::compile($_new_attr, $compiler, $_params);
+ }
+}
diff --git a/src/Compile/Tag/Assign.php b/src/Compile/Tag/Assign.php
new file mode 100644
index 00000000..f53bdf33
--- /dev/null
+++ b/src/Compile/Tag/Assign.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+use Smarty\Smarty;
+
+/**
+ * Smarty Internal Plugin Compile Assign
+ * Compiles the {assign} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Assign Class
+ *
+
+
+ */
+class Assign extends Base
+{
+ /**
+ * @inheritdoc
+ */
+ protected $required_attributes = ['var', 'value'];
+
+ /**
+ * @inheritdoc
+ */
+ protected $optional_attributes = ['scope'];
+
+ /**
+ * @inheritdoc
+ */
+ protected $shorttag_order = ['var', 'value'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $option_flags = array('nocache', 'noscope');
+
+ /**
+ * Compiles code for the {assign} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = array(), $tag = null, $function = null)
+ {
+
+ $_nocache = false;
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+
+ if ($_var = $compiler->getId($_attr[ 'var' ])) {
+ $_var = "'{$_var}'";
+ } else {
+ $_var = $_attr[ 'var' ];
+ }
+ if ($compiler->tag_nocache || $compiler->isNocacheActive()) {
+ $_nocache = true;
+ // create nocache var to make it know for further compiling
+ $compiler->setNocacheInVariable($_attr[ 'var' ]);
+ }
+ // scope setup
+ if ($_attr[ 'noscope' ]) {
+ $_scope = -1;
+ } else {
+ $_scope = isset($_attr['scope']) ? $this->convertScope($_attr['scope']) : null;
+ }
+
+ if (isset($parameter[ 'smarty_internal_index' ])) {
+ $output =
+ "<?php \$_tmp_array = \$_smarty_tpl->getValue({$_var}) ?? [];\n";
+ $output .= "if (!(is_array(\$_tmp_array) || \$_tmp_array instanceof ArrayAccess)) {\n";
+ $output .= "settype(\$_tmp_array, 'array');\n";
+ $output .= "}\n";
+ $output .= "\$_tmp_array{$parameter['smarty_internal_index']} = {$_attr['value']};\n";
+ $output .= "\$_smarty_tpl->assign({$_var}, \$_tmp_array, " . var_export($_nocache, true) . ", " . var_export($_scope, true) . ");?>";
+ } else {
+ $output = "<?php \$_smarty_tpl->assign({$_var}, {$_attr['value']}, " . var_export($_nocache, true) . ", " . var_export($_scope, true) . ");?>";
+ }
+ return $output;
+ }
+}
diff --git a/src/Compile/Tag/BCPluginWrapper.php b/src/Compile/Tag/BCPluginWrapper.php
new file mode 100644
index 00000000..0224250d
--- /dev/null
+++ b/src/Compile/Tag/BCPluginWrapper.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+class BCPluginWrapper extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see Smarty_Internal_CompileBase
+ */
+ public $optional_attributes = array('_any');
+
+ private $callback;
+
+ public function __construct($callback, bool $cacheable = true) {
+ $this->callback = $callback;
+ $this->cacheable = $cacheable;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ return call_user_func($this->callback, $this->getAttributes($compiler, $args), $compiler->getSmarty());
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/Block.php b/src/Compile/Tag/Block.php
new file mode 100644
index 00000000..ce6df4f7
--- /dev/null
+++ b/src/Compile/Tag/Block.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * This file is part of Smarty.
+ *
+ * (c) 2015 Uwe Tews
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\ParseTree\Template;
+
+/**
+ * Smarty Internal Plugin Compile Block Class
+ *
+ * @author Uwe Tews <uwe.tews@googlemail.com>
+ */
+class Block extends Inheritance {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ public $required_attributes = ['name'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ public $shorttag_order = ['name'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $option_flags = ['hide', 'nocache'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ public $optional_attributes = ['assign'];
+
+ /**
+ * Compiles code for the {block} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = array(), $tag = null, $function = null)
+ {
+ if (!isset($compiler->_cache['blockNesting'])) {
+ $compiler->_cache['blockNesting'] = 0;
+ }
+ if ($compiler->_cache['blockNesting'] === 0) {
+ // make sure that inheritance gets initialized in template code
+ $this->registerInit($compiler);
+ $this->option_flags = ['hide', 'nocache', 'append', 'prepend'];
+ } else {
+ $this->option_flags = ['hide', 'nocache'];
+ }
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ ++$compiler->_cache['blockNesting'];
+ $_className = 'Block_' . preg_replace('![^\w]+!', '_', uniqid(mt_rand(), true));
+
+ $this->openTag(
+ $compiler,
+ 'block',
+ [
+ $_attr, $compiler->tag_nocache, $compiler->getParser()->current_buffer,
+ $compiler->getTemplate()->getCompiled()->getNocacheCode(), $_className
+ ]
+ );
+
+ $compiler->getParser()->current_buffer = new Template();
+ $compiler->getTemplate()->getCompiled()->setNocacheCode(false);
+ $compiler->suppressNocacheProcessing = true;
+ }
+}
diff --git a/src/Compile/Tag/BlockClose.php b/src/Compile/Tag/BlockClose.php
new file mode 100644
index 00000000..0c052001
--- /dev/null
+++ b/src/Compile/Tag/BlockClose.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\ParseTree\Template;
+
+/**
+ * Smarty Internal Plugin Compile BlockClose Class
+ */
+class BlockClose extends Inheritance {
+
+ /**
+ * Compiles code for the {/block} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return bool true
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = array(), $tag = null, $function = null)
+ {
+ [$_attr, $_nocache, $_buffer, $_has_nocache_code, $_className] = $this->closeTag($compiler, ['block']);
+
+ $_block = [];
+ if (isset($compiler->_cache['blockParams'])) {
+ $_block = $compiler->_cache['blockParams'][$compiler->_cache['blockNesting']] ?? [];
+ unset($compiler->_cache['blockParams'][$compiler->_cache['blockNesting']]);
+ }
+
+ $_name = $_attr['name'];
+ $_assign = $_attr['assign'] ?? null;
+ unset($_attr[ 'assign' ], $_attr[ 'name' ]);
+
+ foreach ($_attr as $name => $stat) {
+ if ((is_bool($stat) && $stat !== false) || (!is_bool($stat) && $stat !== 'false')) {
+ $_block[ $name ] = 'true';
+ }
+ }
+
+ // get compiled block code
+ $_functionCode = $compiler->getParser()->current_buffer;
+ // setup buffer for template function code
+ $compiler->getParser()->current_buffer = new Template();
+ $output = "<?php\n";
+ $output .= $compiler->cStyleComment(" {block {$_name}} ") . "\n";
+ $output .= "class {$_className} extends \\Smarty\\Runtime\\Block\n";
+ $output .= "{\n";
+ foreach ($_block as $property => $value) {
+ $output .= "public \${$property} = " . var_export($value, true) . ";\n";
+ }
+ $output .= "public function callBlock(\\Smarty\\Template \$_smarty_tpl) {\n";
+
+ $output .= (new \Smarty\Compiler\CodeFrame($compiler->getTemplate()))->insertLocalVariables();
+
+ if ($compiler->getTemplate()->getCompiled()->getNocacheCode()) {
+ $output .= "\$_smarty_tpl->getCached()->hashes['{$compiler->getTemplate()->getCompiled()->nocache_hash}'] = true;\n";
+ }
+ if (isset($_assign)) {
+ $output .= "ob_start();\n";
+ }
+ $output .= "?>\n";
+ $compiler->getParser()->current_buffer->append_subtree(
+ $compiler->getParser(),
+ new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ $output
+ )
+ );
+ $compiler->getParser()->current_buffer->append_subtree($compiler->getParser(), $_functionCode);
+ $output = "<?php\n";
+ if (isset($_assign)) {
+ $output .= "\$_smarty_tpl->assign({$_assign}, ob_get_clean());\n";
+ }
+ $output .= "}\n";
+ $output .= "}\n";
+ $output .= $compiler->cStyleComment(" {/block {$_name}} ") . "\n\n";
+ $output .= "?>\n";
+ $compiler->getParser()->current_buffer->append_subtree(
+ $compiler->getParser(),
+ new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ $output
+ )
+ );
+ $compiler->blockOrFunctionCode .= $compiler->getParser()->current_buffer->to_smarty_php($compiler->getParser());
+
+ $compiler->getParser()->current_buffer = new Template();
+
+ // restore old status
+ $compiler->getTemplate()->getCompiled()->setNocacheCode($_has_nocache_code);
+ $compiler->tag_nocache = $_nocache;
+
+ $compiler->getParser()->current_buffer = $_buffer;
+ $output = "<?php \n";
+ if ($compiler->_cache['blockNesting'] === 1) {
+ $output .= "\$_smarty_tpl->getInheritance()->instanceBlock(\$_smarty_tpl, '$_className', $_name);\n";
+ } else {
+ $output .= "\$_smarty_tpl->getInheritance()->instanceBlock(\$_smarty_tpl, '$_className', $_name, \$this->tplIndex);\n";
+ }
+ $output .= "?>\n";
+ --$compiler->_cache['blockNesting'];
+ if ($compiler->_cache['blockNesting'] === 0) {
+ unset($compiler->_cache['blockNesting']);
+ }
+ $compiler->has_code = true;
+ $compiler->suppressNocacheProcessing = true;
+ return $output;
+ }
+
+} \ No newline at end of file
diff --git a/src/Compile/Tag/BreakTag.php b/src/Compile/Tag/BreakTag.php
new file mode 100644
index 00000000..0ec03df2
--- /dev/null
+++ b/src/Compile/Tag/BreakTag.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Break
+ * Compiles the {break} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Break Class
+ *
+
+
+ */
+class BreakTag extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $optional_attributes = ['levels'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $shorttag_order = ['levels'];
+
+ /**
+ * Tag name may be overloaded by ContinueTag
+ *
+ * @var string
+ */
+ protected $tag = 'break';
+
+ /**
+ * Compiles code for the {break} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = array(), $tag = null, $function = null)
+ {
+ [$levels, $foreachLevels] = $this->checkLevels($args, $compiler);
+ $output = "<?php ";
+ if ($foreachLevels > 0 && $this->tag === 'continue') {
+ $foreachLevels--;
+ }
+ if ($foreachLevels > 0) {
+ /* @var ForeachTag $foreachCompiler */
+ $foreachCompiler = $compiler->getTagCompiler('foreach');
+ $output .= $foreachCompiler->compileRestore($foreachLevels);
+ }
+ $output .= "{$this->tag} {$levels};?>";
+ return $output;
+ }
+
+ /**
+ * check attributes and return array of break and foreach levels
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return array
+ * @throws \Smarty\CompilerException
+ */
+ public function checkLevels($args, \Smarty\Compiler\Template $compiler) {
+ static $_is_loopy = ['for' => true, 'foreach' => true, 'while' => true, 'section' => true];
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ if ($_attr['nocache'] === true) {
+ $compiler->trigger_template_error('nocache option not allowed', null, true);
+ }
+ if (isset($_attr['levels'])) {
+ if (!is_numeric($_attr['levels'])) {
+ $compiler->trigger_template_error('level attribute must be a numeric constant', null, true);
+ }
+ $levels = $_attr['levels'];
+ } else {
+ $levels = 1;
+ }
+ $level_count = $levels;
+
+ $tagStack = $compiler->getTagStack();
+ $stack_count = count($tagStack) - 1;
+
+ $foreachLevels = 0;
+ $lastTag = '';
+ while ($level_count > 0 && $stack_count >= 0) {
+ if (isset($_is_loopy[$tagStack[$stack_count][0]])) {
+ $lastTag = $tagStack[$stack_count][0];
+ if ($level_count === 0) {
+ break;
+ }
+ $level_count--;
+ if ($tagStack[$stack_count][0] === 'foreach') {
+ $foreachLevels++;
+ }
+ }
+ $stack_count--;
+ }
+ if ($level_count !== 0) {
+ $compiler->trigger_template_error("cannot {$this->tag} {$levels} level(s)", null, true);
+ }
+ if ($lastTag === 'foreach' && $this->tag === 'break' && $foreachLevels > 0) {
+ $foreachLevels--;
+ }
+ return [$levels, $foreachLevels];
+ }
+}
diff --git a/src/Compile/Tag/Call.php b/src/Compile/Tag/Call.php
new file mode 100644
index 00000000..501460c5
--- /dev/null
+++ b/src/Compile/Tag/Call.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Function_Call
+ * Compiles the calls of user defined tags defined by {function}
+ *
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Function_Call Class
+ */
+class Call extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ public $required_attributes = ['name'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ public $shorttag_order = ['name'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ public $optional_attributes = ['_any'];
+
+ /**
+ * Compiles the calls of user defined tags defined by {function}
+ *
+ * @param array $args array with attributes from parser
+ * @param object $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ // save possible attributes
+ if (isset($_attr['assign'])) {
+ // output will be stored in a smarty variable instead of being displayed
+ $_assign = $_attr['assign'];
+ }
+ //$_name = trim($_attr['name'], "''");
+ $_name = $_attr['name'];
+ unset($_attr['name'], $_attr['assign'], $_attr['nocache']);
+ // set flag (compiled code of {function} must be included in cache file
+ if (!$compiler->getTemplate()->caching || $compiler->isNocacheActive() || $compiler->tag_nocache) {
+ $_nocache = 'true';
+ } else {
+ $_nocache = 'false';
+ }
+ $_paramsArray = $this->formatParamsArray($_attr);
+ $_params = 'array(' . implode(',', $_paramsArray) . ')';
+ //$compiler->suppressNocacheProcessing = true;
+ // was there an assign attribute
+ if (isset($_assign)) {
+ $_output =
+ "<?php ob_start();\n\$_smarty_tpl->getSmarty()->getRuntime('TplFunction')->callTemplateFunction(\$_smarty_tpl, {$_name}, {$_params}, {$_nocache});\n\$_smarty_tpl->assign({$_assign}, ob_get_clean());?>\n";
+ } else {
+ $_output =
+ "<?php \$_smarty_tpl->getSmarty()->getRuntime('TplFunction')->callTemplateFunction(\$_smarty_tpl, {$_name}, {$_params}, {$_nocache});?>\n";
+ }
+ return $_output;
+ }
+}
diff --git a/src/Compile/Tag/Capture.php b/src/Compile/Tag/Capture.php
new file mode 100644
index 00000000..fc8804a0
--- /dev/null
+++ b/src/Compile/Tag/Capture.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Capture Class
+ *
+
+
+ */
+class Capture extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ public $shorttag_order = ['name'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ public $optional_attributes = ['name', 'assign', 'append'];
+
+ /**
+ * Compiles code for the {$smarty.capture.xxx}
+ *
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ */
+ public static function compileSpecialVariable(
+ \Smarty\Compiler\Template $compiler,
+ $parameter = null
+ ) {
+ return '$_smarty_tpl->getSmarty()->getRuntime(\'Capture\')->getBuffer($_smarty_tpl' .
+ (isset($parameter[1]) ? ", {$parameter[ 1 ]})" : ')');
+ }
+
+ /**
+ * Compiles code for the {capture} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param null $parameter
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ $buffer = $_attr['name'] ?? "'default'";
+ $assign = $_attr['assign'] ?? 'null';
+ $append = $_attr['append'] ?? 'null';
+
+ $compiler->_cache['capture_stack'][] = $compiler->tag_nocache;
+ if ($compiler->tag_nocache) {
+ // push a virtual {nocache} tag onto the stack.
+ $compiler->openTag('nocache');
+ }
+
+ $_output = "<?php \$_smarty_tpl->getSmarty()->getRuntime('Capture')->open(\$_smarty_tpl, $buffer, $assign, $append);?>";
+ return $_output;
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/CaptureClose.php b/src/Compile/Tag/CaptureClose.php
new file mode 100644
index 00000000..0d553a2b
--- /dev/null
+++ b/src/Compile/Tag/CaptureClose.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Capture
+ * Compiles the {capture} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Captureclose Class
+ *
+
+
+ */
+class CaptureClose extends Base {
+
+ /**
+ * Compiles code for the {/capture} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param null $parameter
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ if (array_pop($compiler->_cache['capture_stack'])) {
+ // pop the virtual {nocache} tag from the stack.
+ $compiler->closeTag('nocache');
+ $compiler->tag_nocache = true;
+ }
+
+ return "<?php \$_smarty_tpl->getSmarty()->getRuntime('Capture')->close(\$_smarty_tpl);?>";
+ }
+}
diff --git a/src/Compile/Tag/ConfigLoad.php b/src/Compile/Tag/ConfigLoad.php
new file mode 100644
index 00000000..6425749e
--- /dev/null
+++ b/src/Compile/Tag/ConfigLoad.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Config Load
+ * Compiles the {config load} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+use Smarty\Smarty;
+
+/**
+ * Smarty Internal Plugin Compile Config Load Class
+ *
+
+
+ */
+class ConfigLoad extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $required_attributes = ['file'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $shorttag_order = ['file', 'section'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $optional_attributes = ['section', 'scope'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $option_flags = ['nocache', 'noscope'];
+
+ /**
+ * Compiles code for the {config_load} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ if ($_attr['nocache'] === true) {
+ $compiler->trigger_template_error('nocache option not allowed', null, true);
+ }
+ // save possible attributes
+ $conf_file = $_attr['file'];
+ $section = $_attr['section'] ?? 'null';
+
+ // create config object
+ return "<?php\n\$_smarty_tpl->configLoad({$conf_file}, {$section});\n?>\n";
+ }
+}
diff --git a/src/Compile/Tag/ContinueTag.php b/src/Compile/Tag/ContinueTag.php
new file mode 100644
index 00000000..82abe6ce
--- /dev/null
+++ b/src/Compile/Tag/ContinueTag.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Continue
+ * Compiles the {continue} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+/**
+ * Smarty Internal Plugin Compile Continue Class
+ *
+
+
+ */
+class ContinueTag extends BreakTag {
+
+ /**
+ * Tag name
+ *
+ * @var string
+ */
+ protected $tag = 'continue';
+}
diff --git a/src/Compile/Tag/Debug.php b/src/Compile/Tag/Debug.php
new file mode 100644
index 00000000..bd899892
--- /dev/null
+++ b/src/Compile/Tag/Debug.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Debug
+ * Compiles the {debug} tag.
+ * It opens a window the the Smarty Debugging Console.
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Debug Class
+ *
+
+
+ */
+class Debug extends Base {
+
+ /**
+ * Compiles code for the {debug} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param object $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes, may trigger errors
+ $this->getAttributes($compiler, $args);
+
+ // compile always as nocache
+ $compiler->tag_nocache = true;
+ // display debug template
+ $_output =
+ "<?php \$_smarty_debug = new \\Smarty\\Debug;\n \$_smarty_debug->display_debug(\$_smarty_tpl);\n";
+ $_output .= "unset(\$_smarty_debug);\n?>";
+ return $_output;
+ }
+}
diff --git a/src/Compile/Tag/ElseIfTag.php b/src/Compile/Tag/ElseIfTag.php
new file mode 100644
index 00000000..60b888a8
--- /dev/null
+++ b/src/Compile/Tag/ElseIfTag.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile ElseIf Class
+ *
+
+
+ */
+class ElseIfTag extends Base {
+
+ /**
+ * Compiles code for the {elseif} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ [$nesting, $nocache_pushed] = $this->closeTag($compiler, ['if', 'elseif']);
+
+ if (!isset($parameter['if condition'])) {
+ $compiler->trigger_template_error('missing elseif condition', null, true);
+ }
+ $assignCode = '';
+ $var = '';
+ if (is_array($parameter['if condition'])) {
+ $condition_by_assign = true;
+ if (is_array($parameter['if condition']['var'])) {
+ $var = $parameter['if condition']['var']['var'];
+ } else {
+ $var = $parameter['if condition']['var'];
+ }
+ if ($compiler->isNocacheActive()) {
+ // create nocache var to make it know for further compiling
+ $compiler->setNocacheInVariable($var);
+ }
+ $prefixVar = $compiler->getNewPrefixVariable();
+ $assignCode = "<?php {$prefixVar} = {$parameter[ 'if condition' ][ 'value' ]};?>\n";
+ $assignCompiler = new Assign();
+ $assignAttr = [];
+ $assignAttr[]['value'] = $prefixVar;
+ if (is_array($parameter['if condition']['var'])) {
+ $assignAttr[]['var'] = $parameter['if condition']['var']['var'];
+ $assignCode .= $assignCompiler->compile(
+ $assignAttr,
+ $compiler,
+ ['smarty_internal_index' => $parameter['if condition']['var']['smarty_internal_index']]
+ );
+ } else {
+ $assignAttr[]['var'] = $parameter['if condition']['var'];
+ $assignCode .= $assignCompiler->compile($assignAttr, $compiler, []);
+ }
+ } else {
+ $condition_by_assign = false;
+ }
+ $prefixCode = $compiler->getPrefixCode();
+ if (empty($prefixCode)) {
+ if ($condition_by_assign) {
+ $this->openTag($compiler, 'elseif', [$nesting + 1, $compiler->tag_nocache]);
+ $_output = $compiler->appendCode("<?php } else {\n?>", $assignCode);
+ return $compiler->appendCode($_output, "<?php if ({$prefixVar}) {?>");
+ } else {
+ $this->openTag($compiler, 'elseif', [$nesting, $nocache_pushed]);
+ return "<?php } elseif ({$parameter['if condition']}) {?>";
+ }
+ } else {
+ $_output = $compiler->appendCode("<?php } else {\n?>", $prefixCode);
+ $this->openTag($compiler, 'elseif', [$nesting + 1, $nocache_pushed]);
+ if ($condition_by_assign) {
+ $_output = $compiler->appendCode($_output, $assignCode);
+ return $compiler->appendCode($_output, "<?php if ({$prefixVar}) {?>");
+ } else {
+ return $compiler->appendCode($_output, "<?php if ({$parameter['if condition']}) {?>");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/ElseTag.php b/src/Compile/Tag/ElseTag.php
new file mode 100644
index 00000000..68a9a023
--- /dev/null
+++ b/src/Compile/Tag/ElseTag.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Else Class
+ *
+
+
+ */
+class ElseTag extends Base {
+
+ /**
+ * Compiles code for the {else} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ [$nesting, $compiler->tag_nocache] = $this->closeTag($compiler, ['if', 'elseif']);
+ $this->openTag($compiler, 'else', [$nesting, $compiler->tag_nocache]);
+ return '<?php } else { ?>';
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/EvalTag.php b/src/Compile/Tag/EvalTag.php
new file mode 100644
index 00000000..c6535570
--- /dev/null
+++ b/src/Compile/Tag/EvalTag.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Eval
+ * Compiles the {eval} tag.
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Eval Class
+ *
+
+
+ */
+class EvalTag extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ public $required_attributes = ['var'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ public $optional_attributes = ['assign'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ public $shorttag_order = ['var', 'assign'];
+
+ /**
+ * Compiles code for the {eval} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param object $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ if (isset($_attr['assign'])) {
+ // output will be stored in a smarty variable instead of being displayed
+ $_assign = $_attr['assign'];
+ }
+ // create template object
+ $_output =
+ "\$_template = new \\Smarty\\Template('eval:'.{$_attr[ 'var' ]}, \$_smarty_tpl->getSmarty(), \$_smarty_tpl);";
+ //was there an assign attribute?
+ if (isset($_assign)) {
+ $_output .= "\$_smarty_tpl->assign($_assign,\$_template->fetch());";
+ } else {
+ $_output .= 'echo $_template->fetch();';
+ }
+ return "<?php $_output ?>";
+ }
+}
diff --git a/src/Compile/Tag/ExtendsTag.php b/src/Compile/Tag/ExtendsTag.php
new file mode 100644
index 00000000..2e9e0aed
--- /dev/null
+++ b/src/Compile/Tag/ExtendsTag.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile extend
+ * Compiles the {extends} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+/**
+ * Smarty Internal Plugin Compile extend Class
+ *
+
+
+ */
+class ExtendsTag extends Inheritance {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $required_attributes = ['file'];
+
+ /**
+ * Array of names of optional attribute required by tag
+ * use array('_any') if there is no restriction of attributes names
+ *
+ * @var array
+ */
+ protected $optional_attributes = ['extends_resource'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $shorttag_order = ['file'];
+
+ /**
+ * Compiles code for the {extends} tag extends: resource
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ if ($_attr['nocache'] === true) {
+ $compiler->trigger_template_error('nocache option not allowed', $compiler->getParser()->lex->line - 1);
+ }
+ if (strpos($_attr['file'], '$_tmp') !== false) {
+ $compiler->trigger_template_error('illegal value for file attribute', $compiler->getParser()->lex->line - 1);
+ }
+ // add code to initialize inheritance
+ $this->registerInit($compiler, true);
+ $file = trim($_attr['file'], '\'"');
+ if (strlen($file) > 8 && substr($file, 0, 8) === 'extends:') {
+ // generate code for each template
+ $files = array_reverse(explode('|', substr($file, 8)));
+ $i = 0;
+ foreach ($files as $file) {
+ if ($file[0] === '"') {
+ $file = trim($file, '".');
+ } else {
+ $file = "'{$file}'";
+ }
+ $i++;
+ if ($i === count($files) && isset($_attr['extends_resource'])) {
+ $this->compileEndChild($compiler);
+ }
+ $this->compileInclude($compiler, $file);
+ }
+ if (!isset($_attr['extends_resource'])) {
+ $this->compileEndChild($compiler);
+ }
+ } else {
+ $this->compileEndChild($compiler, $_attr['file']);
+ }
+ $compiler->has_code = false;
+ return '';
+ }
+
+ /**
+ * Add code for inheritance endChild() method to end of template
+ *
+ * @param \Smarty\Compiler\Template $compiler
+ * @param null|string $template optional inheritance parent template
+ *
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ private function compileEndChild(\Smarty\Compiler\Template $compiler, $template = null) {
+ $compiler->getParser()->template_postfix[] = new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ '<?php $_smarty_tpl->getInheritance()->endChild($_smarty_tpl' .
+ (isset($template) ? ", {$template}, \$_smarty_current_dir" : '') . ");\n?>"
+ );
+ }
+
+ /**
+ * Add code for including subtemplate to end of template
+ *
+ * @param \Smarty\Compiler\Template $compiler
+ * @param string $template subtemplate name
+ *
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ private function compileInclude(\Smarty\Compiler\Template $compiler, $template) {
+ $compiler->getParser()->template_postfix[] = new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ $compiler->compileTag(
+ 'include',
+ [
+ $template,
+ ['scope' => 'parent'],
+ ]
+ )
+ );
+ }
+
+ /**
+ * Create source code for {extends} from source components array
+ *
+ * @param \Smarty\Template $template
+ *
+ * @return string
+ */
+ public static function extendsSourceArrayCode(\Smarty\Template $template) {
+ $resources = [];
+ foreach ($template->getSource()->components as $source) {
+ $resources[] = $source->resource;
+ }
+ return $template->getLeftDelimiter() . 'extends file=\'extends:' . join('|', $resources) .
+ '\' extends_resource=true' . $template->getRightDelimiter();
+ }
+}
diff --git a/src/Compile/Tag/ForClose.php b/src/Compile/Tag/ForClose.php
new file mode 100644
index 00000000..bde1a075
--- /dev/null
+++ b/src/Compile/Tag/ForClose.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile For
+ * Compiles the {for} {forelse} {/for} tags
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Forclose Class
+ *
+
+
+ */
+class ForClose extends Base {
+
+ /**
+ * Compiles code for the {/for} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param object $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting--;
+
+ [$openTag, $nocache_pushed] = $this->closeTag($compiler, ['for', 'forelse']);
+ $output = "<?php }\n";
+ if ($openTag !== 'forelse') {
+ $output .= "}\n";
+ }
+ $output .= "?>";
+
+ if ($nocache_pushed) {
+ // pop the pushed virtual nocache tag
+ $this->closeTag($compiler, 'nocache');
+ $compiler->tag_nocache = true;
+ }
+
+ return $output;
+ }
+}
diff --git a/src/Compile/Tag/ForElse.php b/src/Compile/Tag/ForElse.php
new file mode 100644
index 00000000..a754a0d5
--- /dev/null
+++ b/src/Compile/Tag/ForElse.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Forelse Class
+ *
+
+
+ */
+class ForElse extends Base {
+
+ /**
+ * Compiles code for the {forelse} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param object $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ [$tagName, $nocache_pushed] = $this->closeTag($compiler, ['for']);
+ $this->openTag($compiler, 'forelse', ['forelse', $nocache_pushed]);
+ return "<?php }} else { ?>";
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/ForTag.php b/src/Compile/Tag/ForTag.php
new file mode 100644
index 00000000..8066d83e
--- /dev/null
+++ b/src/Compile/Tag/ForTag.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile For Class
+ *
+
+
+ */
+class ForTag extends Base {
+
+ /**
+ * Compiles code for the {for} tag
+ * Smarty supports two different syntax's:
+ * - {for $var in $array}
+ * For looping over arrays or iterators
+ * - {for $x=0; $x<$y; $x++}
+ * For general loops
+ * The parser is generating different sets of attribute by which this compiler can
+ * determine which syntax is used.
+ *
+ * @param array $args array with attributes from parser
+ * @param object $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting++;
+ if ($parameter === 0) {
+ $this->required_attributes = ['start', 'to'];
+ $this->optional_attributes = ['max', 'step'];
+ } else {
+ $this->required_attributes = ['start', 'ifexp', 'var', 'step'];
+ $this->optional_attributes = [];
+ }
+
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ $output = "<?php\n";
+ if ($parameter === 1) {
+ foreach ($_attr['start'] as $_statement) {
+ if (is_array($_statement['var'])) {
+ $var = $_statement['var']['var'];
+ $index = $_statement['var']['smarty_internal_index'];
+ } else {
+ $var = $_statement['var'];
+ $index = '';
+ }
+ $output .= "\$_smarty_tpl->assign($var, null);\n";
+ $output .= "\$_smarty_tpl->tpl_vars[$var]->value{$index} = {$_statement['value']};\n";
+ }
+ if (is_array($_attr['var'])) {
+ $var = $_attr['var']['var'];
+ $index = $_attr['var']['smarty_internal_index'];
+ } else {
+ $var = $_attr['var'];
+ $index = '';
+ }
+ $output .= "if ($_attr[ifexp]) {\nfor (\$_foo=true;$_attr[ifexp]; \$_smarty_tpl->tpl_vars[$var]->value{$index}$_attr[step]) {\n";
+ } else {
+ $_statement = $_attr['start'];
+ if (is_array($_statement['var'])) {
+ $var = $_statement['var']['var'];
+ $index = $_statement['var']['smarty_internal_index'];
+ } else {
+ $var = $_statement['var'];
+ $index = '';
+ }
+ $output .= "\$_smarty_tpl->assign($var, null);";
+ if (isset($_attr['step'])) {
+ $output .= "\$_smarty_tpl->tpl_vars[$var]->step = $_attr[step];";
+ } else {
+ $output .= "\$_smarty_tpl->tpl_vars[$var]->step = 1;";
+ }
+ if (isset($_attr['max'])) {
+ $output .= "\$_smarty_tpl->tpl_vars[$var]->total = (int) min(ceil((\$_smarty_tpl->tpl_vars[$var]->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs(\$_smarty_tpl->tpl_vars[$var]->step)),$_attr[max]);\n";
+ } else {
+ $output .= "\$_smarty_tpl->tpl_vars[$var]->total = (int) ceil((\$_smarty_tpl->tpl_vars[$var]->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs(\$_smarty_tpl->tpl_vars[$var]->step));\n";
+ }
+ $output .= "if (\$_smarty_tpl->tpl_vars[$var]->total > 0) {\n";
+ $output .= "for (\$_smarty_tpl->tpl_vars[$var]->value{$index} = $_statement[value], \$_smarty_tpl->tpl_vars[$var]->iteration = 1;\$_smarty_tpl->tpl_vars[$var]->iteration <= \$_smarty_tpl->tpl_vars[$var]->total;\$_smarty_tpl->tpl_vars[$var]->value{$index} += \$_smarty_tpl->tpl_vars[$var]->step, \$_smarty_tpl->tpl_vars[$var]->iteration++) {\n";
+ $output .= "\$_smarty_tpl->tpl_vars[$var]->first = \$_smarty_tpl->tpl_vars[$var]->iteration === 1;";
+ $output .= "\$_smarty_tpl->tpl_vars[$var]->last = \$_smarty_tpl->tpl_vars[$var]->iteration === \$_smarty_tpl->tpl_vars[$var]->total;";
+ }
+ $output .= '?>';
+
+ if ($compiler->tag_nocache) {
+ // push a {nocache} tag onto the stack to prevent caching of this for loop
+ $this->openTag($compiler, 'nocache');
+ }
+
+ $this->openTag($compiler, 'for', ['for', $compiler->tag_nocache]);
+
+ return $output;
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/ForeachClose.php b/src/Compile/Tag/ForeachClose.php
new file mode 100644
index 00000000..80599149
--- /dev/null
+++ b/src/Compile/Tag/ForeachClose.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Foreach
+ * Compiles the {foreach} {foreachelse} {/foreach} tags
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Foreachclose Class
+ *
+
+
+ */
+class ForeachClose extends Base {
+
+ /**
+ * Compiles code for the {/foreach} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting--;
+
+ [$openTag, $nocache_pushed, $localVariablePrefix, $item, $restore] = $this->closeTag($compiler, ['foreach', 'foreachelse']);
+
+ if ($nocache_pushed) {
+ // pop the pushed virtual nocache tag
+ $this->closeTag($compiler, 'nocache');
+ $compiler->tag_nocache = true;
+ }
+
+ $output = "<?php\n";
+ if ($restore) {
+ $output .= "\$_smarty_tpl->setVariable('{$item}', {$localVariablePrefix}Backup);\n";
+ }
+ $output .= "}\n";
+ /* @var \Smarty\Compile\Tag\ForeachTag $foreachCompiler */
+ $foreachCompiler = $compiler->getTagCompiler('foreach');
+ $output .= $foreachCompiler->compileRestore(1);
+ $output .= "?>";
+ return $output;
+ }
+}
diff --git a/src/Compile/Tag/ForeachElse.php b/src/Compile/Tag/ForeachElse.php
new file mode 100644
index 00000000..3397bb4f
--- /dev/null
+++ b/src/Compile/Tag/ForeachElse.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Foreachelse Class
+ *
+
+
+ */
+class ForeachElse extends Base {
+
+ /**
+ * Compiles code for the {foreachelse} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ [$openTag, $nocache_pushed, $localVariablePrefix, $item, $restore] = $this->closeTag($compiler, ['foreach']);
+ $this->openTag($compiler, 'foreachelse', ['foreachelse', $nocache_pushed, $localVariablePrefix, $item, false]);
+ $output = "<?php\n";
+ if ($restore) {
+ $output .= "\$_smarty_tpl->setVariable('{$item}', {$localVariablePrefix}Backup);\n";
+ }
+ $output .= "}\nif ({$localVariablePrefix}DoElse) {\n?>";
+ return $output;
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/ForeachSection.php b/src/Compile/Tag/ForeachSection.php
new file mode 100644
index 00000000..dd257401
--- /dev/null
+++ b/src/Compile/Tag/ForeachSection.php
@@ -0,0 +1,206 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile ForeachSection
+ * Shared methods for {foreach} {section} tags
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile ForeachSection Class
+ *
+
+
+ */
+abstract class ForeachSection extends Base {
+
+ /**
+ * Name of this tag
+ *
+ * @var string
+ */
+ protected $tagName = '';
+
+ /**
+ * Valid properties of $smarty.xxx variable
+ *
+ * @var array
+ */
+ protected $nameProperties = [];
+
+ /**
+ * {section} tag has no item properties
+ *
+ * @var array
+ */
+ protected $itemProperties = null;
+
+ /**
+ * {section} tag has always name attribute
+ *
+ * @var bool
+ */
+ protected $isNamed = true;
+
+ /**
+ * @var array
+ */
+ protected $matchResults = [];
+
+ /**
+ * Preg search pattern
+ *
+ * @var string
+ */
+ private $propertyPreg = '';
+
+ /**
+ * Offsets in preg match result
+ *
+ * @var array
+ */
+ private $resultOffsets = [];
+
+ /**
+ * Start offset
+ *
+ * @var int
+ */
+ private $startOffset = 0;
+
+ /**
+ * Scan sources for used tag attributes
+ *
+ * @param array $attributes
+ * @param \Smarty\Compiler\Template $compiler
+ *
+ * @throws \Smarty\Exception
+ */
+ protected function scanForProperties($attributes, \Smarty\Compiler\Template $compiler) {
+ $this->propertyPreg = '~(';
+ $this->startOffset = 1;
+ $this->resultOffsets = [];
+ $this->matchResults = ['named' => [], 'item' => []];
+ if (isset($attributes['name'])) {
+ $this->buildPropertyPreg(true, $attributes);
+ }
+ if (isset($this->itemProperties)) {
+ if ($this->isNamed) {
+ $this->propertyPreg .= '|';
+ }
+ $this->buildPropertyPreg(false, $attributes);
+ }
+ $this->propertyPreg .= ')\W~i';
+ // Template source
+ $this->matchTemplateSource($compiler);
+ // Parent template source
+ $this->matchParentTemplateSource($compiler);
+ }
+
+ /**
+ * Build property preg string
+ *
+ * @param bool $named
+ * @param array $attributes
+ */
+ private function buildPropertyPreg($named, $attributes) {
+ if ($named) {
+ $this->resultOffsets['named'] = $this->startOffset = $this->startOffset + 3;
+ $this->propertyPreg .= "(([\$]smarty[.]{$this->tagName}[.]" .
+ ($this->tagName === 'section' ? "|[\[]\s*" : '') .
+ "){$attributes['name']}[.](";
+ $properties = $this->nameProperties;
+ } else {
+ $this->resultOffsets['item'] = $this->startOffset = $this->startOffset + 2;
+ $this->propertyPreg .= "([\$]{$attributes['item']}[@](";
+ $properties = $this->itemProperties;
+ }
+ $propName = reset($properties);
+ while ($propName) {
+ $this->propertyPreg .= "{$propName}";
+ $propName = next($properties);
+ if ($propName) {
+ $this->propertyPreg .= '|';
+ }
+ }
+ $this->propertyPreg .= '))';
+ }
+
+ /**
+ * Find matches in source string
+ *
+ * @param string $source
+ */
+ private function matchProperty($source) {
+ preg_match_all($this->propertyPreg, $source, $match);
+ foreach ($this->resultOffsets as $key => $offset) {
+ foreach ($match[$offset] as $m) {
+ if (!empty($m)) {
+ $this->matchResults[$key][smarty_strtolower_ascii($m)] = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Find matches in template source
+ *
+ * @param \Smarty\Compiler\Template $compiler
+ */
+ private function matchTemplateSource(\Smarty\Compiler\Template $compiler) {
+ $this->matchProperty($compiler->getParser()->lex->data);
+ }
+
+ /**
+ * Find matches in all parent template source
+ *
+ * @param \Smarty\Compiler\Template $compiler
+ *
+ * @throws \Smarty\Exception
+ */
+ private function matchParentTemplateSource(\Smarty\Compiler\Template $compiler) {
+ // search parent compiler template source
+ $nextCompiler = $compiler;
+ while ($nextCompiler !== $nextCompiler->getParentCompiler()) {
+ $nextCompiler = $nextCompiler->getParentCompiler();
+ if ($compiler !== $nextCompiler) {
+ // get template source
+ $_content = $nextCompiler->getTemplate()->getSource()->getContent();
+ if ($_content !== '') {
+ // run pre filter if required
+ $_content = $nextCompiler->getSmarty()->runPreFilters($_content, $nextCompiler->getTemplate());
+ $this->matchProperty($_content);
+ }
+ }
+ }
+ }
+
+ /**
+ * Compiles code for the {$smarty.foreach.xxx} or {$smarty.section.xxx}tag
+ *
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compileSpecialVariable(\Smarty\Compiler\Template $compiler, $parameter) {
+ $tag = smarty_strtolower_ascii(trim($parameter[0], '"\''));
+ $name = isset($parameter[1]) ? $compiler->getId($parameter[1]) : false;
+ if (!$name) {
+ $compiler->trigger_template_error("missing or illegal \$smarty.{$tag} name attribute", null, true);
+ }
+ $property = isset($parameter[2]) ? smarty_strtolower_ascii($compiler->getId($parameter[2])) : false;
+ if (!$property || !in_array($property, $this->nameProperties)) {
+ $compiler->trigger_template_error("missing or illegal \$smarty.{$tag} property attribute", null, true);
+ }
+ $tagVar = "'__smarty_{$tag}_{$name}'";
+ return "(\$_smarty_tpl->getValue({$tagVar})['{$property}'] ?? null)";
+ }
+}
diff --git a/src/Compile/Tag/ForeachTag.php b/src/Compile/Tag/ForeachTag.php
new file mode 100644
index 00000000..c77a5464
--- /dev/null
+++ b/src/Compile/Tag/ForeachTag.php
@@ -0,0 +1,285 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+/**
+ * Smarty Internal Plugin Compile Foreach Class
+ *
+
+
+ */
+class ForeachTag extends ForeachSection {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $required_attributes = ['from', 'item'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $optional_attributes = ['name', 'key', 'properties'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $shorttag_order = ['from', 'item', 'key', 'name'];
+
+ /**
+ * counter
+ *
+ * @var int
+ */
+ private static $counter = 0;
+
+ /**
+ * Name of this tag
+ *
+ * @var string
+ */
+ protected $tagName = 'foreach';
+
+ /**
+ * Valid properties of $smarty.foreach.name.xxx variable
+ *
+ * @var array
+ */
+ protected $nameProperties = ['first', 'last', 'index', 'iteration', 'show', 'total'];
+
+ /**
+ * Valid properties of $item@xxx variable
+ *
+ * @var array
+ */
+ protected $itemProperties = ['first', 'last', 'index', 'iteration', 'show', 'total', 'key'];
+
+ /**
+ * Flag if tag had name attribute
+ *
+ * @var bool
+ */
+ protected $isNamed = false;
+
+ /**
+ * Compiles code for the {foreach} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting++;
+ // init
+ $this->isNamed = false;
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ $from = $_attr['from'];
+ $item = $compiler->getId($_attr['item']);
+ if ($item === false) {
+ $item = $this->getVariableName($_attr['item']);
+ }
+ $key = $name = null;
+ $attributes = ['item' => $item];
+ if (isset($_attr['key'])) {
+ $key = $compiler->getId($_attr['key']);
+ if ($key === false) {
+ $key = $this->getVariableName($_attr['key']);
+ }
+ $attributes['key'] = $key;
+ }
+ if (isset($_attr['name'])) {
+ $this->isNamed = true;
+ $name = $attributes['name'] = $compiler->getId($_attr['name']);
+ }
+ foreach ($attributes as $a => $v) {
+ if ($v === false) {
+ $compiler->trigger_template_error("'{$a}' attribute/variable has illegal value", null, true);
+ }
+ }
+ $fromName = $this->getVariableName($_attr['from']);
+ if ($fromName) {
+ foreach (['item', 'key'] as $a) {
+ if (isset($attributes[$a]) && $attributes[$a] === $fromName) {
+ $compiler->trigger_template_error(
+ "'{$a}' and 'from' may not have same variable name '{$fromName}'",
+ null,
+ true
+ );
+ }
+ }
+ }
+
+ $itemVar = "\$_smarty_tpl->getVariable('{$item}')";
+ $localVariablePrefix = '$foreach' . self::$counter++;
+
+ // search for used tag attributes
+ $itemAttr = [];
+ $namedAttr = [];
+ $this->scanForProperties($attributes, $compiler);
+ if (!empty($this->matchResults['item'])) {
+ $itemAttr = $this->matchResults['item'];
+ }
+ if (!empty($this->matchResults['named'])) {
+ $namedAttr = $this->matchResults['named'];
+ }
+ if (isset($_attr['properties']) && preg_match_all('/[\'](.*?)[\']/', $_attr['properties'], $match)) {
+ foreach ($match[1] as $prop) {
+ if (in_array($prop, $this->itemProperties)) {
+ $itemAttr[$prop] = true;
+ } else {
+ $compiler->trigger_template_error("Invalid property '{$prop}'", null, true);
+ }
+ }
+ if ($this->isNamed) {
+ foreach ($match[1] as $prop) {
+ if (in_array($prop, $this->nameProperties)) {
+ $nameAttr[$prop] = true;
+ } else {
+ $compiler->trigger_template_error("Invalid property '{$prop}'", null, true);
+ }
+ }
+ }
+ }
+ if (isset($itemAttr['first'])) {
+ $itemAttr['index'] = true;
+ }
+ if (isset($namedAttr['first'])) {
+ $namedAttr['index'] = true;
+ }
+ if (isset($namedAttr['last'])) {
+ $namedAttr['iteration'] = true;
+ $namedAttr['total'] = true;
+ }
+ if (isset($itemAttr['last'])) {
+ $itemAttr['iteration'] = true;
+ $itemAttr['total'] = true;
+ }
+ if (isset($namedAttr['show'])) {
+ $namedAttr['total'] = true;
+ }
+ if (isset($itemAttr['show'])) {
+ $itemAttr['total'] = true;
+ }
+ $keyTerm = '';
+ if (isset($attributes['key'])) {
+ $keyTerm = "\$_smarty_tpl->getVariable('{$key}')->value => ";
+ }
+ if (isset($itemAttr['key'])) {
+ $keyTerm = "{$itemVar}->key => ";
+ }
+ if ($this->isNamed) {
+ $foreachVar = "\$_smarty_tpl->tpl_vars['__smarty_foreach_{$attributes['name']}']";
+ }
+ $needTotal = isset($itemAttr['total']);
+
+ if ($compiler->tag_nocache) {
+ // push a {nocache} tag onto the stack to prevent caching of this block
+ $this->openTag($compiler, 'nocache');
+ }
+
+ // Register tag
+ $this->openTag(
+ $compiler,
+ 'foreach',
+ ['foreach', $compiler->tag_nocache, $localVariablePrefix, $item, !empty($itemAttr)]
+ );
+
+ // generate output code
+ $output = "<?php\n";
+ $output .= "\$_from = \$_smarty_tpl->getSmarty()->getRuntime('Foreach')->init(\$_smarty_tpl, $from, " .
+ var_export($item, true);
+ if ($name || $needTotal || $key) {
+ $output .= ', ' . var_export($needTotal, true);
+ }
+ if ($name || $key) {
+ $output .= ', ' . var_export($key, true);
+ }
+ if ($name) {
+ $output .= ', ' . var_export($name, true) . ', ' . var_export($namedAttr, true);
+ }
+ $output .= ");\n";
+ if (isset($itemAttr['show'])) {
+ $output .= "{$itemVar}->show = ({$itemVar}->total > 0);\n";
+ }
+ if (isset($itemAttr['iteration'])) {
+ $output .= "{$itemVar}->iteration = 0;\n";
+ }
+ if (isset($itemAttr['index'])) {
+ $output .= "{$itemVar}->index = -1;\n";
+ }
+ $output .= "{$localVariablePrefix}DoElse = true;\n";
+ $output .= "foreach (\$_from ?? [] as {$keyTerm}{$itemVar}->value) {\n";
+ $output .= "{$localVariablePrefix}DoElse = false;\n";
+ if (isset($attributes['key']) && isset($itemAttr['key'])) {
+ $output .= "\$_smarty_tpl->assign('{$key}', {$itemVar}->key);\n";
+ }
+ if (isset($itemAttr['iteration'])) {
+ $output .= "{$itemVar}->iteration++;\n";
+ }
+ if (isset($itemAttr['index'])) {
+ $output .= "{$itemVar}->index++;\n";
+ }
+ if (isset($itemAttr['first'])) {
+ $output .= "{$itemVar}->first = !{$itemVar}->index;\n";
+ }
+ if (isset($itemAttr['last'])) {
+ $output .= "{$itemVar}->last = {$itemVar}->iteration === {$itemVar}->total;\n";
+ }
+ if (isset($foreachVar)) {
+ if (isset($namedAttr['iteration'])) {
+ $output .= "{$foreachVar}->value['iteration']++;\n";
+ }
+ if (isset($namedAttr['index'])) {
+ $output .= "{$foreachVar}->value['index']++;\n";
+ }
+ if (isset($namedAttr['first'])) {
+ $output .= "{$foreachVar}->value['first'] = !{$foreachVar}->value['index'];\n";
+ }
+ if (isset($namedAttr['last'])) {
+ $output .= "{$foreachVar}->value['last'] = {$foreachVar}->value['iteration'] === {$foreachVar}->value['total'];\n";
+ }
+ }
+ if (!empty($itemAttr)) {
+ $output .= "{$localVariablePrefix}Backup = clone \$_smarty_tpl->getVariable('{$item}');\n";
+ }
+ $output .= '?>';
+ return $output;
+ }
+
+ /**
+ * Get variable name from string
+ *
+ * @param string $input
+ *
+ * @return bool|string
+ */
+ private function getVariableName($input) {
+ if (preg_match('~^[$]_smarty_tpl->getValue\([\'"]*([0-9]*[a-zA-Z_]\w*)[\'"]*\]\)$~', $input, $match)) {
+ return $match[1];
+ }
+ return false;
+ }
+
+ /**
+ * Compiles code for to restore saved template variables
+ *
+ * @param int $levels number of levels to restore
+ *
+ * @return string compiled code
+ */
+ public function compileRestore($levels) {
+ return "\$_smarty_tpl->getSmarty()->getRuntime('Foreach')->restore(\$_smarty_tpl, {$levels});";
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/FunctionClose.php b/src/Compile/Tag/FunctionClose.php
new file mode 100644
index 00000000..6e208ffe
--- /dev/null
+++ b/src/Compile/Tag/FunctionClose.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Function
+ * Compiles the {function} {/function} tags
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Functionclose Class
+ *
+
+
+ */
+class FunctionClose extends Base {
+
+ /**
+ * Compiler object
+ *
+ * @var object
+ */
+ private $compiler = null;
+
+ /**
+ * Compiles code for the {/function} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param object|\Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return bool true
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $this->compiler = $compiler;
+ $saved_data = $this->closeTag($compiler, ['function']);
+ $_attr = $saved_data[0];
+ $_name = trim($_attr['name'], '\'"');
+ $parentCompiler = $compiler->getParentCompiler();
+ $parentCompiler->tpl_function[$_name]['compiled_filepath'] =
+ $parentCompiler->getTemplate()->getCompiled()->filepath;
+ $parentCompiler->tpl_function[$_name]['uid'] = $compiler->getTemplate()->getSource()->uid;
+ $_parameter = $_attr;
+ unset($_parameter['name']);
+ // default parameter
+ $_paramsArray = $this->formatParamsArray($_attr);
+
+ $_paramsCode = (new \Smarty\Compiler\CodeFrame($compiler->getTemplate()))->insertLocalVariables();
+
+ if (!empty($_paramsArray)) {
+ $_params = 'array(' . implode(',', $_paramsArray) . ')';
+ $_paramsCode .= "\$params = array_merge($_params, \$params);\n";
+ }
+ $_functionCode = $compiler->getParser()->current_buffer;
+ // setup buffer for template function code
+ $compiler->getParser()->current_buffer = new \Smarty\ParseTree\Template();
+
+ $_funcName = "smarty_template_function_{$_name}_{$compiler->getTemplate()->getCompiled()->nocache_hash}";
+ $_funcNameCaching = $_funcName . '_nocache';
+
+ if ($compiler->getTemplate()->getCompiled()->getNocacheCode()) {
+ $parentCompiler->tpl_function[$_name]['call_name_caching'] = $_funcNameCaching;
+ $output = "<?php\n";
+ $output .= $compiler->cStyleComment(" {$_funcNameCaching} ") . "\n";
+ $output .= "if (!function_exists('{$_funcNameCaching}')) {\n";
+ $output .= "function {$_funcNameCaching} (\\Smarty\\Template \$_smarty_tpl,\$params) {\n";
+
+ $output .= "ob_start();\n";
+ $output .= "\$_smarty_tpl->getCompiled()->setNocacheCode(true);\n";
+ $output .= $_paramsCode;
+ $output .= "foreach (\$params as \$key => \$value) {\n\$_smarty_tpl->assign(\$key, \$value);\n}\n";
+ $output .= "\$params = var_export(\$params, true);\n";
+ $output .= "echo \"/*%%SmartyNocache:{$compiler->getTemplate()->getCompiled()->nocache_hash}%%*/<?php ";
+ $output .= "\\\$_smarty_tpl->getSmarty()->getRuntime('TplFunction')->saveTemplateVariables(\\\$_smarty_tpl, '{$_name}');\nforeach (\$params as \\\$key => \\\$value) {\n\\\$_smarty_tpl->assign(\\\$key, \\\$value);\n}\n?>";
+ $output .= "/*/%%SmartyNocache:{$compiler->getTemplate()->getCompiled()->nocache_hash}%%*/\";?>";
+ $compiler->getParser()->current_buffer->append_subtree(
+ $compiler->getParser(),
+ new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ $output
+ )
+ );
+ $compiler->getParser()->current_buffer->append_subtree($compiler->getParser(), $_functionCode);
+ $output = "<?php echo \"/*%%SmartyNocache:{$compiler->getTemplate()->getCompiled()->nocache_hash}%%*/<?php ";
+ $output .= "\\\$_smarty_tpl->getSmarty()->getRuntime('TplFunction')->restoreTemplateVariables(\\\$_smarty_tpl, '{$_name}');?>\n";
+ $output .= "/*/%%SmartyNocache:{$compiler->getTemplate()->getCompiled()->nocache_hash}%%*/\";\n?>";
+ $output .= "<?php echo str_replace('{$compiler->getTemplate()->getCompiled()->nocache_hash}', \$_smarty_tpl->getCompiled()->nocache_hash ?? '', ob_get_clean());\n";
+ $output .= "}\n}\n";
+ $output .= $compiler->cStyleComment("/ {$_funcName}_nocache ") . "\n\n";
+ $output .= "?>\n";
+ $compiler->getParser()->current_buffer->append_subtree(
+ $compiler->getParser(),
+ new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ $output
+ )
+ );
+ $_functionCode = new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ preg_replace_callback(
+ "/((<\?php )?echo '\/\*%%SmartyNocache:{$compiler->getTemplate()->getCompiled()->nocache_hash}%%\*\/([\S\s]*?)\/\*\/%%SmartyNocache:{$compiler->getTemplate()->getCompiled()->nocache_hash}%%\*\/';(\?>\n)?)/",
+ [$this, 'removeNocache'],
+ $_functionCode->to_smarty_php($compiler->getParser())
+ )
+ );
+ }
+ $parentCompiler->tpl_function[$_name]['call_name'] = $_funcName;
+ $output = "<?php\n";
+ $output .= $compiler->cStyleComment(" {$_funcName} ") . "\n";
+ $output .= "if (!function_exists('{$_funcName}')) {\n";
+ $output .= "function {$_funcName}(\\Smarty\\Template \$_smarty_tpl,\$params) {\n";
+ $output .= $_paramsCode;
+ $output .= "foreach (\$params as \$key => \$value) {\n\$_smarty_tpl->assign(\$key, \$value);\n}\n";
+ $output .= "?>\n";
+ $compiler->getParser()->current_buffer->append_subtree(
+ $compiler->getParser(),
+ new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ $output
+ )
+ );
+ $compiler->getParser()->current_buffer->append_subtree($compiler->getParser(), $_functionCode);
+ $output = "<?php\n}}\n";
+ $output .= $compiler->cStyleComment("/ {$_funcName} ") . "\n\n";
+ $output .= "?>\n";
+ $compiler->getParser()->current_buffer->append_subtree(
+ $compiler->getParser(),
+ new \Smarty\ParseTree\Tag(
+ $compiler->getParser(),
+ $output
+ )
+ );
+ $parentCompiler->blockOrFunctionCode .= $compiler->getParser()->current_buffer->to_smarty_php($compiler->getParser());
+ // restore old buffer
+ $compiler->getParser()->current_buffer = $saved_data[1];
+ // restore old status
+ $compiler->getTemplate()->getCompiled()->setNocacheCode($saved_data[2]);
+ $compiler->getTemplate()->caching = $saved_data[3];
+ return true;
+ }
+
+ /**
+ * Remove nocache code
+ *
+ * @param $match
+ *
+ * @return string
+ */
+ public function removeNocache($match) {
+ $hash = $this->compiler->getTemplate()->getCompiled()->nocache_hash;
+ $code =
+ preg_replace(
+ "/((<\?php )?echo '\/\*%%SmartyNocache:{$hash}%%\*\/)|(\/\*\/%%SmartyNocache:{$hash}%%\*\/';(\?>\n)?)/",
+ '',
+ $match[0]
+ );
+ return str_replace(['\\\'', '\\\\\''], ['\'', '\\\''], $code);
+ }
+}
diff --git a/src/Compile/Tag/FunctionTag.php b/src/Compile/Tag/FunctionTag.php
new file mode 100644
index 00000000..aa41abdb
--- /dev/null
+++ b/src/Compile/Tag/FunctionTag.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Function Class
+ *
+
+
+ */
+class FunctionTag extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $required_attributes = ['name'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $shorttag_order = ['name'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $optional_attributes = ['_any'];
+
+ /**
+ * Compiles code for the {function} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return bool true
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ if ($_attr['nocache'] === true) {
+ $compiler->trigger_template_error('nocache option not allowed', null, true);
+ }
+ unset($_attr['nocache']);
+ $_name = trim($_attr['name'], '\'"');
+
+ if (!preg_match('/^[a-zA-Z0-9_\x80-\xff]+$/', $_name)) {
+ $compiler->trigger_template_error("Function name contains invalid characters: {$_name}", null, true);
+ }
+
+ $compiler->getParentCompiler()->tpl_function[$_name] = [];
+ $save = [
+ $_attr, $compiler->getParser()->current_buffer, $compiler->getTemplate()->getCompiled()->getNocacheCode(),
+ $compiler->getTemplate()->caching,
+ ];
+ $this->openTag($compiler, 'function', $save);
+ // Init temporary context
+ $compiler->getParser()->current_buffer = new \Smarty\ParseTree\Template();
+ $compiler->getTemplate()->getCompiled()->setNocacheCode(false);
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/IfClose.php b/src/Compile/Tag/IfClose.php
new file mode 100644
index 00000000..12f7e442
--- /dev/null
+++ b/src/Compile/Tag/IfClose.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile If
+ * Compiles the {if} {else} {elseif} {/if} tags
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Ifclose Class
+ *
+
+
+ */
+class IfClose extends Base {
+
+ /**
+ * Compiles code for the {/if} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ [$nesting, $nocache_pushed] = $this->closeTag($compiler, ['if', 'else', 'elseif']);
+
+ if ($nocache_pushed) {
+ // pop the pushed virtual nocache tag
+ $this->closeTag($compiler, 'nocache');
+ $compiler->tag_nocache = true;
+ }
+
+ $tmp = '';
+ for ($i = 0; $i < $nesting; $i++) {
+ $tmp .= '}';
+ }
+ return "<?php {$tmp}?>";
+ }
+}
diff --git a/src/Compile/Tag/IfTag.php b/src/Compile/Tag/IfTag.php
new file mode 100644
index 00000000..84bf477c
--- /dev/null
+++ b/src/Compile/Tag/IfTag.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile If Class
+ *
+
+
+ */
+class IfTag extends Base {
+
+ /**
+ * Compiles code for the {if} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+
+ if ($compiler->tag_nocache) {
+ // push a {nocache} tag onto the stack to prevent caching of this block
+ $this->openTag($compiler, 'nocache');
+ }
+
+ $this->openTag($compiler, 'if', [1, $compiler->tag_nocache]);
+
+ if (!isset($parameter['if condition'])) {
+ $compiler->trigger_template_error('missing if condition', null, true);
+ }
+ if (is_array($parameter['if condition'])) {
+ if (is_array($parameter['if condition']['var'])) {
+ $var = $parameter['if condition']['var']['var'];
+ } else {
+ $var = $parameter['if condition']['var'];
+ }
+ if ($compiler->isNocacheActive()) {
+ // create nocache var to make it know for further compiling
+ $compiler->setNocacheInVariable($var);
+ }
+ $prefixVar = $compiler->getNewPrefixVariable();
+ $_output = "<?php {$prefixVar} = {$parameter[ 'if condition' ][ 'value' ]};?>\n";
+ $assignAttr = [];
+ $assignAttr[]['value'] = $prefixVar;
+ $assignCompiler = new Assign();
+ if (is_array($parameter['if condition']['var'])) {
+ $assignAttr[]['var'] = $parameter['if condition']['var']['var'];
+ $_output .= $assignCompiler->compile(
+ $assignAttr,
+ $compiler,
+ ['smarty_internal_index' => $parameter['if condition']['var']['smarty_internal_index']]
+ );
+ } else {
+ $assignAttr[]['var'] = $parameter['if condition']['var'];
+ $_output .= $assignCompiler->compile($assignAttr, $compiler, []);
+ }
+ $_output .= "<?php if ({$prefixVar}) {?>";
+ return $_output;
+ } else {
+ return "<?php if ({$parameter['if condition']}) {?>";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/IncludeTag.php b/src/Compile/Tag/IncludeTag.php
new file mode 100644
index 00000000..f7619cc7
--- /dev/null
+++ b/src/Compile/Tag/IncludeTag.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Include
+ * Compiles the {include} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+use Smarty\Compiler\Template;
+use Smarty\Data;
+use Smarty\Smarty;
+use Smarty\Template\Compiled;
+
+/**
+ * Smarty Internal Plugin Compile Include Class
+ *
+
+
+ */
+class IncludeTag extends Base {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ protected $required_attributes = ['file'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ protected $shorttag_order = ['file'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ protected $option_flags = ['nocache', 'inline', 'caching'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BaseCompiler
+ */
+ protected $optional_attributes = ['_any'];
+
+ /**
+ * Compiles code for the {include} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param Template $compiler compiler object
+ *
+ * @return string
+ * @throws \Exception
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $uid = $t_hash = null;
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ $fullResourceName = $source_resource = $_attr['file'];
+ $variable_template = false;
+ // parse resource_name
+ if (preg_match('/^([\'"])(([A-Za-z0-9_\-]{2,})[:])?(([^$()]+)|(.+))\1$/', $source_resource, $match)) {
+ $type = !empty($match[3]) ? $match[3] : $compiler->getTemplate()->getSmarty()->default_resource_type;
+ $name = !empty($match[5]) ? $match[5] : $match[6];
+ $handler = \Smarty\Resource\BasePlugin::load($compiler->getSmarty(), $type);
+ if ($handler->recompiled) {
+ $variable_template = true;
+ }
+ if (!$variable_template) {
+ if ($type !== 'string') {
+ $fullResourceName = "{$type}:{$name}";
+ $compiled = $compiler->getParentCompiler()->getTemplate()->getCompiled();
+ if (isset($compiled->includes[$fullResourceName])) {
+ $compiled->includes[$fullResourceName]++;
+ } else {
+ if ("{$compiler->getTemplate()->getSource()->type}:{$compiler->getTemplate()->getSource()->name}" ==
+ $fullResourceName
+ ) {
+ // recursive call of current template
+ $compiled->includes[$fullResourceName] = 2;
+ } else {
+ $compiled->includes[$fullResourceName] = 1;
+ }
+ }
+ $fullResourceName = $match[1] . $fullResourceName . $match[1];
+ }
+ }
+ }
+ // scope setup
+ $_scope = isset($_attr['scope']) ? $this->convertScope($_attr['scope']) : 0;
+
+ // assume caching is off
+ $_caching = Smarty::CACHING_OFF;
+
+ // caching was on and {include} is not in nocache mode
+ if ($compiler->getTemplate()->caching && !$compiler->isNocacheActive()) {
+ $_caching = \Smarty\Template::CACHING_NOCACHE_CODE;
+ }
+
+ /*
+ * if the {include} tag provides individual parameter for caching or compile_id
+ * the subtemplate must not be included into the common cache file and is treated like
+ * a call in nocache mode.
+ *
+ */
+
+ $call_nocache = $compiler->isNocacheActive();
+ if ($_attr['nocache'] !== true && $_attr['caching']) {
+ $_caching = $_new_caching = (int)$_attr['caching'];
+ $call_nocache = true;
+ } else {
+ $_new_caching = Smarty::CACHING_LIFETIME_CURRENT;
+ }
+ if (isset($_attr['cache_lifetime'])) {
+ $_cache_lifetime = $_attr['cache_lifetime'];
+ $call_nocache = true;
+ $_caching = $_new_caching;
+ } else {
+ $_cache_lifetime = '$_smarty_tpl->cache_lifetime';
+ }
+ if (isset($_attr['cache_id'])) {
+ $_cache_id = $_attr['cache_id'];
+ $call_nocache = true;
+ $_caching = $_new_caching;
+ } else {
+ $_cache_id = '$_smarty_tpl->cache_id';
+ }
+
+ // assign attribute
+ if (isset($_attr['assign'])) {
+ // output will be stored in a smarty variable instead of being displayed
+ if ($_assign = $compiler->getId($_attr['assign'])) {
+ $_assign = "'{$_assign}'";
+ if ($call_nocache) {
+ // create nocache var to make it know for further compiling
+ $compiler->setNocacheInVariable($_attr['assign']);
+ }
+ } else {
+ $_assign = $_attr['assign'];
+ }
+ }
+ $has_compiled_template = false;
+
+ // delete {include} standard attributes
+ unset($_attr['file'], $_attr['assign'], $_attr['cache_id'], $_attr['cache_lifetime'], $_attr['nocache'], $_attr['caching'], $_attr['scope'], $_attr['inline']);
+ // remaining attributes must be assigned as smarty variable
+ $_vars = 'array()';
+ if (!empty($_attr)) {
+ $_pairs = [];
+ // create variables
+ foreach ($_attr as $key => $value) {
+ $_pairs[] = "'$key'=>$value";
+ }
+ $_vars = 'array(' . join(',', $_pairs) . ')';
+ }
+ if ($call_nocache) {
+ $compiler->tag_nocache = true;
+ }
+ $_output = "<?php ";
+ // was there an assign attribute
+ if (isset($_assign)) {
+ $_output .= "ob_start();\n";
+ }
+ $_output .= "\$_smarty_tpl->renderSubTemplate({$fullResourceName}, $_cache_id, \$_smarty_tpl->compile_id, " .
+ "$_caching, $_cache_lifetime, $_vars, (int) {$_scope}, \$_smarty_current_dir);\n";
+ if (isset($_assign)) {
+ $_output .= "\$_smarty_tpl->assign({$_assign}, ob_get_clean(), false, {$_scope});\n";
+ }
+ $_output .= "?>";
+ return $_output;
+ }
+
+}
diff --git a/src/Compile/Tag/Inheritance.php b/src/Compile/Tag/Inheritance.php
new file mode 100644
index 00000000..6764651a
--- /dev/null
+++ b/src/Compile/Tag/Inheritance.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Shared Inheritance
+ * Shared methods for {extends} and {block} tags
+ *
+
+
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Compile Shared Inheritance Class
+ *
+
+
+ */
+abstract class Inheritance extends Base
+{
+ /**
+ * Compile inheritance initialization code as prefix
+ *
+ * @param \Smarty\Compiler\Template $compiler
+ * @param bool|false $initChildSequence if true force child template
+ */
+ public static function postCompile(\Smarty\Compiler\Template $compiler, $initChildSequence = false)
+ {
+ $compiler->prefixCompiledCode .= "<?php \$_smarty_tpl->getInheritance()->init(\$_smarty_tpl, " .
+ var_export($initChildSequence, true) . ");\n?>\n";
+ }
+
+ /**
+ * Register post compile callback to compile inheritance initialization code
+ *
+ * @param \Smarty\Compiler\Template $compiler
+ * @param bool|false $initChildSequence if true force child template
+ */
+ public function registerInit(\Smarty\Compiler\Template $compiler, $initChildSequence = false)
+ {
+ if ($initChildSequence || !isset($compiler->_cache[ 'inheritanceInit' ])) {
+ $compiler->registerPostCompileCallback(
+ array(self::class, 'postCompile'),
+ array($initChildSequence),
+ 'inheritanceInit',
+ $initChildSequence
+ );
+ $compiler->_cache[ 'inheritanceInit' ] = true;
+ }
+ }
+}
diff --git a/src/Compile/Tag/Ldelim.php b/src/Compile/Tag/Ldelim.php
new file mode 100644
index 00000000..5a48d3ad
--- /dev/null
+++ b/src/Compile/Tag/Ldelim.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Ldelim
+ * Compiles the {ldelim} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Ldelim Class
+ *
+
+
+ */
+class Ldelim extends Base {
+
+ /**
+ * Compiles code for the {ldelim} tag
+ * This tag does output the left delimiter
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $_attr = $this->getAttributes($compiler, $args);
+ if ($_attr['nocache'] === true) {
+ $compiler->trigger_template_error('nocache option not allowed', null, true);
+ }
+ return $compiler->getTemplate()->getLeftDelimiter();
+ }
+}
diff --git a/src/Compile/Tag/Nocache.php b/src/Compile/Tag/Nocache.php
new file mode 100644
index 00000000..5e2cbd31
--- /dev/null
+++ b/src/Compile/Tag/Nocache.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Nocache Class
+ *
+
+
+ */
+class Nocache extends Base {
+
+ /**
+ * Array of names of valid option flags
+ *
+ * @var array
+ */
+ protected $option_flags = [];
+
+ /**
+ * Compiles code for the {nocache} tag
+ * This tag does not generate compiled output. It only sets a compiler flag.
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return bool
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $this->openTag($compiler, 'nocache');
+ // this tag does not return compiled code
+ $compiler->has_code = false;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/NocacheClose.php b/src/Compile/Tag/NocacheClose.php
new file mode 100644
index 00000000..cacbf701
--- /dev/null
+++ b/src/Compile/Tag/NocacheClose.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Nocache
+ * Compiles the {nocache} {/nocache} tags.
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Nocacheclose Class
+ *
+
+
+ */
+class NocacheClose extends Base {
+
+ /**
+ * Compiles code for the {/nocache} tag
+ * This tag does not generate compiled output. It only sets a compiler flag.
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return bool
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $this->closeTag($compiler, ['nocache']);
+ // this tag does not return compiled code
+ $compiler->has_code = false;
+ return true;
+ }
+}
diff --git a/src/Compile/Tag/Rdelim.php b/src/Compile/Tag/Rdelim.php
new file mode 100644
index 00000000..87bd1889
--- /dev/null
+++ b/src/Compile/Tag/Rdelim.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Rdelim
+ * Compiles the {rdelim} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+/**
+ * Smarty Internal Plugin Compile Rdelim Class
+ *
+
+
+ */
+class Rdelim extends Ldelim {
+
+ /**
+ * Compiles code for the {rdelim} tag
+ * This tag does output the right delimiter.
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ parent::compile($args, $compiler);
+ return $compiler->getTemplate()->getRightDelimiter();
+ }
+}
diff --git a/src/Compile/Tag/Section.php b/src/Compile/Tag/Section.php
new file mode 100644
index 00000000..de9202c5
--- /dev/null
+++ b/src/Compile/Tag/Section.php
@@ -0,0 +1,398 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+/**
+ * Smarty Internal Plugin Compile Section Class
+ *
+
+
+ */
+class Section extends ForeachSection {
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $required_attributes = ['name', 'loop'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $shorttag_order = ['name', 'loop'];
+
+ /**
+ * Attribute definition: Overwrites base class.
+ *
+ * @var array
+ * @see BasePlugin
+ */
+ protected $optional_attributes = ['start', 'step', 'max', 'show', 'properties'];
+
+ /**
+ * counter
+ *
+ * @var int
+ */
+ protected $counter = 0;
+
+ /**
+ * Name of this tag
+ *
+ * @var string
+ */
+ protected $tagName = 'section';
+
+ /**
+ * Valid properties of $smarty.section.name.xxx variable
+ *
+ * @var array
+ */
+ protected $nameProperties = [
+ 'first', 'last', 'index', 'iteration', 'show', 'total', 'rownum', 'index_prev',
+ 'index_next', 'loop',
+ ];
+
+ /**
+ * {section} tag has no item properties
+ *
+ * @var array
+ */
+ protected $itemProperties = null;
+
+ /**
+ * {section} tag has always name attribute
+ *
+ * @var bool
+ */
+ protected $isNamed = true;
+
+ /**
+ * Compiles code for the {section} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ * @throws \Smarty\Exception
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting++;
+ // check and get attributes
+ $_attr = $this->getAttributes($compiler, $args);
+ $attributes = ['name' => $compiler->getId($_attr['name'])];
+ unset($_attr['name']);
+ foreach ($attributes as $a => $v) {
+ if ($v === false) {
+ $compiler->trigger_template_error("'{$a}' attribute/variable has illegal value", null, true);
+ }
+ }
+ $local = "\$__section_{$attributes['name']}_" . $this->counter++ . '_';
+ $sectionVar = "\$_smarty_tpl->tpl_vars['__smarty_section_{$attributes['name']}']";
+
+ if ($compiler->tag_nocache) {
+ // push a {nocache} tag onto the stack to prevent caching of this block
+ $this->openTag('nocache');
+ }
+
+ $this->openTag($compiler, 'section', ['section', $compiler->tag_nocache]);
+
+ $initLocal = [];
+ $initNamedProperty = [];
+ $initFor = [];
+ $incFor = [];
+ $cmpFor = [];
+ $propValue = [
+ 'index' => "{$sectionVar}->value['index']", 'show' => 'true', 'step' => 1,
+ 'iteration' => "{$local}iteration",
+ ];
+ $propType = ['index' => 2, 'iteration' => 2, 'show' => 0, 'step' => 0,];
+ // search for used tag attributes
+ $this->scanForProperties($attributes, $compiler);
+ if (!empty($this->matchResults['named'])) {
+ $namedAttr = $this->matchResults['named'];
+ }
+ if (isset($_attr['properties']) && preg_match_all("/['](.*?)[']/", $_attr['properties'], $match)) {
+ foreach ($match[1] as $prop) {
+ if (in_array($prop, $this->nameProperties)) {
+ $namedAttr[$prop] = true;
+ } else {
+ $compiler->trigger_template_error("Invalid property '{$prop}'", null, true);
+ }
+ }
+ }
+ $namedAttr['index'] = true;
+ $output = "<?php\n";
+ foreach ($_attr as $attr_name => $attr_value) {
+ switch ($attr_name) {
+ case 'loop':
+ if (is_numeric($attr_value)) {
+ $v = (int)$attr_value;
+ $t = 0;
+ } else {
+ $v = "(is_array(@\$_loop=$attr_value) ? count(\$_loop) : max(0, (int) \$_loop))";
+ $t = 1;
+ }
+ if ($t === 1) {
+ $initLocal['loop'] = $v;
+ $v = "{$local}loop";
+ }
+ break;
+ case 'show':
+ if (is_bool($attr_value)) {
+ $v = $attr_value ? 'true' : 'false';
+ $t = 0;
+ } else {
+ $v = "(bool) $attr_value";
+ $t = 3;
+ }
+ break;
+ case 'step':
+ if (is_numeric($attr_value)) {
+ $v = (int)$attr_value;
+ $v = ($v === 0) ? 1 : $v;
+ $t = 0;
+ break;
+ }
+ $initLocal['step'] = "((int)@$attr_value) === 0 ? 1 : (int)@$attr_value";
+ $v = "{$local}step";
+ $t = 2;
+ break;
+ case 'max':
+ case 'start':
+ if (is_numeric($attr_value)) {
+ $v = (int)$attr_value;
+ $t = 0;
+ break;
+ }
+ $v = "(int)@$attr_value";
+ $t = 3;
+ break;
+ }
+ if ($t === 3 && $compiler->getId($attr_value)) {
+ $t = 1;
+ }
+ $propValue[$attr_name] = $v;
+ $propType[$attr_name] = $t;
+ }
+ if (isset($namedAttr['step'])) {
+ $initNamedProperty['step'] = $propValue['step'];
+ }
+ if (isset($namedAttr['iteration'])) {
+ $propValue['iteration'] = "{$sectionVar}->value['iteration']";
+ }
+ $incFor['iteration'] = "{$propValue['iteration']}++";
+ $initFor['iteration'] = "{$propValue['iteration']} = 1";
+ if ($propType['step'] === 0) {
+ if ($propValue['step'] === 1) {
+ $incFor['index'] = "{$sectionVar}->value['index']++";
+ } elseif ($propValue['step'] > 1) {
+ $incFor['index'] = "{$sectionVar}->value['index'] += {$propValue['step']}";
+ } else {
+ $incFor['index'] = "{$sectionVar}->value['index'] -= " . -$propValue['step'];
+ }
+ } else {
+ $incFor['index'] = "{$sectionVar}->value['index'] += {$propValue['step']}";
+ }
+ if (!isset($propValue['max'])) {
+ $propValue['max'] = $propValue['loop'];
+ $propType['max'] = $propType['loop'];
+ } elseif ($propType['max'] !== 0) {
+ $propValue['max'] = "{$propValue['max']} < 0 ? {$propValue['loop']} : {$propValue['max']}";
+ $propType['max'] = 1;
+ } else {
+ if ($propValue['max'] < 0) {
+ $propValue['max'] = $propValue['loop'];
+ $propType['max'] = $propType['loop'];
+ }
+ }
+ if (!isset($propValue['start'])) {
+ $start_code =
+ [1 => "{$propValue['step']} > 0 ? ", 2 => '0', 3 => ' : ', 4 => $propValue['loop'], 5 => ' - 1'];
+ if ($propType['loop'] === 0) {
+ $start_code[5] = '';
+ $start_code[4] = $propValue['loop'] - 1;
+ }
+ if ($propType['step'] === 0) {
+ if ($propValue['step'] > 0) {
+ $start_code = [1 => '0'];
+ $propType['start'] = 0;
+ } else {
+ $start_code[1] = $start_code[2] = $start_code[3] = '';
+ $propType['start'] = $propType['loop'];
+ }
+ } else {
+ $propType['start'] = 1;
+ }
+ $propValue['start'] = join('', $start_code);
+ } else {
+ $start_code =
+ [
+ 1 => "{$propValue['start']} < 0 ? ", 2 => 'max(', 3 => "{$propValue['step']} > 0 ? ", 4 => '0',
+ 5 => ' : ', 6 => '-1', 7 => ', ', 8 => "{$propValue['start']} + {$propValue['loop']}", 10 => ')',
+ 11 => ' : ', 12 => 'min(', 13 => $propValue['start'], 14 => ', ',
+ 15 => "{$propValue['step']} > 0 ? ", 16 => $propValue['loop'], 17 => ' : ',
+ 18 => $propType['loop'] === 0 ? $propValue['loop'] - 1 : "{$propValue['loop']} - 1",
+ 19 => ')',
+ ];
+ if ($propType['step'] === 0) {
+ $start_code[3] = $start_code[5] = $start_code[15] = $start_code[17] = '';
+ if ($propValue['step'] > 0) {
+ $start_code[6] = $start_code[18] = '';
+ } else {
+ $start_code[4] = $start_code[16] = '';
+ }
+ }
+ if ($propType['start'] === 0) {
+ if ($propType['loop'] === 0) {
+ $start_code[8] = $propValue['start'] + $propValue['loop'];
+ }
+ $propType['start'] = $propType['step'] + $propType['loop'];
+ $start_code[1] = '';
+ if ($propValue['start'] < 0) {
+ for ($i = 11; $i <= 19; $i++) {
+ $start_code[$i] = '';
+ }
+ if ($propType['start'] === 0) {
+ $start_code = [
+ max(
+ $propValue['step'] > 0 ? 0 : -1,
+ $propValue['start'] + $propValue['loop']
+ ),
+ ];
+ }
+ } else {
+ for ($i = 1; $i <= 11; $i++) {
+ $start_code[$i] = '';
+ }
+ if ($propType['start'] === 0) {
+ $start_code =
+ [
+ min(
+ $propValue['step'] > 0 ? $propValue['loop'] : $propValue['loop'] - 1,
+ $propValue['start']
+ ),
+ ];
+ }
+ }
+ }
+ $propValue['start'] = join('', $start_code);
+ }
+ if ($propType['start'] !== 0) {
+ $initLocal['start'] = $propValue['start'];
+ $propValue['start'] = "{$local}start";
+ }
+ $initFor['index'] = "{$sectionVar}->value['index'] = {$propValue['start']}";
+ if (!isset($_attr['start']) && !isset($_attr['step']) && !isset($_attr['max'])) {
+ $propValue['total'] = $propValue['loop'];
+ $propType['total'] = $propType['loop'];
+ } else {
+ $propType['total'] =
+ $propType['start'] + $propType['loop'] + $propType['step'] + $propType['max'];
+ if ($propType['total'] === 0) {
+ $propValue['total'] =
+ min(
+ ceil(
+ ($propValue['step'] > 0 ? $propValue['loop'] - $propValue['start'] :
+ (int)$propValue['start'] + 1) / abs($propValue['step'])
+ ),
+ $propValue['max']
+ );
+ } else {
+ $total_code = [
+ 1 => 'min(', 2 => 'ceil(', 3 => '(', 4 => "{$propValue['step']} > 0 ? ",
+ 5 => $propValue['loop'], 6 => ' - ', 7 => $propValue['start'], 8 => ' : ',
+ 9 => $propValue['start'], 10 => '+ 1', 11 => ')', 12 => '/ ', 13 => 'abs(',
+ 14 => $propValue['step'], 15 => ')', 16 => ')', 17 => ", {$propValue['max']})",
+ ];
+ if (!isset($propValue['max'])) {
+ $total_code[1] = $total_code[17] = '';
+ }
+ if ($propType['loop'] + $propType['start'] === 0) {
+ $total_code[5] = $propValue['loop'] - $propValue['start'];
+ $total_code[6] = $total_code[7] = '';
+ }
+ if ($propType['start'] === 0) {
+ $total_code[9] = (int)$propValue['start'] + 1;
+ $total_code[10] = '';
+ }
+ if ($propType['step'] === 0) {
+ $total_code[13] = $total_code[15] = '';
+ if ($propValue['step'] === 1 || $propValue['step'] === -1) {
+ $total_code[2] = $total_code[12] = $total_code[14] = $total_code[16] = '';
+ } elseif ($propValue['step'] < 0) {
+ $total_code[14] = -$propValue['step'];
+ }
+ $total_code[4] = '';
+ if ($propValue['step'] > 0) {
+ $total_code[8] = $total_code[9] = $total_code[10] = '';
+ } else {
+ $total_code[5] = $total_code[6] = $total_code[7] = $total_code[8] = '';
+ }
+ }
+ $propValue['total'] = join('', $total_code);
+ }
+ }
+ if (isset($namedAttr['loop'])) {
+ $initNamedProperty['loop'] = "'loop' => {$propValue['loop']}";
+ }
+ if (isset($namedAttr['total'])) {
+ $initNamedProperty['total'] = "'total' => {$propValue['total']}";
+ if ($propType['total'] > 0) {
+ $propValue['total'] = "{$sectionVar}->value['total']";
+ }
+ } elseif ($propType['total'] > 0) {
+ $initLocal['total'] = $propValue['total'];
+ $propValue['total'] = "{$local}total";
+ }
+ $cmpFor['iteration'] = "{$propValue['iteration']} <= {$propValue['total']}";
+ foreach ($initLocal as $key => $code) {
+ $output .= "{$local}{$key} = {$code};\n";
+ }
+ $_vars = 'array(' . join(', ', $initNamedProperty) . ')';
+ $output .= "{$sectionVar} = new \\Smarty\\Variable({$_vars});\n";
+ $cond_code = "{$propValue['total']} !== 0";
+ if ($propType['total'] === 0) {
+ if ($propValue['total'] === 0) {
+ $cond_code = 'false';
+ } else {
+ $cond_code = 'true';
+ }
+ }
+ if ($propType['show'] > 0) {
+ $output .= "{$local}show = {$propValue['show']} ? {$cond_code} : false;\n";
+ $output .= "if ({$local}show) {\n";
+ } elseif ($propValue['show'] === 'true') {
+ $output .= "if ({$cond_code}) {\n";
+ } else {
+ $output .= "if (false) {\n";
+ }
+ $jinit = join(', ', $initFor);
+ $jcmp = join(', ', $cmpFor);
+ $jinc = join(', ', $incFor);
+ $output .= "for ({$jinit}; {$jcmp}; {$jinc}){\n";
+ if (isset($namedAttr['rownum'])) {
+ $output .= "{$sectionVar}->value['rownum'] = {$propValue['iteration']};\n";
+ }
+ if (isset($namedAttr['index_prev'])) {
+ $output .= "{$sectionVar}->value['index_prev'] = {$propValue['index']} - {$propValue['step']};\n";
+ }
+ if (isset($namedAttr['index_next'])) {
+ $output .= "{$sectionVar}->value['index_next'] = {$propValue['index']} + {$propValue['step']};\n";
+ }
+ if (isset($namedAttr['first'])) {
+ $output .= "{$sectionVar}->value['first'] = ({$propValue['iteration']} === 1);\n";
+ }
+ if (isset($namedAttr['last'])) {
+ $output .= "{$sectionVar}->value['last'] = ({$propValue['iteration']} === {$propValue['total']});\n";
+ }
+ $output .= '?>';
+ return $output;
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/SectionClose.php b/src/Compile/Tag/SectionClose.php
new file mode 100644
index 00000000..dee65bab
--- /dev/null
+++ b/src/Compile/Tag/SectionClose.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Section
+ * Compiles the {section} {sectionelse} {/section} tags
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Sectionclose Class
+ */
+class SectionClose extends Base {
+
+ /**
+ * Compiles code for the {/section} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting--;
+
+ [$openTag, $nocache_pushed] = $this->closeTag($compiler, ['section', 'sectionelse']);
+
+ if ($nocache_pushed) {
+ // pop the pushed virtual nocache tag
+ $this->closeTag('nocache');
+ }
+
+ $output = "<?php\n";
+ if ($openTag === 'sectionelse') {
+ $output .= "}\n";
+ } else {
+ $output .= "}\n}\n";
+ }
+ $output .= '?>';
+ return $output;
+ }
+}
diff --git a/src/Compile/Tag/SectionElse.php b/src/Compile/Tag/SectionElse.php
new file mode 100644
index 00000000..be861e98
--- /dev/null
+++ b/src/Compile/Tag/SectionElse.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Sectionelse Class
+ *
+
+
+ */
+class SectionElse extends Base {
+
+ /**
+ * Compiles code for the {sectionelse} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ [$openTag, $nocache_pushed] = $this->closeTag($compiler, ['section']);
+ $this->openTag($compiler, 'sectionelse', ['sectionelse', $nocache_pushed]);
+ return "<?php }} else {\n ?>";
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/Setfilter.php b/src/Compile/Tag/Setfilter.php
new file mode 100644
index 00000000..037a701e
--- /dev/null
+++ b/src/Compile/Tag/Setfilter.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Setfilter Class
+ *
+
+
+ */
+class Setfilter extends Base {
+
+ /**
+ * Compiles code for setfilter tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->variable_filter_stack[] = $compiler->getSmarty()->getDefaultModifiers();
+
+ // The modifier_list is passed as an array of array's. The inner arrays have the modifier at index 0,
+ // and, possibly, parameters at subsequent indexes, e.g. [ ['escape','"mail"'] ]
+ // We will collapse them so the syntax is OK for ::setDefaultModifiers() as follows: [ 'escape:"mail"' ]
+ $newList = [];
+ foreach($parameter['modifier_list'] as $modifier) {
+ $newList[] = implode(':', $modifier);
+ }
+
+ $compiler->getSmarty()->setDefaultModifiers($newList);
+
+ // this tag does not return compiled code
+ $compiler->has_code = false;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/Compile/Tag/SetfilterClose.php b/src/Compile/Tag/SetfilterClose.php
new file mode 100644
index 00000000..273bb6fb
--- /dev/null
+++ b/src/Compile/Tag/SetfilterClose.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile Setfilter
+ * Compiles code for setfilter tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Setfilterclose Class
+ *
+
+
+ */
+class SetfilterClose extends Base {
+
+ /**
+ * Compiles code for the {/setfilter} tag
+ * This tag does not generate compiled output. It resets variable filter.
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $this->getAttributes($compiler, $args);
+
+ // reset variable filter to previous state
+ $compiler->getSmarty()->setDefaultModifiers(
+ count($compiler->variable_filter_stack) ? array_pop($compiler->variable_filter_stack) : []
+ );
+
+ // this tag does not return compiled code
+ $compiler->has_code = false;
+ return true;
+ }
+}
diff --git a/src/Compile/Tag/WhileClose.php b/src/Compile/Tag/WhileClose.php
new file mode 100644
index 00000000..6c45cd72
--- /dev/null
+++ b/src/Compile/Tag/WhileClose.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Smarty Internal Plugin Compile While
+ * Compiles the {while} tag
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile Whileclose Class
+ *
+
+
+ */
+class WhileClose extends Base {
+
+ /**
+ * Compiles code for the {/while} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ *
+ * @return string compiled code
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting--;
+
+ $nocache_pushed = $this->closeTag($compiler, ['while']);
+
+ if ($nocache_pushed) {
+ // pop the pushed virtual nocache tag
+ $this->closeTag($compiler, 'nocache');
+ $compiler->tag_nocache = true;
+ }
+
+ return "<?php }?>\n";
+ }
+}
diff --git a/src/Compile/Tag/WhileTag.php b/src/Compile/Tag/WhileTag.php
new file mode 100644
index 00000000..3df7d197
--- /dev/null
+++ b/src/Compile/Tag/WhileTag.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Smarty\Compile\Tag;
+
+use Smarty\Compile\Base;
+
+/**
+ * Smarty Internal Plugin Compile While Class
+ *
+
+
+ */
+class WhileTag extends Base {
+
+ /**
+ * Compiles code for the {while} tag
+ *
+ * @param array $args array with attributes from parser
+ * @param \Smarty\Compiler\Template $compiler compiler object
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws \Smarty\CompilerException
+ */
+ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = [], $tag = null, $function = null) {
+ $compiler->loopNesting++;
+
+ if ($compiler->tag_nocache) {
+ // push a {nocache} tag onto the stack to prevent caching of this block
+ $this->openTag($compiler, 'nocache');
+ }
+
+ $this->openTag($compiler, 'while', $compiler->tag_nocache);
+
+ if (!array_key_exists('if condition', $parameter)) {
+ $compiler->trigger_template_error('missing while condition', null, true);
+ }
+
+ if (is_array($parameter['if condition'])) {
+ if ($compiler->isNocacheActive()) {
+ // create nocache var to make it know for further compiling
+ if (is_array($parameter['if condition']['var'])) {
+ $var = $parameter['if condition']['var']['var'];
+ } else {
+ $var = $parameter['if condition']['var'];
+ }
+ $compiler->setNocacheInVariable($var);
+ }
+ $prefixVar = $compiler->getNewPrefixVariable();
+ $assignCompiler = new Assign();
+ $assignAttr = [];
+ $assignAttr[]['value'] = $prefixVar;
+ if (is_array($parameter['if condition']['var'])) {
+ $assignAttr[]['var'] = $parameter['if condition']['var']['var'];
+ $_output = "<?php while ({$prefixVar} = {$parameter[ 'if condition' ][ 'value' ]}) {?>";
+ $_output .= $assignCompiler->compile(
+ $assignAttr,
+ $compiler,
+ ['smarty_internal_index' => $parameter['if condition']['var']['smarty_internal_index']]
+ );
+ } else {
+ $assignAttr[]['var'] = $parameter['if condition']['var'];
+ $_output = "<?php while ({$prefixVar} = {$parameter[ 'if condition' ][ 'value' ]}) {?>";
+ $_output .= $assignCompiler->compile($assignAttr, $compiler, []);
+ }
+ return $_output;
+ } else {
+ return "<?php\n while ({$parameter['if condition']}) {?>";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Compiler/BaseCompiler.php b/src/Compiler/BaseCompiler.php
new file mode 100644
index 00000000..78c35a69
--- /dev/null
+++ b/src/Compiler/BaseCompiler.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Smarty\Compiler;
+
+use Smarty\Smarty;
+
+abstract class BaseCompiler {
+
+ /**
+ * Smarty object
+ *
+ * @var Smarty
+ */
+ protected $smarty = null;
+
+ /**
+ * @return Smarty|null
+ */
+ public function getSmarty(): Smarty {
+ return $this->smarty;
+ }
+
+} \ No newline at end of file
diff --git a/src/Compiler/CodeFrame.php b/src/Compiler/CodeFrame.php
new file mode 100644
index 00000000..69d17ed9
--- /dev/null
+++ b/src/Compiler/CodeFrame.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Smarty\Compiler;
+
+use Smarty\Exception;
+
+/**
+ * Smarty Internal Extension
+ * This file contains the Smarty template extension to create a code frame
+ *
+ * @author Uwe Tews
+ */
+
+/**
+ * Create code frame for compiled and cached templates
+ */
+class CodeFrame
+{
+
+ /**
+ * @var \Smarty\Template
+ */
+ private $_template;
+
+ public function __construct(\Smarty\Template $_template) {
+ $this->_template = $_template;
+ }
+
+ /**
+ * Create code frame for compiled and cached templates
+ *
+ * @param string $content optional template content
+ * @param string $functions compiled template function and block code
+ * @param bool $cache flag for cache file
+ * @param Template|null $compiler
+ *
+ * @return string
+ * @throws Exception
+*/
+ public function create(
+ $content = '',
+ $functions = '',
+ $cache = false,
+ \Smarty\Compiler\Template $compiler = null
+ ) {
+ // build property code
+ $properties[ 'version' ] = \Smarty\Smarty::SMARTY_VERSION;
+ $properties[ 'unifunc' ] = 'content_' . str_replace(array('.', ','), '_', uniqid('', true));
+ if (!$cache) {
+ $properties[ 'has_nocache_code' ] = $this->_template->getCompiled()->getNocacheCode();
+ $properties[ 'file_dependency' ] = $this->_template->getCompiled()->file_dependency;
+ $properties[ 'includes' ] = $this->_template->getCompiled()->includes;
+ } else {
+ $properties[ 'has_nocache_code' ] = $this->_template->getCached()->getNocacheCode();
+ $properties[ 'file_dependency' ] = $this->_template->getCached()->file_dependency;
+ $properties[ 'cache_lifetime' ] = $this->_template->cache_lifetime;
+ }
+ $output = sprintf(
+ "<?php\n/* Smarty version %s, created on %s\n from '%s' */\n\n",
+ $properties[ 'version' ],
+ date("Y-m-d H:i:s"),
+ str_replace('*/', '* /', $this->_template->getSource()->getFullResourceName())
+ );
+ $output .= "/* @var \\Smarty\\Template \$_smarty_tpl */\n";
+ $dec = "\$_smarty_tpl->" . ($cache ? "getCached()" : "getCompiled()");
+ $dec .= "->isFresh(\$_smarty_tpl, " . var_export($properties, true) . ')';
+ $output .= "if ({$dec}) {\n";
+ $output .= "function {$properties['unifunc']} (\\Smarty\\Template \$_smarty_tpl) {\n";
+
+ $output .= $this->insertLocalVariables();
+
+ if (!$cache && !empty($compiler->tpl_function)) {
+ $output .= '$_smarty_tpl->getSmarty()->getRuntime(\'TplFunction\')->registerTplFunctions($_smarty_tpl, ';
+ $output .= var_export($compiler->tpl_function, true);
+ $output .= ");\n";
+ }
+ if ($cache && $this->_template->getSmarty()->hasRuntime('TplFunction')) {
+ if ($tplfunctions = $this->_template->getSmarty()->getRuntime('TplFunction')->getTplFunction($this->_template)) {
+ $output .= "\$_smarty_tpl->getSmarty()->getRuntime('TplFunction')->registerTplFunctions(\$_smarty_tpl, " .
+ var_export($tplfunctions, true) . ");\n";
+ }
+ }
+ $output .= "?>";
+ $output .= $content;
+ $output .= "<?php }\n?>";
+ $output .= $functions;
+ $output .= "<?php }\n";
+ // remove unneeded PHP tags
+ if (preg_match('/\s*\?>[\n]?<\?php\s*/', $output)) {
+ $curr_split = preg_split(
+ '/\s*\?>[\n]?<\?php\s*/',
+ $output
+ );
+ preg_match_all(
+ '/\s*\?>[\n]?<\?php\s*/',
+ $output,
+ $curr_parts
+ );
+ $output = '';
+ foreach ($curr_split as $idx => $curr_output) {
+ $output .= $curr_output;
+ if (isset($curr_parts[ 0 ][ $idx ])) {
+ $output .= "\n";
+ }
+ }
+ }
+ if (preg_match('/\?>\s*$/', $output)) {
+ $curr_split = preg_split(
+ '/\?>\s*$/',
+ $output
+ );
+ $output = '';
+ foreach ($curr_split as $idx => $curr_output) {
+ $output .= $curr_output;
+ }
+ }
+ return $output;
+ }
+
+ /**
+ * @return string
+ */
+ public function insertLocalVariables(): string {
+ return '$_smarty_current_dir = ' . var_export(dirname($this->_template->getSource()->getFilepath()), true) . ";\n";
+ }
+}
diff --git a/src/Compiler/Configfile.php b/src/Compiler/Configfile.php
new file mode 100644
index 00000000..7297a62b
--- /dev/null
+++ b/src/Compiler/Configfile.php
@@ -0,0 +1,175 @@
+<?php
+/**
+ * Smarty Internal Plugin Config File Compiler
+ * This is the config file compiler class. It calls the lexer and parser to
+ * perform the compiling.
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compiler;
+use Smarty\Lexer\ConfigfileLexer;
+use Smarty\Parser\ConfigfileParser;
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\CompilerException;
+
+/**
+ * Main config file compiler class
+ *
+
+
+ */
+class Configfile extends BaseCompiler {
+
+ /**
+ * Lexer object
+ *
+ * @var ConfigfileLexer
+ */
+ public $lex;
+
+ /**
+ * Parser object
+ *
+ * @var ConfigfileParser
+ */
+ public $parser;
+
+ /**
+ * Smarty object
+ *
+ * @var Smarty object
+ */
+ public $smarty;
+
+ /**
+ * Smarty object
+ *
+ * @var Template object
+ */
+ public $template;
+
+ /**
+ * Compiled config data sections and variables
+ *
+ * @var array
+ */
+ public $config_data = [];
+
+ /**
+ * Initialize compiler
+ *
+ * @param Smarty $smarty global instance
+ */
+ public function __construct(Smarty $smarty) {
+ $this->smarty = $smarty;
+ // get required plugins
+ $this->smarty = $smarty;
+ $this->config_data['sections'] = [];
+ $this->config_data['vars'] = [];
+ }
+
+ /**
+ * Method to compile Smarty config source.
+ *
+ * @param Template $template
+ *
+ * @return bool true if compiling succeeded, false if it failed
+ * @throws \Smarty\Exception
+ */
+ public function compileTemplate(Template $template) {
+ $this->template = $template;
+ $this->template->getCompiled()->file_dependency[$this->template->getSource()->uid] =
+ [
+ $this->template->getSource()->getResourceName(),
+ $this->template->getSource()->getTimeStamp(),
+ $this->template->getSource()->type,
+ ];
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->start_compile($this->template);
+ }
+ // init the lexer/parser to compile the config file
+ /* @var ConfigfileLexer $this->lex */
+ $this->lex = new ConfigfileLexer(
+ str_replace(
+ [
+ "\r\n",
+ "\r",
+ ],
+ "\n",
+ $template->getSource()->getContent()
+ ) . "\n",
+ $this
+ );
+ /* @var ConfigfileParser $this->parser */
+ $this->parser = new ConfigfileParser($this->lex, $this);
+ if ($this->smarty->_parserdebug) {
+ $this->parser->PrintTrace();
+ }
+ // get tokens from lexer and parse them
+ while ($this->lex->yylex()) {
+ if ($this->smarty->_parserdebug) {
+ echo "Parsing {$this->parser->yyTokenName[$this->lex->token]} Token {$this->lex->value} Line {$this->lex->line} \n";
+ }
+ $this->parser->doParse($this->lex->token, $this->lex->value);
+ }
+ // finish parsing process
+ $this->parser->doParse(0, 0);
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->end_compile($this->template);
+ }
+ // template header code
+ $template_header = sprintf(
+ "<?php /* Smarty version %s, created on %s\n compiled from '%s' */ ?>\n",
+ \Smarty\Smarty::SMARTY_VERSION,
+ date("Y-m-d H:i:s"),
+ str_replace('*/', '* /', $this->template->getSource()->getFullResourceName())
+ );
+ $code = '<?php $_smarty_tpl->parent->assignConfigVars(' .
+ var_export($this->config_data, true) . ', $_smarty_tpl->getValue("sections")); ?>';
+ return $template_header . $this->template->createCodeFrame($code);
+ }
+
+ /**
+ * display compiler error messages without dying
+ * If parameter $args is empty it is a parser detected syntax error.
+ * In this case the parser is called to obtain information about expected tokens.
+ * If parameter $args contains a string this is used as error message
+ *
+ * @param string $args individual error message or null
+ *
+ * @throws CompilerException
+ */
+ public function trigger_config_file_error($args = null) {
+ // get config source line which has error
+ $line = $this->lex->line;
+ if (isset($args)) {
+ // $line--;
+ }
+ $match = preg_split("/\n/", $this->lex->data);
+ $error_text =
+ "Syntax error in config file '{$this->template->getSource()->getFullResourceName()}' on line {$line} '{$match[$line - 1]}' ";
+ if (isset($args)) {
+ // individual error message
+ $error_text .= $args;
+ } else {
+ // expected token from parser
+ foreach ($this->parser->yy_get_expected_tokens($this->parser->yymajor) as $token) {
+ $exp_token = $this->parser->yyTokenName[$token];
+ if (isset($this->lex->smarty_token_names[$exp_token])) {
+ // token type from lexer
+ $expect[] = '"' . $this->lex->smarty_token_names[$exp_token] . '"';
+ } else {
+ // otherwise internal token name
+ $expect[] = $this->parser->yyTokenName[$token];
+ }
+ }
+ // output parser error message
+ $error_text .= ' - Unexpected "' . $this->lex->value . '", expected one of: ' . implode(' , ', $expect);
+ }
+ throw new CompilerException($error_text);
+ }
+}
diff --git a/src/Compiler/Template.php b/src/Compiler/Template.php
new file mode 100644
index 00000000..03ee5110
--- /dev/null
+++ b/src/Compiler/Template.php
@@ -0,0 +1,1480 @@
+<?php
+/**
+ * Smarty Internal Plugin Smarty Template Compiler Base
+ * This file contains the basic classes and methods for compiling Smarty templates with lexer/parser
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty\Compiler;
+
+use Smarty\Compile\BlockCompiler;
+use Smarty\Compile\DefaultHandlerBlockCompiler;
+use Smarty\Compile\DefaultHandlerFunctionCallCompiler;
+use Smarty\Compile\ModifierCompiler;
+use Smarty\Compile\ObjectMethodBlockCompiler;
+use Smarty\Compile\ObjectMethodCallCompiler;
+use Smarty\Compile\FunctionCallCompiler;
+use Smarty\Compile\PrintExpressionCompiler;
+use Smarty\Lexer\TemplateLexer;
+use Smarty\Parser\TemplateParser;
+use Smarty\Smarty;
+use Smarty\Compile\Tag\ExtendsTag;
+use Smarty\CompilerException;
+use Smarty\Exception;
+use function array_merge;
+use function is_array;
+use function strlen;
+use function substr;
+
+/**
+ * Class SmartyTemplateCompiler
+ *
+
+
+ */
+class Template extends BaseCompiler {
+
+ /**
+ * counter for prefix variable number
+ *
+ * @var int
+ */
+ public static $prefixVariableNumber = 0;
+
+ /**
+ * Parser object
+ *
+ * @var \Smarty\Parser\TemplateParser
+ */
+ private $parser = null;
+
+ /**
+ * hash for nocache sections
+ *
+ * @var mixed
+ */
+ public $nocache_hash = null;
+
+ /**
+ * suppress generation of nocache code
+ *
+ * @var bool
+ */
+ public $suppressNocacheProcessing = false;
+
+ /**
+ * caching enabled (copied from template object)
+ *
+ * @var int
+ */
+ public $caching = 0;
+
+ /**
+ * tag stack
+ *
+ * @var array
+ */
+ private $_tag_stack = [];
+
+ /**
+ * tag stack count
+ *
+ * @var array
+ */
+ private $_tag_stack_count = [];
+
+ /**
+ * current template
+ *
+ * @var \Smarty\Template
+ */
+ private $template = null;
+
+ /**
+ * merged included sub template data
+ *
+ * @var array
+ */
+ public $mergedSubTemplatesData = [];
+
+ /**
+ * merged sub template code
+ *
+ * @var array
+ */
+ public $mergedSubTemplatesCode = [];
+
+ /**
+ * source line offset for error messages
+ *
+ * @var int
+ */
+ public $trace_line_offset = 0;
+
+ /**
+ * trace uid
+ *
+ * @var string
+ */
+ public $trace_uid = '';
+
+ /**
+ * trace file path
+ *
+ * @var string
+ */
+ public $trace_filepath = '';
+
+ /**
+ * Template functions
+ *
+ * @var array
+ */
+ public $tpl_function = [];
+
+ /**
+ * compiled template or block function code
+ *
+ * @var string
+ */
+ public $blockOrFunctionCode = '';
+
+ /**
+ * flags for used modifier plugins
+ *
+ * @var array
+ */
+ public $modifier_plugins = [];
+
+ /**
+ * parent compiler object for merged subtemplates and template functions
+ *
+ * @var \Smarty\Compiler\Template
+ */
+ private $parent_compiler = null;
+
+ /**
+ * Flag true when compiling nocache section
+ *
+ * @var bool
+ */
+ public $nocache = false;
+
+ /**
+ * Flag true when tag is compiled as nocache
+ *
+ * @var bool
+ */
+ public $tag_nocache = false;
+
+ /**
+ * Compiled tag prefix code
+ *
+ * @var array
+ */
+ public $prefix_code = [];
+
+ /**
+ * Prefix code stack
+ *
+ * @var array
+ */
+ public $prefixCodeStack = [];
+
+ /**
+ * Tag has compiled code
+ *
+ * @var bool
+ */
+ public $has_code = false;
+
+ /**
+ * A variable string was compiled
+ *
+ * @var bool
+ */
+ public $has_variable_string = false;
+
+ /**
+ * Stack for {setfilter} {/setfilter}
+ *
+ * @var array
+ */
+ public $variable_filter_stack = [];
+
+ /**
+ * Nesting count of looping tags like {foreach}, {for}, {section}, {while}
+ *
+ * @var int
+ */
+ public $loopNesting = 0;
+
+ /**
+ * Strip preg pattern
+ *
+ * @var string
+ */
+ public $stripRegEx = '![\t ]*[\r\n]+[\t ]*!';
+
+ /**
+ * General storage area for tag compiler plugins
+ *
+ * @var array
+ */
+ public $_cache = array();
+
+ /**
+ * Lexer preg pattern for left delimiter
+ *
+ * @var string
+ */
+ private $ldelPreg = '[{]';
+
+ /**
+ * Lexer preg pattern for right delimiter
+ *
+ * @var string
+ */
+ private $rdelPreg = '[}]';
+
+ /**
+ * Length of right delimiter
+ *
+ * @var int
+ */
+ private $rdelLength = 0;
+
+ /**
+ * Length of left delimiter
+ *
+ * @var int
+ */
+ private $ldelLength = 0;
+
+ /**
+ * Lexer preg pattern for user literals
+ *
+ * @var string
+ */
+ private $literalPreg = '';
+
+ /**
+ * array of callbacks called when the normal compile process of template is finished
+ *
+ * @var array
+ */
+ public $postCompileCallbacks = [];
+
+ /**
+ * prefix code
+ *
+ * @var string
+ */
+ public $prefixCompiledCode = '';
+
+ /**
+ * postfix code
+ *
+ * @var string
+ */
+ public $postfixCompiledCode = '';
+ /**
+ * @var ObjectMethodBlockCompiler
+ */
+ private $objectMethodBlockCompiler;
+ /**
+ * @var DefaultHandlerBlockCompiler
+ */
+ private $defaultHandlerBlockCompiler;
+ /**
+ * @var BlockCompiler
+ */
+ private $blockCompiler;
+ /**
+ * @var DefaultHandlerFunctionCallCompiler
+ */
+ private $defaultHandlerFunctionCallCompiler;
+ /**
+ * @var FunctionCallCompiler
+ */
+ private $functionCallCompiler;
+ /**
+ * @var ObjectMethodCallCompiler
+ */
+ private $objectMethodCallCompiler;
+ /**
+ * @var ModifierCompiler
+ */
+ private $modifierCompiler;
+ /**
+ * @var PrintExpressionCompiler
+ */
+ private $printExpressionCompiler;
+
+ /**
+ * Depth of nested {nocache}{/nocache} blocks. If outside, this is 0. If inside, this is 1 or higher (if nested).
+ * @var int
+ */
+ private $noCacheStackDepth = 0;
+
+
+ /**
+ * Initialize compiler
+ *
+ * @param Smarty $smarty global instance
+ */
+ public function __construct(Smarty $smarty) {
+ $this->smarty = $smarty;
+ $this->nocache_hash = str_replace(
+ [
+ '.',
+ ',',
+ ],
+ '_',
+ uniqid(mt_rand(), true)
+ );
+
+ $this->modifierCompiler = new ModifierCompiler();
+ $this->functionCallCompiler = new FunctionCallCompiler();
+ $this->defaultHandlerFunctionCallCompiler = new DefaultHandlerFunctionCallCompiler();
+ $this->blockCompiler = new BlockCompiler();
+ $this->defaultHandlerBlockCompiler = new DefaultHandlerBlockCompiler();
+ $this->objectMethodBlockCompiler = new ObjectMethodBlockCompiler();
+ $this->objectMethodCallCompiler = new ObjectMethodCallCompiler();
+ $this->printExpressionCompiler = new PrintExpressionCompiler();
+ }
+
+ /**
+ * Method to compile a Smarty template
+ *
+ * @param \Smarty\Template $template template object to compile
+ *
+ * @return string code
+ * @throws Exception
+ */
+ public function compileTemplate(\Smarty\Template $template) {
+ return $template->createCodeFrame(
+ $this->compileTemplateSource($template),
+ $this->smarty->runPostFilters($this->blockOrFunctionCode, $this->template) .
+ join('', $this->mergedSubTemplatesCode),
+ false,
+ $this
+ );
+ }
+
+ /**
+ * Compile template source and run optional post filter
+ *
+ * @param \Smarty\Template $template
+ * @param Template|null $parent_compiler
+ *
+ * @return string
+ * @throws CompilerException
+ * @throws Exception
+ */
+ public function compileTemplateSource(\Smarty\Template $template, \Smarty\Compiler\Template $parent_compiler = null) {
+ try {
+ // save template object in compiler class
+ $this->template = $template;
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->start_compile($this->template);
+ }
+ $this->parent_compiler = $parent_compiler ? $parent_compiler : $this;
+
+ if (empty($template->getCompiled()->nocache_hash)) {
+ $template->getCompiled()->nocache_hash = $this->nocache_hash;
+ } else {
+ $this->nocache_hash = $template->getCompiled()->nocache_hash;
+ }
+ $this->caching = $template->caching;
+
+ // flag for nocache sections
+ $this->nocache = false;
+ $this->tag_nocache = false;
+ // reset has nocache code flag
+ $this->template->getCompiled()->setNocacheCode(false);
+
+ $this->has_variable_string = false;
+ $this->prefix_code = [];
+ // add file dependency
+ if ($this->template->getSource()->handler->checkTimestamps()) {
+ $this->parent_compiler->getTemplate()->getCompiled()->file_dependency[$this->template->getSource()->uid] =
+ [
+ $this->template->getSource()->getResourceName(),
+ $this->template->getSource()->getTimeStamp(),
+ $this->template->getSource()->type,
+ ];
+ }
+ // get template source
+ if (!empty($this->template->getSource()->components)) {
+ // we have array of inheritance templates by extends: resource
+ // generate corresponding source code sequence
+ $_content =
+ ExtendsTag::extendsSourceArrayCode($this->template);
+ } else {
+ // get template source
+ $_content = $this->template->getSource()->getContent();
+ }
+ $_compiled_code = $this->smarty->runPostFilters(
+ $this->doCompile(
+ $this->smarty->runPreFilters($_content, $this->template),
+ true
+ ),
+ $this->template
+ );
+ } catch (\Exception $e) {
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->end_compile($this->template);
+ }
+ $this->_tag_stack = [];
+ // free memory
+ $this->parent_compiler = null;
+ $this->template = null;
+ $this->parser = null;
+ throw $e;
+ }
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->end_compile($this->template);
+ }
+ $this->parent_compiler = null;
+ $this->parser = null;
+ return $_compiled_code;
+ }
+
+ /**
+ * Compile Tag
+ * This is a call back from the lexer/parser
+ *
+ * Save current prefix code
+ * Compile tag
+ * Merge tag prefix code with saved one
+ * (required nested tags in attributes)
+ *
+ * @param string $tag tag name
+ * @param array $args array with tag attributes
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws Exception
+ * @throws CompilerException
+ */
+ public function compileTag($tag, $args, $parameter = []) {
+ $this->prefixCodeStack[] = $this->prefix_code;
+ $this->prefix_code = [];
+ $result = $this->compileTag2(strtolower($tag), $args, $parameter);
+ $this->prefix_code = array_merge($this->prefix_code, array_pop($this->prefixCodeStack));
+ return $result;
+ }
+
+ /**
+ * Compiles code for modifier execution
+ *
+ * @param $modifierlist
+ * @param $value
+ *
+ * @return string compiled code
+ * @throws CompilerException
+ * @throws Exception
+ */
+ public function compileModifier($modifierlist, $value) {
+ return $this->modifierCompiler->compile([], $this, ['modifierlist' => $modifierlist, 'value' => $value]);
+ }
+
+ /**
+ * compile variable
+ *
+ * @param string $variable
+ *
+ * @return string
+ */
+ public function compileVariable($variable) {
+ if (!strpos($variable, '(')) {
+ // not a variable variable
+ $var = trim($variable, '\'');
+ $this->tag_nocache = $this->tag_nocache |
+ $this->template->getVariable(
+ $var,
+ true,
+ false
+ )->isNocache();
+ }
+ return '$_smarty_tpl->getValue(' . $variable . ')';
+ }
+
+ /**
+ * compile config variable
+ *
+ * @param string $variable
+ *
+ * @return string
+ */
+ public function compileConfigVariable($variable) {
+ // return '$_smarty_tpl->config_vars[' . $variable . ']';
+ return '$_smarty_tpl->getConfigVariable(' . $variable . ')';
+ }
+
+ /**
+ * This method is called from parser to process a text content section if strip is enabled
+ * - remove text from inheritance child templates as they may generate output
+ *
+ * @param string $text
+ *
+ * @return string
+ */
+ public function processText($text) {
+
+ if (strpos($text, '<') === false) {
+ return preg_replace($this->stripRegEx, '', $text);
+ }
+
+ $store = [];
+ $_store = 0;
+
+ // capture html elements not to be messed with
+ $_offset = 0;
+ if (preg_match_all(
+ '#(<script[^>]*>.*?</script[^>]*>)|(<textarea[^>]*>.*?</textarea[^>]*>)|(<pre[^>]*>.*?</pre[^>]*>)#is',
+ $text,
+ $matches,
+ PREG_OFFSET_CAPTURE | PREG_SET_ORDER
+ )
+ ) {
+ foreach ($matches as $match) {
+ $store[] = $match[0][0];
+ $_length = strlen($match[0][0]);
+ $replace = '@!@SMARTY:' . $_store . ':SMARTY@!@';
+ $text = substr_replace($text, $replace, $match[0][1] - $_offset, $_length);
+ $_offset += $_length - strlen($replace);
+ $_store++;
+ }
+ }
+ $expressions = [// replace multiple spaces between tags by a single space
+ '#(:SMARTY@!@|>)[\040\011]+(?=@!@SMARTY:|<)#s' => '\1 \2',
+ // remove newline between tags
+ '#(:SMARTY@!@|>)[\040\011]*[\n]\s*(?=@!@SMARTY:|<)#s' => '\1\2',
+ // remove multiple spaces between attributes (but not in attribute values!)
+ '#(([a-z0-9]\s*=\s*("[^"]*?")|(\'[^\']*?\'))|<[a-z0-9_]+)\s+([a-z/>])#is' => '\1 \5',
+ '#>[\040\011]+$#Ss' => '> ',
+ '#>[\040\011]*[\n]\s*$#Ss' => '>',
+ $this->stripRegEx => '',
+ ];
+ $text = preg_replace(array_keys($expressions), array_values($expressions), $text);
+ $_offset = 0;
+ if (preg_match_all(
+ '#@!@SMARTY:([0-9]+):SMARTY@!@#is',
+ $text,
+ $matches,
+ PREG_OFFSET_CAPTURE | PREG_SET_ORDER
+ )
+ ) {
+ foreach ($matches as $match) {
+ $_length = strlen($match[0][0]);
+ $replace = $store[$match[1][0]];
+ $text = substr_replace($text, $replace, $match[0][1] + $_offset, $_length);
+ $_offset += strlen($replace) - $_length;
+ $_store++;
+ }
+ }
+ return $text;
+ }
+
+ /**
+ * lazy loads internal compile plugin for tag compile objects cached for reuse.
+ *
+ * class name format: \Smarty\Compile\TagName
+ *
+ * @param string $tag tag name
+ *
+ * @return ?\Smarty\Compile\CompilerInterface tag compiler object or null if not found or untrusted by security policy
+ */
+ public function getTagCompiler($tag): ?\Smarty\Compile\CompilerInterface {
+
+ if (isset($this->smarty->security_policy) && !$this->smarty->security_policy->isTrustedTag($tag, $this)) {
+ return null;
+ }
+
+ foreach ($this->smarty->getExtensions() as $extension) {
+ if ($compiler = $extension->getTagCompiler($tag)) {
+ return $compiler;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * lazy loads internal compile plugin for modifier compile objects cached for reuse.
+ *
+ * @param string $modifier tag name
+ *
+ * @return bool|\Smarty\Compile\Modifier\ModifierCompilerInterface tag compiler object or false if not found or untrusted by security policy
+ */
+ public function getModifierCompiler($modifier) {
+
+ if (isset($this->smarty->security_policy) && !$this->smarty->security_policy->isTrustedModifier($modifier, $this)) {
+ return false;
+ }
+
+ foreach ($this->smarty->getExtensions() as $extension) {
+ if ($modifierCompiler = $extension->getModifierCompiler($modifier)) {
+ return $modifierCompiler;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for plugins by default plugin handler
+ *
+ * @param string $tag name of tag
+ * @param string $plugin_type type of plugin
+ *
+ * @return callback|null
+ * @throws \Smarty\CompilerException
+ */
+ public function getPluginFromDefaultHandler($tag, $plugin_type) {
+
+ $defaultPluginHandlerFunc = $this->smarty->getDefaultPluginHandlerFunc();
+
+ if (!is_callable($defaultPluginHandlerFunc)) {
+ return null;
+ }
+
+
+ $callback = null;
+ $script = null;
+ $cacheable = true;
+
+ $result = call_user_func_array(
+ $defaultPluginHandlerFunc,
+ [
+ $tag,
+ $plugin_type,
+ null, // This used to pass $this->template, but this parameter has been removed in 5.0
+ &$callback,
+ &$script,
+ &$cacheable,
+ ]
+ );
+ if ($result) {
+ $this->tag_nocache = $this->tag_nocache || !$cacheable;
+ if ($script !== null) {
+ if (is_file($script)) {
+ include_once $script;
+ } else {
+ $this->trigger_template_error("Default plugin handler: Returned script file '{$script}' for '{$tag}' not found");
+ }
+ }
+ if (is_callable($callback)) {
+ return $callback;
+ } else {
+ $this->trigger_template_error("Default plugin handler: Returned callback for '{$tag}' not callable");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Append code segments and remove unneeded ?> <?php transitions
+ *
+ * @param string $left
+ * @param string $right
+ *
+ * @return string
+ */
+ public function appendCode($left, $right) {
+ if (preg_match('/\s*\?>\s?$/D', $left) && preg_match('/^<\?php\s+/', $right)) {
+ $left = preg_replace('/\s*\?>\s?$/D', "\n", $left);
+ $left .= preg_replace('/^<\?php\s+/', '', $right);
+ } else {
+ $left .= $right;
+ }
+ return $left;
+ }
+
+ /**
+ * Inject inline code for nocache template sections
+ * This method gets the content of each template element from the parser.
+ * If the content is compiled code, and it should be not be cached the code is injected
+ * into the rendered output.
+ *
+ * @param string $content content of template element
+ *
+ * @return string content
+ */
+ public function processNocacheCode($content) {
+
+ // If the template is not evaluated, and we have a nocache section and/or a nocache tag
+ // generate replacement code
+ if (!empty($content)
+ && !($this->template->getSource()->handler->recompiled)
+ && $this->caching
+ && $this->isNocacheActive()
+ ) {
+ $this->template->getCompiled()->setNocacheCode(true);
+ $_output = addcslashes($content, '\'\\');
+ $_output =
+ "<?php echo '" . $this->getNocacheBlockStartMarker() . $_output . $this->getNocacheBlockEndMarker() . "';?>\n";
+ } else {
+ $_output = $content;
+ }
+
+ $this->modifier_plugins = [];
+ $this->suppressNocacheProcessing = false;
+ $this->tag_nocache = false;
+ return $_output;
+ }
+
+
+ private function getNocacheBlockStartMarker(): string {
+ return "/*%%SmartyNocache:{$this->nocache_hash}%%*/";
+ }
+
+ private function getNocacheBlockEndMarker(): string {
+ return "/*/%%SmartyNocache:{$this->nocache_hash}%%*/";
+ }
+
+
+ /**
+ * Get Id
+ *
+ * @param string $input
+ *
+ * @return bool|string
+ */
+ public function getId($input) {
+ if (preg_match('~^([\'"]*)([0-9]*[a-zA-Z_]\w*)\1$~', $input, $match)) {
+ return $match[2];
+ }
+ return false;
+ }
+
+ /**
+ * Set nocache flag in variable or create new variable
+ *
+ * @param string $varName
+ */
+ public function setNocacheInVariable($varName) {
+ // create nocache var to make it know for further compiling
+ if ($_var = $this->getId($varName)) {
+ if ($this->template->hasVariable($_var)) {
+ $this->template->getVariable($_var)->setNocache(true);
+ } else {
+ $this->template->assign($_var, null, true);
+ }
+ }
+ }
+
+ /**
+ * display compiler error messages without dying
+ * If parameter $args is empty it is a parser detected syntax error.
+ * In this case the parser is called to obtain information about expected tokens.
+ * If parameter $args contains a string this is used as error message
+ *
+ * @param string $args individual error message or null
+ * @param string $line line-number
+ * @param null|bool $tagline if true the line number of last tag
+ *
+ * @throws \Smarty\CompilerException when an unexpected token is found
+ */
+ public function trigger_template_error($args = null, $line = null, $tagline = null) {
+ $lex = $this->parser->lex;
+ if ($tagline === true) {
+ // get line number of Tag
+ $line = $lex->taglineno;
+ } elseif (!isset($line)) {
+ // get template source line which has error
+ $line = $lex->line;
+ } else {
+ $line = (int)$line;
+ }
+ if (in_array(
+ $this->template->getSource()->type,
+ [
+ 'eval',
+ 'string',
+ ]
+ )
+ ) {
+ $templateName = $this->template->getSource()->type . ':' . trim(
+ preg_replace(
+ '![\t\r\n]+!',
+ ' ',
+ strlen($lex->data) > 40 ?
+ substr($lex->data, 0, 40) .
+ '...' : $lex->data
+ )
+ );
+ } else {
+ $templateName = $this->template->getSource()->getFullResourceName();
+ }
+ // $line += $this->trace_line_offset;
+ $match = preg_split("/\n/", $lex->data);
+ $error_text =
+ 'Syntax error in template "' . (empty($this->trace_filepath) ? $templateName : $this->trace_filepath) .
+ '" on line ' . ($line + $this->trace_line_offset) . ' "' .
+ trim(preg_replace('![\t\r\n]+!', ' ', $match[$line - 1])) . '" ';
+ if (isset($args)) {
+ // individual error message
+ $error_text .= $args;
+ } else {
+ $expect = [];
+ // expected token from parser
+ $error_text .= ' - Unexpected "' . $lex->value . '"';
+ if (count($this->parser->yy_get_expected_tokens($this->parser->yymajor)) <= 4) {
+ foreach ($this->parser->yy_get_expected_tokens($this->parser->yymajor) as $token) {
+ $exp_token = $this->parser->yyTokenName[$token];
+ if (isset($lex->smarty_token_names[$exp_token])) {
+ // token type from lexer
+ $expect[] = '"' . $lex->smarty_token_names[$exp_token] . '"';
+ } else {
+ // otherwise internal token name
+ $expect[] = $this->parser->yyTokenName[$token];
+ }
+ }
+ $error_text .= ', expected one of: ' . implode(' , ', $expect);
+ }
+ }
+ if ($this->smarty->_parserdebug) {
+ $this->parser->errorRunDown();
+ echo ob_get_clean();
+ flush();
+ }
+ $e = new CompilerException(
+ $error_text,
+ 0,
+ $this->template->getSource()->getFullResourceName(),
+ $line
+ );
+ $e->source = trim(preg_replace('![\t\r\n]+!', ' ', $match[$line - 1]));
+ $e->desc = $args;
+ $e->template = $this->template->getSource()->getFullResourceName();
+ throw $e;
+ }
+
+ /**
+ * Return var_export() value with all white spaces removed
+ *
+ * @param mixed $value
+ *
+ * @return string
+ */
+ public function getVarExport($value) {
+ return preg_replace('/\s/', '', var_export($value, true));
+ }
+
+ /**
+ * enter double quoted string
+ * - save tag stack count
+ */
+ public function enterDoubleQuote() {
+ array_push($this->_tag_stack_count, $this->getTagStackCount());
+ }
+
+ /**
+ * Return tag stack count
+ *
+ * @return int
+ */
+ public function getTagStackCount() {
+ return count($this->_tag_stack);
+ }
+
+ /**
+ * @param $lexerPreg
+ *
+ * @return mixed
+ */
+ public function replaceDelimiter($lexerPreg) {
+ return str_replace(
+ ['SMARTYldel', 'SMARTYliteral', 'SMARTYrdel', 'SMARTYautoliteral', 'SMARTYal'],
+ [
+ $this->ldelPreg, $this->literalPreg, $this->rdelPreg,
+ $this->smarty->getAutoLiteral() ? '{1,}' : '{9}',
+ $this->smarty->getAutoLiteral() ? '' : '\\s*',
+ ],
+ $lexerPreg
+ );
+ }
+
+ /**
+ * Build lexer regular expressions for left and right delimiter and user defined literals
+ */
+ public function initDelimiterPreg() {
+ $ldel = $this->smarty->getLeftDelimiter();
+ $this->ldelLength = strlen($ldel);
+ $this->ldelPreg = '';
+ foreach (str_split($ldel, 1) as $chr) {
+ $this->ldelPreg .= '[' . preg_quote($chr, '/') . ']';
+ }
+ $rdel = $this->smarty->getRightDelimiter();
+ $this->rdelLength = strlen($rdel);
+ $this->rdelPreg = '';
+ foreach (str_split($rdel, 1) as $chr) {
+ $this->rdelPreg .= '[' . preg_quote($chr, '/') . ']';
+ }
+ $literals = $this->smarty->getLiterals();
+ if (!empty($literals)) {
+ foreach ($literals as $key => $literal) {
+ $literalPreg = '';
+ foreach (str_split($literal, 1) as $chr) {
+ $literalPreg .= '[' . preg_quote($chr, '/') . ']';
+ }
+ $literals[$key] = $literalPreg;
+ }
+ $this->literalPreg = '|' . implode('|', $literals);
+ } else {
+ $this->literalPreg = '';
+ }
+ }
+
+ /**
+ * leave double quoted string
+ * - throw exception if block in string was not closed
+ *
+ * @throws \Smarty\CompilerException
+ */
+ public function leaveDoubleQuote() {
+ if (array_pop($this->_tag_stack_count) !== $this->getTagStackCount()) {
+ $tag = $this->getOpenBlockTag();
+ $this->trigger_template_error(
+ "unclosed '{{$tag}}' in doubled quoted string",
+ null,
+ true
+ );
+ }
+ }
+
+ /**
+ * Get left delimiter preg
+ *
+ * @return string
+ */
+ public function getLdelPreg() {
+ return $this->ldelPreg;
+ }
+
+ /**
+ * Get right delimiter preg
+ *
+ * @return string
+ */
+ public function getRdelPreg() {
+ return $this->rdelPreg;
+ }
+
+ /**
+ * Get length of left delimiter
+ *
+ * @return int
+ */
+ public function getLdelLength() {
+ return $this->ldelLength;
+ }
+
+ /**
+ * Get length of right delimiter
+ *
+ * @return int
+ */
+ public function getRdelLength() {
+ return $this->rdelLength;
+ }
+
+ /**
+ * Get name of current open block tag
+ *
+ * @return string|boolean
+ */
+ public function getOpenBlockTag() {
+ $tagCount = $this->getTagStackCount();
+ if ($tagCount) {
+ return $this->_tag_stack[$tagCount - 1][0];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Check if $value contains variable elements
+ *
+ * @param mixed $value
+ *
+ * @return bool|int
+ */
+ public function isVariable($value) {
+ if (is_string($value)) {
+ return preg_match('/[$(]/', $value);
+ }
+ if (is_bool($value) || is_numeric($value)) {
+ return false;
+ }
+ if (is_array($value)) {
+ foreach ($value as $k => $v) {
+ if ($this->isVariable($k) || $this->isVariable($v)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * Get new prefix variable name
+ *
+ * @return string
+ */
+ public function getNewPrefixVariable() {
+ ++self::$prefixVariableNumber;
+ return $this->getPrefixVariable();
+ }
+
+ /**
+ * Get current prefix variable name
+ *
+ * @return string
+ */
+ public function getPrefixVariable() {
+ return '$_prefixVariable' . self::$prefixVariableNumber;
+ }
+
+ /**
+ * append code to prefix buffer
+ *
+ * @param string $code
+ */
+ public function appendPrefixCode($code) {
+ $this->prefix_code[] = $code;
+ }
+
+ /**
+ * get prefix code string
+ *
+ * @return string
+ */
+ public function getPrefixCode() {
+ $code = '';
+ $prefixArray = array_merge($this->prefix_code, array_pop($this->prefixCodeStack));
+ $this->prefixCodeStack[] = [];
+ foreach ($prefixArray as $c) {
+ $code = $this->appendCode($code, $c);
+ }
+ $this->prefix_code = [];
+ return $code;
+ }
+
+ public function cStyleComment($string) {
+ return '/*' . str_replace('*/', '* /', $string) . '*/';
+ }
+
+ public function compileChildBlock() {
+ $this->has_code = true;
+ return $this->blockCompiler->compileChild($this);
+ }
+
+ public function compileParentBlock() {
+ $this->has_code = true;
+ return $this->blockCompiler->compileParent($this);
+ }
+
+ /**
+ * Compile Tag
+ *
+ * @param string $tag tag name
+ * @param array $args array with tag attributes
+ * @param array $parameter array with compilation parameter
+ *
+ * @return string compiled code
+ * @throws Exception
+ * @throws CompilerException
+ */
+ private function compileTag2($tag, $args, $parameter) {
+ // $args contains the attributes parsed and compiled by the lexer/parser
+ // assume that tag does compile into code, but creates no HTML output
+ $this->has_code = true;
+
+ $this->handleNocacheFlag($args);
+
+ // compile built-in tags
+ if ($tagCompiler = $this->getTagCompiler($tag)) {
+ if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($tag, $this)) {
+ $this->tag_nocache = $this->tag_nocache | !$tagCompiler->isCacheable();
+ $_output = $tagCompiler->compile($args, $this, $parameter);
+ if ($_output !== false) {
+ if (!empty($parameter['modifierlist'])) {
+ throw new CompilerException('No modifiers allowed on ' . $tag);
+ }
+ return $this->has_code && $_output !== true ? $_output : null;
+ }
+ }
+ }
+
+ // call to function previousely defined by {function} tag
+ if ($this->canCompileTemplateFunctionCall($tag)) {
+
+ if (!empty($parameter['modifierlist'])) {
+ throw new CompilerException('No modifiers allowed on ' . $tag);
+ }
+
+ $args['_attr']['name'] = "'{$tag}'";
+ $tagCompiler = $this->getTagCompiler('call');
+ $_output = $tagCompiler === null ? false : $tagCompiler->compile($args, $this, $parameter);
+ return $this->has_code ? $_output : null;
+ }
+
+ // remaining tastes: (object-)function, (object-function-)block, custom-compiler
+ // opening and closing tags for these are handled with the same handler
+ $base_tag = $this->getBaseTag($tag);
+
+ // check if tag is a registered object
+ if (isset($this->smarty->registered_objects[$base_tag]) && isset($parameter['object_method'])) {
+ return $this->compileRegisteredObjectMethodCall($base_tag, $args, $parameter, $tag);
+ }
+
+ // check if tag is a function
+ if ($this->smarty->getFunctionHandler($base_tag)) {
+ if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($base_tag, $this)) {
+ return (new \Smarty\Compile\PrintExpressionCompiler())->compile(
+ [],
+ $this,
+ ['value' => $this->compileFunctionCall($base_tag, $args, $parameter)]
+ );
+ }
+ }
+
+ // check if tag is a block
+ if ($this->smarty->getBlockHandler($base_tag)) {
+ if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($base_tag, $this)) {
+ return $this->blockCompiler->compile($args, $this, $parameter, $tag, $base_tag);
+ }
+ }
+
+ // the default plugin handler is a handler of last resort, it may also handle not specifically registered tags.
+ if ($callback = $this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_COMPILER)) {
+ if (!empty($parameter['modifierlist'])) {
+ throw new CompilerException('No modifiers allowed on ' . $base_tag);
+ }
+ $tagCompiler = new \Smarty\Compile\Tag\BCPluginWrapper($callback);
+ return $tagCompiler->compile($args, $this, $parameter);
+ }
+
+ if ($this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_FUNCTION)) {
+ return $this->defaultHandlerFunctionCallCompiler->compile($args, $this, $parameter, $tag, $base_tag);
+ }
+
+ if ($this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_BLOCK)) {
+ return $this->defaultHandlerBlockCompiler->compile($args, $this, $parameter, $tag, $base_tag);
+ }
+
+ $this->trigger_template_error("unknown tag '{$tag}'", null, true);
+ }
+
+ /**
+ * Sets $this->tag_nocache if attributes contain the 'nocache' flag.
+ *
+ * @param array $attributes
+ *
+ * @return void
+ */
+ private function handleNocacheFlag(array $attributes) {
+ foreach ($attributes as $value) {
+ if (is_string($value) && trim($value, '\'" ') == 'nocache') {
+ $this->tag_nocache = true;
+ }
+ }
+ }
+
+ private function getBaseTag($tag) {
+ if (strlen($tag) < 6 || substr($tag, -5) !== 'close') {
+ return $tag;
+ } else {
+ return substr($tag, 0, -5);
+ }
+ }
+
+ /**
+ * Compiles the output of a variable or expression.
+ *
+ * @param $value
+ * @param $attributes
+ * @param $modifiers
+ *
+ * @return string
+ * @throws Exception
+ */
+ public function compilePrintExpression($value, $attributes = [], $modifiers = null) {
+ $this->handleNocacheFlag($attributes);
+ return $this->printExpressionCompiler->compile($attributes, $this, [
+ 'value'=> $value,
+ 'modifierlist' => $modifiers,
+ ]);
+ }
+
+ /**
+ * method to compile a Smarty template
+ *
+ * @param mixed $_content template source
+ * @param bool $isTemplateSource
+ *
+ * @return bool true if compiling succeeded, false if it failed
+ * @throws \Smarty\CompilerException
+ */
+ protected function doCompile($_content, $isTemplateSource = false) {
+ /* here is where the compiling takes place. Smarty
+ tags in the templates are replaces with PHP code,
+ then written to compiled files. */
+ // init the lexer/parser to compile the template
+ $this->parser = new TemplateParser(
+ new TemplateLexer(
+ str_replace(
+ [
+ "\r\n",
+ "\r",
+ ],
+ "\n",
+ $_content
+ ),
+ $this
+ ),
+ $this
+ );
+ if ($isTemplateSource && $this->template->caching) {
+ $this->parser->insertPhpCode("<?php\n\$_smarty_tpl->getCompiled()->nocache_hash = '{$this->nocache_hash}';\n?>\n");
+ }
+ if ($this->smarty->_parserdebug) {
+ $this->parser->PrintTrace();
+ $this->parser->lex->PrintTrace();
+ }
+ // get tokens from lexer and parse them
+ while ($this->parser->lex->yylex()) {
+ if ($this->smarty->_parserdebug) {
+ echo "Line {$this->parser->lex->line} Parsing {$this->parser->yyTokenName[$this->parser->lex->token]} Token " .
+ $this->parser->lex->value;
+ }
+ $this->parser->doParse($this->parser->lex->token, $this->parser->lex->value);
+ }
+ // finish parsing process
+ $this->parser->doParse(0, 0);
+ // check for unclosed tags
+ if ($this->getTagStackCount() > 0) {
+ // get stacked info
+ [$openTag, $_data] = array_pop($this->_tag_stack);
+ $this->trigger_template_error(
+ "unclosed " . $this->smarty->getLeftDelimiter() . $openTag .
+ $this->smarty->getRightDelimiter() . " tag"
+ );
+ }
+ // call post compile callbacks
+ foreach ($this->postCompileCallbacks as $cb) {
+ $parameter = $cb;
+ $parameter[0] = $this;
+ call_user_func_array($cb[0], $parameter);
+ }
+ // return compiled code
+ return $this->prefixCompiledCode . $this->parser->retvalue . $this->postfixCompiledCode;
+ }
+
+ /**
+ * Register a post compile callback
+ * - when the callback is called after template compiling the compiler object will be inserted as first parameter
+ *
+ * @param callback $callback
+ * @param array $parameter optional parameter array
+ * @param string $key optional key for callback
+ * @param bool $replace if true replace existing keyed callback
+ */
+ public function registerPostCompileCallback($callback, $parameter = [], $key = null, $replace = false) {
+ array_unshift($parameter, $callback);
+ if (isset($key)) {
+ if ($replace || !isset($this->postCompileCallbacks[$key])) {
+ $this->postCompileCallbacks[$key] = $parameter;
+ }
+ } else {
+ $this->postCompileCallbacks[] = $parameter;
+ }
+ }
+
+ /**
+ * Remove a post compile callback
+ *
+ * @param string $key callback key
+ */
+ public function unregisterPostCompileCallback($key) {
+ unset($this->postCompileCallbacks[$key]);
+ }
+
+ /**
+ * @param string $tag
+ *
+ * @return bool
+ * @throws Exception
+ */
+ private function canCompileTemplateFunctionCall(string $tag): bool {
+ return
+ isset($this->parent_compiler->tpl_function[$tag])
+ || (
+ $this->template->getSmarty()->hasRuntime('TplFunction')
+ && ($this->template->getSmarty()->getRuntime('TplFunction')->getTplFunction($this->template, $tag) !== false)
+ );
+ }
+
+ /**
+ * @throws CompilerException
+ */
+ private function compileRegisteredObjectMethodCall(string $base_tag, array $args, array $parameter, string $tag) {
+
+ $method = $parameter['object_method'];
+ $allowedAsBlockFunction = in_array($method, $this->smarty->registered_objects[$base_tag][3]);
+
+ if ($base_tag === $tag) {
+ // opening tag
+
+ $allowedAsNormalFunction = empty($this->smarty->registered_objects[$base_tag][1])
+ || in_array($method, $this->smarty->registered_objects[$base_tag][1]);
+
+ if ($allowedAsBlockFunction) {
+ return $this->objectMethodBlockCompiler->compile($args, $this, $parameter, $tag, $method);
+ } elseif ($allowedAsNormalFunction) {
+ return $this->objectMethodCallCompiler->compile($args, $this, $parameter, $tag, $method);
+ }
+
+ $this->trigger_template_error(
+ 'not allowed method "' . $method . '" in registered object "' .
+ $tag . '"',
+ null,
+ true
+ );
+ }
+
+ // closing tag
+ if ($allowedAsBlockFunction) {
+ return $this->objectMethodBlockCompiler->compile($args, $this, $parameter, $tag, $method);
+ }
+
+ $this->trigger_template_error(
+ 'not allowed closing tag method "' . $method .
+ '" in registered object "' . $base_tag . '"',
+ null,
+ true
+ );
+ }
+
+ public function compileFunctionCall(string $base_tag, array $args, array $parameter = []) {
+ return $this->functionCallCompiler->compile($args, $this, $parameter, $base_tag, $base_tag);
+ }
+
+ /**
+ * @return TemplateParser|null
+ */
+ public function getParser(): ?TemplateParser {
+ return $this->parser;
+ }
+
+ /**
+ * @param TemplateParser|null $parser
+ */
+ public function setParser(?TemplateParser $parser): void {
+ $this->parser = $parser;
+ }
+
+ /**
+ * @return \Smarty\Template|null
+ */
+ public function getTemplate(): ?\Smarty\Template {
+ return $this->template;
+ }
+
+ /**
+ * @param \Smarty\Template|null $template
+ */
+ public function setTemplate(?\Smarty\Template $template): void {
+ $this->template = $template;
+ }
+
+ /**
+ * @return Template|null
+ */
+ public function getParentCompiler(): ?Template {
+ return $this->parent_compiler;
+ }
+
+ /**
+ * @param Template|null $parent_compiler
+ */
+ public function setParentCompiler(?Template $parent_compiler): void {
+ $this->parent_compiler = $parent_compiler;
+ }
+
+
+ /**
+ * Push opening tag name on stack
+ * Optionally additional data can be saved on stack
+ *
+ * @param string $openTag the opening tag's name
+ * @param mixed $data optional data saved
+ */
+ public function openTag($openTag, $data = null) {
+ $this->_tag_stack[] = [$openTag, $data];
+ if ($openTag == 'nocache') {
+ $this->noCacheStackDepth++;
+ }
+ }
+
+ /**
+ * Pop closing tag
+ * Raise an error if this stack-top doesn't match with expected opening tags
+ *
+ * @param array|string $expectedTag the expected opening tag names
+ *
+ * @return mixed any type the opening tag's name or saved data
+ * @throws CompilerException
+ */
+ public function closeTag($expectedTag) {
+ if ($this->getTagStackCount() > 0) {
+ // get stacked info
+ [$_openTag, $_data] = array_pop($this->_tag_stack);
+ // open tag must match with the expected ones
+ if (in_array($_openTag, (array)$expectedTag)) {
+
+ if ($_openTag == 'nocache') {
+ $this->noCacheStackDepth--;
+ }
+
+ if (is_null($_data)) {
+ // return opening tag
+ return $_openTag;
+ } else {
+ // return restored data
+ return $_data;
+ }
+ }
+ // wrong nesting of tags
+ $this->trigger_template_error("unclosed '" . $this->getTemplate()->getLeftDelimiter() . "{$_openTag}" .
+ $this->getTemplate()->getRightDelimiter() . "' tag");
+ return;
+ }
+ // wrong nesting of tags
+ $this->trigger_template_error('unexpected closing tag', null, true);
+ }
+
+ /**
+ * Returns true if we are in a {nocache}...{/nocache} block, but false if inside {block} tag inside a {nocache} block...
+ * @return bool
+ */
+ public function isNocacheActive(): bool {
+ return !$this->suppressNocacheProcessing && ($this->noCacheStackDepth > 0 || $this->tag_nocache);
+ }
+
+ /**
+ * Returns the full tag stack, used in the compiler for {break}
+ * @return array
+ */
+ public function getTagStack(): array {
+ return $this->_tag_stack;
+ }
+}
diff --git a/src/CompilerException.php b/src/CompilerException.php
new file mode 100644
index 00000000..e3d67b46
--- /dev/null
+++ b/src/CompilerException.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * Smarty compiler exception class
+ *
+
+ */
+class CompilerException extends Exception {
+
+ /**
+ * The constructor of the exception
+ *
+ * @param string $message The Exception message to throw.
+ * @param int $code The Exception code.
+ * @param string|null $filename The filename where the exception is thrown.
+ * @param int|null $line The line number where the exception is thrown.
+ * @param Throwable|null $previous The previous exception used for the exception chaining.
+ */
+ public function __construct(
+ string $message = "",
+ int $code = 0,
+ ?string $filename = null,
+ ?int $line = null,
+ Throwable $previous = null
+ ) {
+ parent::__construct($message, $code, $previous);
+
+ // These are optional parameters, should be be overridden only when present!
+ if ($filename) {
+ $this->file = $filename;
+ }
+ if ($line) {
+ $this->line = $line;
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString() {
+ return ' --> Smarty Compiler: ' . $this->message . ' <-- ';
+ }
+
+ /**
+ * @param int $line
+ */
+ public function setLine($line) {
+ $this->line = $line;
+ }
+
+ /**
+ * The template source snippet relating to the error
+ *
+ * @type string|null
+ */
+ public $source = null;
+
+ /**
+ * The raw text of the error message
+ *
+ * @type string|null
+ */
+ public $desc = null;
+
+ /**
+ * The resource identifier or template name
+ *
+ * @type string|null
+ */
+ public $template = null;
+}
diff --git a/src/Data.php b/src/Data.php
new file mode 100644
index 00000000..adacf216
--- /dev/null
+++ b/src/Data.php
@@ -0,0 +1,496 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * Smarty Internal Plugin Data
+ * This file contains the basic properties and methods for holding config and template variables
+ */
+class Data
+{
+
+ /**
+ * define variable scopes
+ */
+ const SCOPE_LOCAL = 1;
+ const SCOPE_PARENT = 2;
+ const SCOPE_TPL_ROOT = 4;
+ const SCOPE_ROOT = 8;
+ const SCOPE_SMARTY = 16;
+ const SCOPE_GLOBAL = 32;
+
+ /**
+ * Global smarty instance
+ *
+ * @var Smarty
+ */
+ protected $smarty = null;
+
+ /**
+ * template variables
+ *
+ * @var Variable[]
+ */
+ public $tpl_vars = array();
+
+ /**
+ * parent data container (if any)
+ *
+ * @var Data
+ */
+ public $parent = null;
+
+ /**
+ * configuration settings
+ *
+ * @var string[]
+ */
+ public $config_vars = array();
+
+ /**
+ * Default scope for new variables
+ * @var int
+ */
+ protected $defaultScope = self::SCOPE_LOCAL;
+
+ /**
+ * create Smarty data object
+ *
+ * @param Smarty|array $_parent parent template
+ * @param Smarty|Template $smarty global smarty instance
+ * @param string $name optional data block name
+ *
+ * @throws Exception
+ */
+ public function __construct($_parent = null, $smarty = null, $name = null) {
+
+ $this->smarty = $smarty;
+ if (is_object($_parent)) {
+ // when object set up back pointer
+ $this->parent = $_parent;
+ } elseif (is_array($_parent)) {
+ // set up variable values
+ foreach ($_parent as $_key => $_val) {
+ $this->assign($_key, $_val);
+ }
+ } elseif ($_parent !== null) {
+ throw new Exception('Wrong type for template variables');
+ }
+ }
+
+ /**
+ * assigns a Smarty variable
+ *
+ * @param array|string $tpl_var the template variable name(s)
+ * @param mixed $value the value to assign
+ * @param boolean $nocache if true any output of this variable will be not cached
+ * @param int $scope one of self::SCOPE_* constants
+ *
+ * @return Data current Data (or Smarty or \Smarty\Template) instance for
+ * chaining
+ */
+ public function assign($tpl_var, $value = null, $nocache = false, $scope = null)
+ {
+ if (is_array($tpl_var)) {
+ foreach ($tpl_var as $_key => $_val) {
+ $this->assign($_key, $_val, $nocache, $scope);
+ }
+ return;
+ }
+ switch ($scope ?? $this->getDefaultScope()) {
+ case self::SCOPE_GLOBAL:
+ case self::SCOPE_SMARTY:
+ $this->getSmarty()->assign($tpl_var, $value);
+ break;
+ case self::SCOPE_TPL_ROOT:
+ $ptr = $this;
+ while (isset($ptr->parent) && ($ptr->parent instanceof Template)) {
+ $ptr = $ptr->parent;
+ }
+ $ptr->assign($tpl_var, $value);
+ break;
+ case self::SCOPE_ROOT:
+ $ptr = $this;
+ while (isset($ptr->parent) && !($ptr->parent instanceof Smarty)) {
+ $ptr = $ptr->parent;
+ }
+ $ptr->assign($tpl_var, $value);
+ break;
+ case self::SCOPE_PARENT:
+ if ($this->parent) {
+ $this->parent->assign($tpl_var, $value);
+ } else {
+ // assign local as fallback
+ $this->assign($tpl_var, $value);
+ }
+ break;
+ case self::SCOPE_LOCAL:
+ default:
+ if (isset($this->tpl_vars[$tpl_var])) {
+ $this->tpl_vars[$tpl_var]->setValue($value);
+ if ($nocache) {
+ $this->tpl_vars[$tpl_var]->setNocache(true);
+ }
+ } else {
+ $this->tpl_vars[$tpl_var] = new Variable($value, $nocache);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * appends values to template variables
+ *
+ * @param array|string $tpl_var the template variable name(s)
+ * @param mixed $value the value to append
+ * @param bool $merge flag if array elements shall be merged
+ * @param bool $nocache if true any output of this variable will
+ * be not cached
+ *
+ * @return Data
+ * @link https://www.smarty.net/docs/en/api.append.tpl
+ *
+ * @api Smarty::append()
+ */
+ public function append($tpl_var, $value = null, $merge = false, $nocache = false)
+ {
+ if (is_array($tpl_var)) {
+ foreach ($tpl_var as $_key => $_val) {
+ $this->append($_key, $_val, $merge, $nocache);
+ }
+ } else {
+
+ $newValue = $this->getValue($tpl_var) ?? [];
+ if (!is_array($newValue)) {
+ $newValue = (array) $newValue;
+ }
+
+ if ($merge && is_array($value)) {
+ foreach ($value as $_mkey => $_mval) {
+ $newValue[$_mkey] = $_mval;
+ }
+ } else {
+ $newValue[] = $value;
+ }
+
+ $this->assign($tpl_var, $newValue, $nocache);
+ }
+ return $this;
+ }
+
+ /**
+ * assigns a global Smarty variable
+ *
+ * @param string $varName the global variable name
+ * @param mixed $value the value to assign
+ * @param boolean $nocache if true any output of this variable will be not cached
+ *
+ * @return Data
+ * @deprecated since 5.0
+ */
+ public function assignGlobal($varName, $value = null, $nocache = false)
+ {
+ trigger_error(__METHOD__ . " is deprecated. Use \\Smarty\\Smarty::assign() to assign a variable " .
+ " at the Smarty level.", E_USER_DEPRECATED);
+ return $this->getSmarty()->assign($varName, $value, $nocache);
+ }
+
+ /**
+ * Returns a single or all template variables
+ *
+ * @param string $varName variable name or null
+ * @param bool $searchParents include parent templates?
+ *
+ * @return mixed variable value or or array of variables
+ * @api Smarty::getTemplateVars()
+ * @link https://www.smarty.net/docs/en/api.get.template.vars.tpl
+ *
+ */
+ public function getTemplateVars($varName = null, $searchParents = true)
+ {
+ if (isset($varName)) {
+ return $this->getValue($varName, $searchParents);
+ }
+
+ return array_merge($this->parent && $searchParents ? $this->parent->getTemplateVars() : [], $this->tpl_vars);
+ }
+
+ /**
+ * Wrapper for ::getVariable()
+ *
+ * @deprecated since 5.0
+ *
+ * @param $varName
+ * @param $searchParents
+ * @param $errorEnable
+ *
+ * @return void
+ */
+ public function _getVariable($varName, $searchParents = true, $errorEnable = true) {
+ trigger_error('Using ::_getVariable() to is deprecated and will be ' .
+ 'removed in a future release. Use getVariable() instead.', E_USER_DEPRECATED);
+ return $this->getVariable($varName, $searchParents, $errorEnable);
+ }
+
+ /**
+ * Gets the object of a Smarty variable
+ *
+ * @param string $varName the name of the Smarty variable
+ * @param bool $searchParents search also in parent data
+ * @param bool $errorEnable
+ *
+ * @return Variable
+ */
+ public function getVariable($varName, $searchParents = true, $errorEnable = true) {
+ if (isset($this->tpl_vars[$varName])) {
+ return $this->tpl_vars[$varName];
+ }
+
+ if ($searchParents && $this->parent) {
+ return $this->parent->getVariable($varName, $searchParents, $errorEnable);
+ }
+
+ if ($errorEnable && $this->getSmarty()->error_unassigned) {
+ // force a notice
+ $x = $$varName;
+ }
+ return new UndefinedVariable();
+ }
+
+ /**
+ * Directly sets a complete Variable object in the variable with the given name.
+ * @param $varName
+ * @param Variable $variableObject
+ *
+ * @return void
+ */
+ public function setVariable($varName, Variable $variableObject) {
+ $this->tpl_vars[$varName] = $variableObject;
+ }
+
+ /**
+ * Indicates if given variable has been set.
+ * @param $varName
+ *
+ * @return bool
+ */
+ public function hasVariable($varName): bool {
+ return !($this->getVariable($varName) instanceof UndefinedVariable);
+ }
+
+ /**
+ * Returns the value of the Smarty\Variable given by $varName, or null if the variable does not exist.
+ *
+ * @param $varName
+ * @param bool $searchParents
+ *
+ * @return mixed|null
+ */
+ public function getValue($varName, $searchParents = true) {
+ $variable = $this->getVariable($varName, $searchParents);
+ return isset($variable) ? $variable->getValue() : null;
+ }
+
+ /**
+ * load config variables into template object
+ *
+ * @param array $new_config_vars
+ */
+ public function assignConfigVars($new_config_vars, array $sections = []) {
+
+ // copy global config vars
+ foreach ($new_config_vars['vars'] as $variable => $value) {
+ if ($this->getSmarty()->config_overwrite || !isset($this->config_vars[$variable])) {
+ $this->config_vars[$variable] = $value;
+ } else {
+ $this->config_vars[$variable] = array_merge((array)$this->config_vars[$variable], (array)$value);
+ }
+ }
+
+ foreach ($sections as $tpl_section) {
+ if (isset($new_config_vars['sections'][$tpl_section])) {
+ foreach ($new_config_vars['sections'][$tpl_section]['vars'] as $variable => $value) {
+ if ($this->getSmarty()->config_overwrite || !isset($this->config_vars[$variable])) {
+ $this->config_vars[$variable] = $value;
+ } else {
+ $this->config_vars[$variable] = array_merge((array)$this->config_vars[$variable], (array)$value);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get Smarty object
+ *
+ * @return Smarty
+ */
+ public function getSmarty()
+ {
+ return $this->smarty;
+ }
+
+ /**
+ * clear the given assigned template variable(s).
+ *
+ * @param string|array $tpl_var the template variable(s) to clear
+ *
+ * @return Data
+ * @link https://www.smarty.net/docs/en/api.clear.assign.tpl
+ *
+ * @api Smarty::clearAssign()
+ */
+ public function clearAssign($tpl_var)
+ {
+ if (is_array($tpl_var)) {
+ foreach ($tpl_var as $curr_var) {
+ unset($this->tpl_vars[ $curr_var ]);
+ }
+ } else {
+ unset($this->tpl_vars[ $tpl_var ]);
+ }
+ return $this;
+ }
+
+ /**
+ * clear all the assigned template variables.
+ *
+ * @return Data
+ * @link https://www.smarty.net/docs/en/api.clear.all.assign.tpl
+ *
+ * @api Smarty::clearAllAssign()
+ */
+ public function clearAllAssign()
+ {
+ $this->tpl_vars = array();
+ return $this;
+ }
+
+ /**
+ * clear a single or all config variables
+ *
+ * @param string|null $name variable name or null
+ *
+ * @return Data
+ * @link https://www.smarty.net/docs/en/api.clear.config.tpl
+ *
+ * @api Smarty::clearConfig()
+ */
+ public function clearConfig($name = null)
+ {
+ if (isset($name)) {
+ unset($this->config_vars[ $name ]);
+ } else {
+ $this->config_vars = array();
+ }
+ return $this;
+ }
+
+ /**
+ * Gets a config variable value
+ *
+ * @param string $varName the name of the config variable
+ *
+ * @return mixed the value of the config variable
+ * @throws Exception
+ */
+ public function getConfigVariable($varName)
+ {
+
+ if (isset($this->config_vars[$varName])) {
+ return $this->config_vars[$varName];
+ }
+
+ $returnValue = $this->parent ? $this->parent->getConfigVariable($varName) : null;
+
+ if ($returnValue === null && $this->getSmarty()->error_unassigned) {
+ throw new Exception("Undefined variable $varName");
+ }
+
+ return $returnValue;
+ }
+
+ public function hasConfigVariable($varName): bool {
+ try {
+ return $this->getConfigVariable($varName) !== null;
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns a single or all config variables
+ *
+ * @param string $varname variable name or null
+ *
+ * @return mixed variable value or or array of variables
+ * @throws Exception
+ * @link https://www.smarty.net/docs/en/api.get.config.vars.tpl
+ *
+ * @api Smarty::getConfigVars()
+ */
+ public function getConfigVars($varname = null)
+ {
+ if (isset($varname)) {
+ return $this->getConfigVariable($varname);
+ }
+
+ return array_merge($this->parent ? $this->parent->getConfigVars() : [], $this->config_vars);
+ }
+
+ /**
+ * load a config file, optionally load just selected sections
+ *
+ * @param string $config_file filename
+ * @param mixed $sections array of section names, single
+ * section or null
+
+ * @returns $this
+ * @throws \Exception
+ * @link https://www.smarty.net/docs/en/api.config.load.tpl
+ *
+ * @api Smarty::configLoad()
+ */
+ public function configLoad($config_file, $sections = null)
+ {
+ $template = $this->getSmarty()->doCreateTemplate($config_file, null, null, $this, null, null, true);
+ $template->caching = Smarty::CACHING_OFF;
+ $template->assign('sections', (array) $sections ?? []);
+ // trigger a call to $this->assignConfigVars
+ $template->fetch();
+ return $this;
+ }
+
+ /**
+ * Sets the default scope for new variables assigned in this template.
+ * @param int $scope
+ *
+ * @return void
+ */
+ protected function setDefaultScope(int $scope) {
+ $this->defaultScope = $scope;
+ }
+
+ /**
+ * Returns the default scope for new variables assigned in this template.
+ * @return int
+ */
+ public function getDefaultScope(): int {
+ return $this->defaultScope;
+ }
+
+ /**
+ * @return Data|Smarty|null
+ */
+ public function getParent() {
+ return $this->parent;
+ }
+
+ /**
+ * @param Data|Smarty|null $parent
+ */
+ public function setParent($parent): void {
+ $this->parent = $parent;
+ }
+}
diff --git a/src/Debug.php b/src/Debug.php
new file mode 100644
index 00000000..d313a7a5
--- /dev/null
+++ b/src/Debug.php
@@ -0,0 +1,380 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * Smarty Internal Plugin Debug
+ * Class to collect data for the Smarty Debugging Console
+ *
+
+
+ * @author Uwe Tews
+ */
+
+/**
+ * Smarty Internal Plugin Debug Class
+ *
+
+
+ */
+class Debug extends Data
+{
+ /**
+ * template data
+ *
+ * @var array
+ */
+ public $template_data = array();
+
+ /**
+ * List of uid's which shall be ignored
+ *
+ * @var array
+ */
+ public $ignore_uid = array();
+
+ /**
+ * Index of display() and fetch() calls
+ *
+ * @var int
+ */
+ public $index = 0;
+
+ /**
+ * Counter for window offset
+ *
+ * @var int
+ */
+ public $offset = 0;
+
+ /**
+ * Start logging template
+ *
+ * @param \Smarty\Template $template template
+ * @param null $mode true: display false: fetch null: subtemplate
+ */
+ public function start_template(\Smarty\Template $template, $mode = null)
+ {
+ if (isset($mode) && !$template->_isSubTpl()) {
+ $this->index++;
+ $this->offset++;
+ $this->template_data[ $this->index ] = null;
+ }
+ $key = $this->get_key($template);
+ $this->template_data[ $this->index ][ $key ][ 'start_template_time' ] = microtime(true);
+ }
+
+ /**
+ * End logging of cache time
+ *
+ * @param \Smarty\Template $template cached template
+ */
+ public function end_template(\Smarty\Template $template)
+ {
+ $key = $this->get_key($template);
+ $this->template_data[ $this->index ][ $key ][ 'total_time' ] +=
+ microtime(true) - $this->template_data[ $this->index ][ $key ][ 'start_template_time' ];
+ }
+
+ /**
+ * Start logging of compile time
+ *
+ * @param \Smarty\Template $template
+ */
+ public function start_compile(\Smarty\Template $template)
+ {
+ static $_is_stringy = array('string' => true, 'eval' => true);
+ if (!empty($template->getCompiler()->trace_uid)) {
+ $key = $template->getCompiler()->trace_uid;
+ if (!isset($this->template_data[ $this->index ][ $key ])) {
+ $this->saveTemplateData($_is_stringy, $template, $key);
+ }
+ } else {
+ if (isset($this->ignore_uid[ $template->getSource()->uid ])) {
+ return;
+ }
+ $key = $this->get_key($template);
+ }
+ $this->template_data[ $this->index ][ $key ][ 'start_time' ] = microtime(true);
+ }
+
+ /**
+ * End logging of compile time
+ *
+ * @param \Smarty\Template $template
+ */
+ public function end_compile(\Smarty\Template $template)
+ {
+ if (!empty($template->getCompiler()->trace_uid)) {
+ $key = $template->getCompiler()->trace_uid;
+ } else {
+ if (isset($this->ignore_uid[ $template->getSource()->uid ])) {
+ return;
+ }
+ $key = $this->get_key($template);
+ }
+ $this->template_data[ $this->index ][ $key ][ 'compile_time' ] +=
+ microtime(true) - $this->template_data[ $this->index ][ $key ][ 'start_time' ];
+ }
+
+ /**
+ * Start logging of render time
+ *
+ * @param \Smarty\Template $template
+ */
+ public function start_render(\Smarty\Template $template)
+ {
+ $key = $this->get_key($template);
+ $this->template_data[ $this->index ][ $key ][ 'start_time' ] = microtime(true);
+ }
+
+ /**
+ * End logging of compile time
+ *
+ * @param \Smarty\Template $template
+ */
+ public function end_render(\Smarty\Template $template)
+ {
+ $key = $this->get_key($template);
+ $this->template_data[ $this->index ][ $key ][ 'render_time' ] +=
+ microtime(true) - $this->template_data[ $this->index ][ $key ][ 'start_time' ];
+ }
+
+ /**
+ * Start logging of cache time
+ *
+ * @param \Smarty\Template $template cached template
+ */
+ public function start_cache(\Smarty\Template $template)
+ {
+ $key = $this->get_key($template);
+ $this->template_data[ $this->index ][ $key ][ 'start_time' ] = microtime(true);
+ }
+
+ /**
+ * End logging of cache time
+ *
+ * @param \Smarty\Template $template cached template
+ */
+ public function end_cache(\Smarty\Template $template)
+ {
+ $key = $this->get_key($template);
+ $this->template_data[ $this->index ][ $key ][ 'cache_time' ] +=
+ microtime(true) - $this->template_data[ $this->index ][ $key ][ 'start_time' ];
+ }
+
+ /**
+ * Register template object
+ *
+ * @param \Smarty\Template $template cached template
+ */
+ public function register_template(\Smarty\Template $template)
+ {
+ }
+
+ /**
+ * Register data object
+ *
+ * @param Data $data data object
+ */
+ public static function register_data(Data $data)
+ {
+ }
+
+ /**
+ * Opens a window for the Smarty Debugging Console and display the data
+ *
+ * @param \Smarty\Template|\Smarty $obj object to debug
+ * @param bool $full
+ *
+ * @throws \Exception
+ * @throws \Smarty\Exception
+ */
+ public function display_debug($obj, $full = false)
+ {
+ if (!$full) {
+ $this->offset++;
+ $savedIndex = $this->index;
+ $this->index = 9999;
+ }
+ $smarty = $obj->getSmarty();
+ // create fresh instance of smarty for displaying the debug console
+ // to avoid problems if the application did overload the Smarty class
+ $debObj = new \Smarty\Smarty();
+ // copy the working dirs from application
+ $debObj->setCompileDir($smarty->getCompileDir());
+ $debObj->compile_check = \Smarty::COMPILECHECK_ON;
+ $debObj->security_policy = null;
+ $debObj->debugging = false;
+ $debObj->debugging_ctrl = 'NONE';
+ $debObj->error_reporting = E_ALL & ~E_NOTICE;
+ $debObj->debug_tpl = $smarty->debug_tpl ?? 'file:' . __DIR__ . '/../debug.tpl';
+ $debObj->registered_resources = array();
+ $debObj->registered_filters = array();
+ $debObj->escape_html = true;
+ $debObj->caching = \Smarty::CACHING_OFF;
+ // prepare information of assigned variables
+ $ptr = $this->get_debug_vars($obj);
+ $_assigned_vars = $ptr->tpl_vars;
+ ksort($_assigned_vars);
+ $_config_vars = $ptr->config_vars;
+ ksort($_config_vars);
+ $debugging = $smarty->debugging;
+ $templateName = $obj->getSource()->type . ':' . $obj->getSource()->name;
+ $displayMode = $debugging === 2 || !$full;
+ $offset = $this->offset * 50;
+ $_template = $debObj->doCreateTemplate($debObj->debug_tpl);
+ if ($obj instanceof \Smarty\Template) {
+ $_template->assign('template_name', $templateName);
+ } elseif ($obj instanceof Smarty || $full) {
+ $_template->assign('template_data', $this->template_data[$this->index]);
+ } else {
+ $_template->assign('template_data', null);
+ }
+ $_template->assign('assigned_vars', $_assigned_vars);
+ $_template->assign('config_vars', $_config_vars);
+ $_template->assign('execution_time', microtime(true) - $smarty->start_time);
+ $_template->assign('targetWindow', $displayMode ? md5("$offset$templateName") : '__Smarty__');
+ $_template->assign('offset', $offset);
+ echo $_template->fetch();
+ if (isset($full)) {
+ $this->index--;
+ }
+ if (!$full) {
+ $this->index = $savedIndex;
+ }
+ }
+
+ /**
+ * Recursively gets variables from all template/data scopes
+ *
+ * @param \Smarty\Data $obj object to debug
+ *
+ * @return \StdClass
+ */
+ private function get_debug_vars($obj)
+ {
+ $config_vars = array();
+ foreach ($obj->config_vars as $key => $var) {
+ $config_vars[$key]['value'] = $var;
+ $config_vars[$key]['scope'] = get_class($obj) . ':' . spl_object_id($obj);
+ }
+ $tpl_vars = array();
+ foreach ($obj->tpl_vars as $key => $var) {
+ foreach ($var as $varkey => $varvalue) {
+ if ($varkey === 'value') {
+ $tpl_vars[ $key ][ $varkey ] = $varvalue;
+ } else {
+ if ($varkey === 'nocache') {
+ if ($varvalue === true) {
+ $tpl_vars[ $key ][ $varkey ] = $varvalue;
+ }
+ } else {
+ if ($varkey !== 'scope' || $varvalue !== 0) {
+ $tpl_vars[ $key ][ 'attributes' ][ $varkey ] = $varvalue;
+ }
+ }
+ }
+ }
+ $tpl_vars[$key]['scope'] = get_class($obj) . ':' . spl_object_id($obj);
+ }
+ if (isset($obj->parent)) {
+ $parent = $this->get_debug_vars($obj->parent);
+ foreach ($parent->tpl_vars as $name => $pvar) {
+ if (isset($tpl_vars[ $name ]) && $tpl_vars[ $name ][ 'value' ] === $pvar[ 'value' ]) {
+ $tpl_vars[ $name ][ 'scope' ] = $pvar[ 'scope' ];
+ }
+ }
+ $tpl_vars = array_merge($parent->tpl_vars, $tpl_vars);
+ foreach ($parent->config_vars as $name => $pvar) {
+ if (isset($config_vars[ $name ]) && $config_vars[ $name ][ 'value' ] === $pvar[ 'value' ]) {
+ $config_vars[ $name ][ 'scope' ] = $pvar[ 'scope' ];
+ }
+ }
+ $config_vars = array_merge($parent->config_vars, $config_vars);
+ }
+ return (object)array('tpl_vars' => $tpl_vars, 'config_vars' => $config_vars);
+ }
+
+ /**
+ * Return key into $template_data for template
+ *
+ * @param \Smarty\Template $template template object
+ *
+ * @return string key into $template_data
+ */
+ private function get_key(\Smarty\Template $template)
+ {
+ static $_is_stringy = array('string' => true, 'eval' => true);
+
+ $key = $template->getSource()->uid;
+ if (isset($this->template_data[ $this->index ][ $key ])) {
+ return $key;
+ } else {
+ $this->saveTemplateData($_is_stringy, $template, $key);
+ $this->template_data[ $this->index ][ $key ][ 'total_time' ] = 0;
+ return $key;
+ }
+ }
+
+ /**
+ * Ignore template
+ *
+ * @param \Smarty\Template $template
+ */
+ public function ignore(\Smarty\Template $template)
+ {
+ $this->ignore_uid[$template->getSource()->uid] = true;
+ }
+
+ /**
+ * handle 'URL' debugging mode
+ *
+ * @param \Smarty $smarty
+ */
+ public function debugUrl(\Smarty $smarty)
+ {
+ if (isset($_SERVER[ 'QUERY_STRING' ])) {
+ $_query_string = $_SERVER[ 'QUERY_STRING' ];
+ } else {
+ $_query_string = '';
+ }
+ if (false !== strpos($_query_string, $smarty->smarty_debug_id)) {
+ if (false !== strpos($_query_string, $smarty->smarty_debug_id . '=on')) {
+ // enable debugging for this browser session
+ setcookie('SMARTY_DEBUG', true);
+ $smarty->debugging = true;
+ } elseif (false !== strpos($_query_string, $smarty->smarty_debug_id . '=off')) {
+ // disable debugging for this browser session
+ setcookie('SMARTY_DEBUG', false);
+ $smarty->debugging = false;
+ } else {
+ // enable debugging for this page
+ $smarty->debugging = true;
+ }
+ } else {
+ if (isset($_COOKIE[ 'SMARTY_DEBUG' ])) {
+ $smarty->debugging = true;
+ }
+ }
+ }
+
+ /**
+ * @param array $_is_stringy
+ * @param \Smarty\Template $template
+ * @param string $key
+ *
+ * @return void
+ */
+ private function saveTemplateData(array $_is_stringy, \Smarty\Template $template, string $key): void {
+ if (isset($_is_stringy[$template->getSource()->type])) {
+ $this->template_data[$this->index][$key]['name'] =
+ '\'' . substr($template->getSource()->name, 0, 25) . '...\'';
+ } else {
+ $this->template_data[$this->index][$key]['name'] = $template->getSource()->getResourceName();
+ }
+ $this->template_data[$this->index][$key]['compile_time'] = 0;
+ $this->template_data[$this->index][$key]['render_time'] = 0;
+ $this->template_data[$this->index][$key]['cache_time'] = 0;
+ }
+}
diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php
new file mode 100644
index 00000000..16ebbeaa
--- /dev/null
+++ b/src/ErrorHandler.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * Smarty error handler to fix new error levels in PHP8 for backwards compatibility
+ * @author Simon Wisselink
+ */
+class ErrorHandler
+{
+ /**
+ * Allows {$foo->propName} where propName is undefined.
+ * @var bool
+ */
+ public $allowUndefinedProperties = true;
+
+ /**
+ * Allows {$foo.bar} where bar is unset and {$foo.bar1.bar2} where either bar1 or bar2 is unset.
+ * @var bool
+ */
+ public $allowUndefinedArrayKeys = true;
+
+ /**
+ * Allows {$foo->bar} where bar is not an object (e.g. null or false).
+ * @var bool
+ */
+ public $allowDereferencingNonObjects = true;
+
+ private $previousErrorHandler = null;
+
+ /**
+ * Enable error handler to intercept errors
+ */
+ public function activate() {
+ /*
+ Error muting is done because some people implemented custom error_handlers using
+ https://php.net/set_error_handler and for some reason did not understand the following paragraph:
+
+ It is important to remember that the standard PHP error handler is completely bypassed for the
+ error types specified by error_types unless the callback function returns FALSE.
+ error_reporting() settings will have no effect and your error handler will be called regardless -
+ however you are still able to read the current value of error_reporting and act appropriately.
+ Of particular note is that this value will be 0 if the statement that caused the error was
+ prepended by the @ error-control operator.
+ */
+ $this->previousErrorHandler = set_error_handler([$this, 'handleError']);
+ }
+
+ /**
+ * Disable error handler
+ */
+ public function deactivate() {
+ restore_error_handler();
+ $this->previousErrorHandler = null;
+ }
+
+ /**
+ * Error Handler to mute expected messages
+ *
+ * @link https://php.net/set_error_handler
+ *
+ * @param integer $errno Error level
+ * @param $errstr
+ * @param $errfile
+ * @param $errline
+ * @param $errcontext
+ *
+ * @return bool
+ */
+ public function handleError($errno, $errstr, $errfile, $errline, $errcontext = [])
+ {
+ if ($this->allowUndefinedProperties && preg_match(
+ '/^(Undefined property)/',
+ $errstr
+ )) {
+ return; // suppresses this error
+ }
+
+ if ($this->allowUndefinedArrayKeys && preg_match(
+ '/^(Undefined index|Undefined array key|Trying to access array offset on value of type)/',
+ $errstr
+ )) {
+ return; // suppresses this error
+ }
+
+ if ($this->allowDereferencingNonObjects && preg_match(
+ '/^Attempt to read property ".+?" on/',
+ $errstr
+ )) {
+ return; // suppresses this error
+ }
+
+ // pass all other errors through to the previous error handler or to the default PHP error handler
+ return $this->previousErrorHandler ?
+ call_user_func($this->previousErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext) : false;
+ }
+}
diff --git a/src/Exception.php b/src/Exception.php
new file mode 100644
index 00000000..0f75f568
--- /dev/null
+++ b/src/Exception.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * Smarty exception class
+ */
+class Exception extends \Exception {
+
+ /**
+ * @return string
+ */
+ public function __toString() {
+ return ' --> Smarty: ' . $this->message . ' <-- ';
+ }
+}
diff --git a/src/Extension/BCPluginsAdapter.php b/src/Extension/BCPluginsAdapter.php
new file mode 100644
index 00000000..aa0eefe2
--- /dev/null
+++ b/src/Extension/BCPluginsAdapter.php
@@ -0,0 +1,229 @@
+<?php
+
+namespace Smarty\Extension;
+
+use Smarty\BlockHandler\BlockPluginWrapper;
+use Smarty\Compile\CompilerInterface;
+use Smarty\Compile\Modifier\BCPluginWrapper as ModifierCompilerPluginWrapper;
+use Smarty\Compile\Tag\BCPluginWrapper as TagPluginWrapper;
+use Smarty\Filter\FilterPluginWrapper;
+use Smarty\FunctionHandler\BCPluginWrapper as FunctionPluginWrapper;
+
+class BCPluginsAdapter extends Base {
+
+ /**
+ * @var \Smarty\Smarty
+ */
+ private $smarty;
+
+ public function __construct(\Smarty\Smarty $smarty) {
+ $this->smarty = $smarty;
+ }
+
+ private function findPlugin($type, $name): ?array {
+ if (null !== $plugin = $this->smarty->getRegisteredPlugin($type, $name)) {
+ return $plugin;
+ }
+
+ return null;
+ }
+
+ public function getTagCompiler(string $tag): ?\Smarty\Compile\CompilerInterface {
+
+ $plugin = $this->findPlugin(\Smarty\Smarty::PLUGIN_COMPILER, $tag);
+ if ($plugin === null) {
+ return null;
+ }
+
+ if (is_callable($plugin[0])) {
+ $callback = $plugin[0];
+ $cacheable = (bool) $plugin[1] ?? true;
+ return new TagPluginWrapper($callback, $cacheable);
+ } elseif (class_exists($plugin[0])) {
+ $compiler = new $plugin[0];
+ if ($compiler instanceof CompilerInterface) {
+ return $compiler;
+ }
+ }
+
+ return null;
+ }
+
+ public function getFunctionHandler(string $functionName): ?\Smarty\FunctionHandler\FunctionHandlerInterface {
+ $plugin = $this->findPlugin(\Smarty\Smarty::PLUGIN_FUNCTION, $functionName);
+ if ($plugin === null) {
+ return null;
+ }
+ $callback = $plugin[0];
+ $cacheable = (bool) $plugin[1] ?? true;
+
+ return new FunctionPluginWrapper($callback, $cacheable);
+
+ }
+
+ public function getBlockHandler(string $blockTagName): ?\Smarty\BlockHandler\BlockHandlerInterface {
+ $plugin = $this->findPlugin(\Smarty\Smarty::PLUGIN_BLOCK, $blockTagName);
+ if ($plugin === null) {
+ return null;
+ }
+ $callback = $plugin[0];
+ $cacheable = (bool) $plugin[1] ?? true;
+
+ return new BlockPluginWrapper($callback, $cacheable);
+ }
+
+ public function getModifierCallback(string $modifierName) {
+
+ $plugin = $this->findPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, $modifierName);
+ if ($plugin === null) {
+ return null;
+ }
+ return $plugin[0];
+ }
+
+ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier\ModifierCompilerInterface {
+ $plugin = $this->findPlugin(\Smarty\Smarty::PLUGIN_MODIFIERCOMPILER, $modifier);
+ if ($plugin === null) {
+ return null;
+ }
+ $callback = $plugin[0];
+
+ return new ModifierCompilerPluginWrapper($callback);
+ }
+
+ /**
+ * @var array
+ */
+ private $preFilters = [];
+
+ public function getPreFilters(): array {
+ return $this->preFilters;
+ }
+
+ public function addPreFilter(\Smarty\Filter\FilterInterface $filter) {
+ $this->preFilters[] = $filter;
+ }
+
+ public function addCallableAsPreFilter(callable $callable, ?string $name = null) {
+ if ($name === null) {
+ $this->preFilters[] = new FilterPluginWrapper($callable);
+ } else {
+ $this->preFilters[$name] = new FilterPluginWrapper($callable);
+ }
+ }
+
+ public function removePrefilter(string $name) {
+ unset($this->preFilters[$name]);
+ }
+
+ /**
+ * @var array
+ */
+ private $postFilters = [];
+
+ public function getPostFilters(): array {
+ return $this->postFilters;
+ }
+
+ public function addPostFilter(\Smarty\Filter\FilterInterface $filter) {
+ $this->postFilters[] = $filter;
+ }
+
+ public function addCallableAsPostFilter(callable $callable, ?string $name = null) {
+ if ($name === null) {
+ $this->postFilters[] = new FilterPluginWrapper($callable);
+ } else {
+ $this->postFilters[$name] = new FilterPluginWrapper($callable);
+ }
+ }
+
+ public function removePostFilter(string $name) {
+ unset($this->postFilters[$name]);
+ }
+
+
+ /**
+ * @var array
+ */
+ private $outputFilters = [];
+
+ public function getOutputFilters(): array {
+ return $this->outputFilters;
+ }
+
+ public function addOutputFilter(\Smarty\Filter\FilterInterface $filter) {
+ $this->outputFilters[] = $filter;
+ }
+
+ public function addCallableAsOutputFilter(callable $callable, ?string $name = null) {
+ if ($name === null) {
+ $this->outputFilters[] = new FilterPluginWrapper($callable);
+ } else {
+ $this->outputFilters[$name] = new FilterPluginWrapper($callable);
+ }
+ }
+
+ public function removeOutputFilter(string $name) {
+ unset($this->outputFilters[$name]);
+ }
+
+ public function loadPluginsFromDir(string $path) {
+
+ foreach([
+ 'function',
+ 'modifier',
+ 'block',
+ 'compiler',
+ 'prefilter',
+ 'postfilter',
+ 'outputfilter',
+ ] as $type) {
+ foreach (glob($path . $type . '.?*.php') as $filename) {
+ $pluginName = $this->getPluginNameFromFilename($filename);
+ if ($pluginName !== null) {
+ require_once $filename;
+ $functionOrClassName = 'smarty_' . $type . '_' . $pluginName;
+ if (function_exists($functionOrClassName) || class_exists($functionOrClassName)) {
+ $this->smarty->registerPlugin($type, $pluginName, $functionOrClassName, true, []);
+ }
+ }
+ }
+ }
+
+ $type = 'resource';
+ foreach (glob($path . $type . '.?*.php') as $filename) {
+ $pluginName = $this->getPluginNameFromFilename($filename);
+ if ($pluginName !== null) {
+ require_once $filename;
+ if (class_exists($className = 'smarty_' . $type . '_' . $pluginName)) {
+ $this->smarty->registerResource($pluginName, new $className());
+ }
+ }
+ }
+
+ $type = 'cacheresource';
+ foreach (glob($path . $type . '.?*.php') as $filename) {
+ $pluginName = $this->getPluginNameFromFilename($filename);
+ if ($pluginName !== null) {
+ require_once $filename;
+ if (class_exists($className = 'smarty_' . $type . '_' . $pluginName)) {
+ $this->smarty->registerCacheResource($pluginName, new $className());
+ }
+ }
+ }
+
+ }
+
+ /**
+ * @param $filename
+ *
+ * @return string|null
+ */
+ private function getPluginNameFromFilename($filename) {
+ if (!preg_match('/.*\.([a-z_A-Z0-9]+)\.php$/',$filename,$matches)) {
+ return null;
+ }
+ return $matches[1];
+ }
+
+} \ No newline at end of file
diff --git a/src/Extension/Base.php b/src/Extension/Base.php
new file mode 100644
index 00000000..b37b6acd
--- /dev/null
+++ b/src/Extension/Base.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Smarty\Extension;
+
+use Smarty\FunctionHandler\FunctionHandlerInterface;
+
+class Base implements ExtensionInterface {
+
+ public function getTagCompiler(string $tag): ?\Smarty\Compile\CompilerInterface {
+ return null;
+ }
+
+ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier\ModifierCompilerInterface {
+ return null;
+ }
+
+ public function getFunctionHandler(string $functionName): ?\Smarty\FunctionHandler\FunctionHandlerInterface {
+ return null;
+ }
+
+ public function getBlockHandler(string $blockTagName): ?\Smarty\BlockHandler\BlockHandlerInterface {
+ return null;
+ }
+
+ public function getModifierCallback(string $modifierName) {
+ return null;
+ }
+
+ public function getPreFilters(): array {
+ return [];
+ }
+
+ public function getPostFilters(): array {
+ return [];
+ }
+
+ public function getOutputFilters(): array {
+ return [];
+ }
+
+} \ No newline at end of file
diff --git a/src/Extension/CallbackWrapper.php b/src/Extension/CallbackWrapper.php
new file mode 100644
index 00000000..827dd78b
--- /dev/null
+++ b/src/Extension/CallbackWrapper.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Smarty\Extension;
+
+use Smarty\Exception;
+
+class CallbackWrapper {
+
+ /**
+ * @var callback
+ */
+ private $callback;
+ /**
+ * @var string
+ */
+ private $modifierName;
+
+ /**
+ * @param string $modifierName
+ * @param callback $callback
+ */
+ public function __construct(string $modifierName, $callback) {
+ $this->callback = $callback;
+ $this->modifierName = $modifierName;
+ }
+
+ public function handle(...$params) {
+ try {
+ return call_user_func_array($this->callback, $params);
+ } catch (\ArgumentCountError $e) {
+ throw new Exception("Invalid number of arguments to modifier " . $this->modifierName);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php
new file mode 100644
index 00000000..a7c658d3
--- /dev/null
+++ b/src/Extension/CoreExtension.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Smarty\Extension;
+
+class CoreExtension extends Base {
+ public function getTagCompiler(string $tag): ?\Smarty\Compile\CompilerInterface {
+ switch ($tag) {
+ case 'append': return new \Smarty\Compile\Tag\Append();
+ case 'assign': return new \Smarty\Compile\Tag\Assign();
+ case 'block': return new \Smarty\Compile\Tag\Block();
+ case 'blockclose': return new \Smarty\Compile\Tag\BlockClose();
+ case 'break': return new \Smarty\Compile\Tag\BreakTag();
+ case 'call': return new \Smarty\Compile\Tag\Call();
+ case 'capture': return new \Smarty\Compile\Tag\Capture();
+ case 'captureclose': return new \Smarty\Compile\Tag\CaptureClose();
+ case 'config_load': return new \Smarty\Compile\Tag\ConfigLoad();
+ case 'continue': return new \Smarty\Compile\Tag\ContinueTag();
+ case 'debug': return new \Smarty\Compile\Tag\Debug();
+ case 'eval': return new \Smarty\Compile\Tag\EvalTag();
+ case 'extends': return new \Smarty\Compile\Tag\ExtendsTag();
+ case 'for': return new \Smarty\Compile\Tag\ForTag();
+ case 'foreach': return new \Smarty\Compile\Tag\ForeachTag();
+ case 'foreachelse': return new \Smarty\Compile\Tag\ForeachElse();
+ case 'foreachclose': return new \Smarty\Compile\Tag\ForeachClose();
+ case 'forelse': return new \Smarty\Compile\Tag\ForElse();
+ case 'forclose': return new \Smarty\Compile\Tag\ForClose();
+ case 'function': return new \Smarty\Compile\Tag\FunctionTag();
+ case 'functionclose': return new \Smarty\Compile\Tag\FunctionClose();
+ case 'if': return new \Smarty\Compile\Tag\IfTag();
+ case 'else': return new \Smarty\Compile\Tag\ElseTag();
+ case 'elseif': return new \Smarty\Compile\Tag\ElseIfTag();
+ case 'ifclose': return new \Smarty\Compile\Tag\IfClose();
+ case 'include': return new \Smarty\Compile\Tag\IncludeTag();
+ case 'ldelim': return new \Smarty\Compile\Tag\Ldelim();
+ case 'rdelim': return new \Smarty\Compile\Tag\Rdelim();
+ case 'nocache': return new \Smarty\Compile\Tag\Nocache();
+ case 'nocacheclose': return new \Smarty\Compile\Tag\NocacheClose();
+ case 'section': return new \Smarty\Compile\Tag\Section();
+ case 'sectionelse': return new \Smarty\Compile\Tag\SectionElse();
+ case 'sectionclose': return new \Smarty\Compile\Tag\SectionClose();
+ case 'setfilter': return new \Smarty\Compile\Tag\Setfilter();
+ case 'setfilterclose': return new \Smarty\Compile\Tag\SetfilterClose();
+ case 'while': return new \Smarty\Compile\Tag\WhileTag();
+ case 'whileclose': return new \Smarty\Compile\Tag\WhileClose();
+ }
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/src/Extension/DefaultExtension.php b/src/Extension/DefaultExtension.php
new file mode 100644
index 00000000..070274c7
--- /dev/null
+++ b/src/Extension/DefaultExtension.php
@@ -0,0 +1,677 @@
+<?php
+
+namespace Smarty\Extension;
+
+class DefaultExtension extends Base {
+
+ private $modifiers = [];
+
+ private $functionHandlers = [];
+
+ private $blockHandlers = [];
+
+ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier\ModifierCompilerInterface {
+
+ if (isset($this->modifiers[$modifier])) {
+ return $this->modifiers[$modifier];
+ }
+
+ switch ($modifier) {
+ case 'cat': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\CatModifierCompiler(); break;
+ case 'count_characters': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\CountCharactersModifierCompiler(); break;
+ case 'count_paragraphs': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\CountParagraphsModifierCompiler(); break;
+ case 'count_sentences': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\CountSentencesModifierCompiler(); break;
+ case 'count_words': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\CountWordsModifierCompiler(); break;
+ case 'default': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\DefaultModifierCompiler(); break;
+ case 'empty': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\EmptyModifierCompiler(); break;
+ case 'escape': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\EscapeModifierCompiler(); break;
+ case 'from_charset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\FromCharsetModifierCompiler(); break;
+ case 'indent': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IndentModifierCompiler(); break;
+ case 'isset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IssetModifierCompiler(); break;
+ case 'lower': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\LowerModifierCompiler(); break;
+ case 'nl2br': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\Nl2brModifierCompiler(); break;
+ case 'noprint': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\NoPrintModifierCompiler(); break;
+ case 'round': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\RoundModifierCompiler(); break;
+ case 'str_repeat': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StrRepeatModifierCompiler(); break;
+ case 'string_format': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StringFormatModifierCompiler(); break;
+ case 'strip': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StripModifierCompiler(); break;
+ case 'strip_tags': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StripTagsModifierCompiler(); break;
+ case 'strlen': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StrlenModifierCompiler(); break;
+ case 'to_charset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\ToCharsetModifierCompiler(); break;
+ case 'unescape': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\UnescapeModifierCompiler(); break;
+ case 'upper': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\UpperModifierCompiler(); break;
+ case 'wordwrap': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\WordWrapModifierCompiler(); break;
+ }
+
+ return $this->modifiers[$modifier] ?? null;
+ }
+
+ public function getModifierCallback(string $modifierName) {
+ switch ($modifierName) {
+ case 'capitalize': return [$this, 'smarty_modifier_capitalize'];
+ case 'count': return [$this, 'smarty_modifier_count'];
+ case 'date_format': return [$this, 'smarty_modifier_date_format'];
+ case 'debug_print_var': return [$this, 'smarty_modifier_debug_print_var'];
+ case 'escape': return [$this, 'smarty_modifier_escape'];
+ case 'explode': return [$this, 'smarty_modifier_explode'];
+ case 'mb_wordwrap': return [$this, 'smarty_modifier_mb_wordwrap'];
+ case 'number_format': return [$this, 'smarty_modifier_number_format'];
+ case 'regex_replace': return [$this, 'smarty_modifier_regex_replace'];
+ case 'replace': return [$this, 'smarty_modifier_replace'];
+ case 'spacify': return [$this, 'smarty_modifier_spacify'];
+ case 'truncate': return [$this, 'smarty_modifier_truncate'];
+ }
+ return null;
+ }
+
+ public function getFunctionHandler(string $functionName): ?\Smarty\FunctionHandler\FunctionHandlerInterface {
+
+ if (isset($this->functionHandlers[$functionName])) {
+ return $this->functionHandlers[$functionName];
+ }
+
+ switch ($functionName) {
+ case 'count': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Count(); break;
+ case 'counter': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Counter(); break;
+ case 'cycle': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Cycle(); break;
+ case 'fetch': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Fetch(); break;
+ case 'html_checkboxes': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlCheckboxes(); break;
+ case 'html_image': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlImage(); break;
+ case 'html_options': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlOptions(); break;
+ case 'html_radios': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlRadios(); break;
+ case 'html_select_date': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectDate(); break;
+ case 'html_select_time': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectTime(); break;
+ case 'html_table': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlTable(); break;
+ case 'in_array': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\InArray(); break;
+ case 'is_array': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\IsArray(); break;
+ case 'mailto': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Mailto(); break;
+ case 'math': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Math(); break;
+ case 'strlen': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Strlen(); break;
+ case 'time': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Time(); break;
+ }
+
+ return $this->functionHandlers[$functionName] ?? null;
+ }
+
+ public function getBlockHandler(string $blockTagName): ?\Smarty\BlockHandler\BlockHandlerInterface {
+
+ switch ($blockTagName) {
+ case 'textformat': $this->blockHandlers[$blockTagName] = new \Smarty\BlockHandler\TextFormat(); break;
+ }
+
+ return $this->blockHandlers[$blockTagName] ?? null;
+ }
+
+ /**
+ * Smarty spacify modifier plugin
+ * Type: modifier
+ * Name: spacify
+ * Purpose: add spaces between characters in a string
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.spacify.php spacify (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param string $string input string
+ * @param string $spacify_char string to insert between characters.
+ *
+ * @return string
+ */
+ public function smarty_modifier_spacify($string, $spacify_char = ' ')
+ {
+ // well… what about charsets besides latin and UTF-8?
+ return implode($spacify_char, preg_split('//' . \Smarty\Smarty::$_UTF8_MODIFIER, $string, -1, PREG_SPLIT_NO_EMPTY));
+ }
+
+ /**
+ * Smarty capitalize modifier plugin
+ * Type: modifier
+ * Name: capitalize
+ * Purpose: capitalize words in the string
+ * {@internal {$string|capitalize:true:true} is the fastest option for MBString enabled systems }}
+ *
+ * @param string $string string to capitalize
+ * @param boolean $uc_digits also capitalize "x123" to "X123"
+ * @param boolean $lc_rest capitalize first letters, lowercase all following letters "aAa" to "Aaa"
+ *
+ * @return string capitalized string
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author Rodney Rehm
+ */
+ public function smarty_modifier_capitalize($string, $uc_digits = false, $lc_rest = false)
+ {
+ $string = (string) $string;
+
+ if ($lc_rest) {
+ // uppercase (including hyphenated words)
+ $upper_string = mb_convert_case($string, MB_CASE_TITLE, \Smarty\Smarty::$_CHARSET);
+ } else {
+ // uppercase word breaks
+ $upper_string = preg_replace_callback(
+ "!(^|[^\p{L}'])([\p{Ll}])!S" . \Smarty\Smarty::$_UTF8_MODIFIER,
+ function ($matches) {
+ return stripslashes($matches[1]) .
+ mb_convert_case(stripslashes($matches[2]), MB_CASE_UPPER, \Smarty\Smarty::$_CHARSET);
+ },
+ $string
+ );
+ }
+ // check uc_digits case
+ if (!$uc_digits) {
+ if (preg_match_all(
+ "!\b([\p{L}]*[\p{N}]+[\p{L}]*)\b!" . \Smarty\Smarty::$_UTF8_MODIFIER,
+ $string,
+ $matches,
+ PREG_OFFSET_CAPTURE
+ )
+ ) {
+ foreach ($matches[ 1 ] as $match) {
+ $upper_string =
+ substr_replace(
+ $upper_string,
+ mb_strtolower($match[ 0 ], \Smarty\Smarty::$_CHARSET),
+ $match[ 1 ],
+ strlen($match[ 0 ])
+ );
+ }
+ }
+ }
+ $upper_string =
+ preg_replace_callback(
+ "!((^|\s)['\"])(\w)!" . \Smarty\Smarty::$_UTF8_MODIFIER,
+ function ($matches) {
+ return stripslashes(
+ $matches[ 1 ]) . mb_convert_case(stripslashes($matches[ 3 ]),
+ MB_CASE_UPPER,
+ \Smarty\Smarty::$_CHARSET
+ );
+ },
+ $upper_string
+ );
+ return $upper_string;
+ }
+
+ /**
+ * Smarty count modifier plugin
+ * Type: modifier
+ * Name: count
+ * Purpose: counts all elements in an array or in a Countable object
+ * Input:
+ * - Countable|array: array or object to count
+ * - mode: int defaults to 0 for normal count mode, if set to 1 counts recursive
+ *
+ * @param mixed $arrayOrObject input array/object
+ * @param int $mode count mode
+ *
+ * @return int
+ */
+ public function smarty_modifier_count($arrayOrObject, $mode = 0) {
+ /*
+ * @see https://www.php.net/count
+ * > Prior to PHP 8.0.0, if the parameter was neither an array nor an object that implements the Countable interface,
+ * > 1 would be returned, unless value was null, in which case 0 would be returned.
+ */
+
+ if ($arrayOrObject instanceof Countable || is_array($arrayOrObject)) {
+ return count($arrayOrObject, (int) $mode);
+ } elseif ($arrayOrObject === null) {
+ return 0;
+ }
+ return 1;
+ }
+
+ /**
+ * Smarty date_format modifier plugin
+ * Type: modifier
+ * Name: date_format
+ * Purpose: format datestamps via strftime
+ * Input:
+ * - string: input date string
+ * - format: strftime format for output
+ * - default_date: default date if $string is empty
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.date.format.php date_format (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param string $string input date string
+ * @param string $format strftime format for output
+ * @param string $default_date default date if $string is empty
+ * @param string $formatter either 'strftime' or 'auto'
+ *
+ * @return string |void
+ * @uses smarty_make_timestamp()
+ */
+ public function smarty_modifier_date_format($string, $format = null, $default_date = '', $formatter = 'auto')
+ {
+ if ($format === null) {
+ $format = \Smarty\Smarty::$_DATE_FORMAT;
+ }
+
+ if (!empty($string) && $string !== '0000-00-00' && $string !== '0000-00-00 00:00:00') {
+ $timestamp = smarty_make_timestamp($string);
+ } elseif (!empty($default_date)) {
+ $timestamp = smarty_make_timestamp($default_date);
+ } else {
+ return;
+ }
+ if ($formatter === 'strftime' || ($formatter === 'auto' && strpos($format, '%') !== false)) {
+ if (\Smarty\Smarty::$_IS_WINDOWS) {
+ $_win_from = array(
+ '%D',
+ '%h',
+ '%n',
+ '%r',
+ '%R',
+ '%t',
+ '%T'
+ );
+ $_win_to = array(
+ '%m/%d/%y',
+ '%b',
+ "\n",
+ '%I:%M:%S %p',
+ '%H:%M',
+ "\t",
+ '%H:%M:%S'
+ );
+ if (strpos($format, '%e') !== false) {
+ $_win_from[] = '%e';
+ $_win_to[] = sprintf('%\' 2d', date('j', $timestamp));
+ }
+ if (strpos($format, '%l') !== false) {
+ $_win_from[] = '%l';
+ $_win_to[] = sprintf('%\' 2d', date('h', $timestamp));
+ }
+ $format = str_replace($_win_from, $_win_to, $format);
+ }
+ // @ to suppress deprecation errors when running in PHP8.1 or higher.
+ return @strftime($format, $timestamp);
+ } else {
+ return date($format, $timestamp);
+ }
+ }
+
+ /**
+ * Smarty debug_print_var modifier plugin
+ * Type: modifier
+ * Name: debug_print_var
+ * Purpose: formats variable contents for display in the console
+ *
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param array|object $var variable to be formatted
+ * @param int $max maximum recursion depth if $var is an array or object
+ * @param int $length maximum string length if $var is a string
+ * @param int $depth actual recursion depth
+ * @param array $objects processed objects in actual depth to prevent recursive object processing
+ *
+ * @return string
+ */
+ public function smarty_modifier_debug_print_var($var, $max = 10, $length = 40, $depth = 0, $objects = array())
+ {
+ $_replace = array("\n" => '\n', "\r" => '\r', "\t" => '\t');
+ switch (gettype($var)) {
+ case 'array':
+ $results = '<b>Array (' . count($var) . ')</b>';
+ if ($depth === $max) {
+ break;
+ }
+ foreach ($var as $curr_key => $curr_val) {
+ $results .= '<br>' . str_repeat('&nbsp;', $depth * 2) . '<b>' . strtr($curr_key, $_replace) .
+ '</b> =&gt; ' .
+ $this->smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
+ $depth--;
+ }
+ break;
+ case 'object':
+ $object_vars = get_object_vars($var);
+ $results = '<b>' . get_class($var) . ' Object (' . count($object_vars) . ')</b>';
+ if (in_array($var, $objects)) {
+ $results .= ' called recursive';
+ break;
+ }
+ if ($depth === $max) {
+ break;
+ }
+ $objects[] = $var;
+ foreach ($object_vars as $curr_key => $curr_val) {
+ $results .= '<br>' . str_repeat('&nbsp;', $depth * 2) . '<b> -&gt;' . strtr($curr_key, $_replace) .
+ '</b> = ' . $this->smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
+ $depth--;
+ }
+ break;
+ case 'boolean':
+ case 'NULL':
+ case 'resource':
+ if (true === $var) {
+ $results = 'true';
+ } elseif (false === $var) {
+ $results = 'false';
+ } elseif (null === $var) {
+ $results = 'null';
+ } else {
+ $results = htmlspecialchars((string)$var);
+ }
+ $results = '<i>' . $results . '</i>';
+ break;
+ case 'integer':
+ case 'float':
+ $results = htmlspecialchars((string)$var);
+ break;
+ case 'string':
+ $results = strtr($var, $_replace);
+ if (mb_strlen($var, \Smarty\Smarty::$_CHARSET) > $length) {
+ $results = mb_substr($var, 0, $length - 3, \Smarty\Smarty::$_CHARSET) . '...';
+ }
+ $results = htmlspecialchars('"' . $results . '"', ENT_QUOTES, \Smarty\Smarty::$_CHARSET);
+ break;
+ case 'unknown type':
+ default:
+ $results = strtr((string)$var, $_replace);
+ if (mb_strlen($results, \Smarty\Smarty::$_CHARSET) > $length) {
+ $results = mb_substr($results, 0, $length - 3, \Smarty\Smarty::$_CHARSET) . '...';
+ }
+ $results = htmlspecialchars($results, ENT_QUOTES, \Smarty\Smarty::$_CHARSET);
+ }
+ return $results;
+ }
+
+ /**
+ * Smarty escape modifier plugin
+ * Type: modifier
+ * Name: escape
+ * Purpose: escape string for output
+ *
+ * @link https://www.smarty.net/docs/en/language.modifier.escape
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param string $string input string
+ * @param string $esc_type escape type
+ * @param string $char_set character set, used for htmlspecialchars() or htmlentities()
+ * @param boolean $double_encode encode already encoded entitites again, used for htmlspecialchars() or htmlentities()
+ *
+ * @return string escaped input string
+ */
+ public function smarty_modifier_escape($string, $esc_type = 'html', $char_set = null, $double_encode = true)
+ {
+ if (!$char_set) {
+ $char_set = \Smarty\Smarty::$_CHARSET;
+ }
+
+ $string = (string)$string;
+
+ switch ($esc_type) {
+ case 'html':
+ return htmlspecialchars($string, ENT_QUOTES, $char_set, $double_encode);
+ // no break
+ case 'htmlall':
+ $string = mb_convert_encoding($string, 'UTF-8', $char_set);
+ return htmlentities($string, ENT_QUOTES, 'UTF-8', $double_encode);
+ // no break
+ case 'url':
+ return rawurlencode($string);
+ case 'urlpathinfo':
+ return str_replace('%2F', '/', rawurlencode($string));
+ case 'quotes':
+ // escape unescaped single quotes
+ return preg_replace("%(?<!\\\\)'%", "\\'", $string);
+ case 'hex':
+ // escape every byte into hex
+ // Note that the UTF-8 encoded character ä will be represented as %c3%a4
+ $return = '';
+ $_length = strlen($string);
+ for ($x = 0; $x < $_length; $x++) {
+ $return .= '%' . bin2hex($string[ $x ]);
+ }
+ return $return;
+ case 'hexentity':
+ $return = '';
+ foreach ($this->mb_to_unicode($string, \Smarty\Smarty::$_CHARSET) as $unicode) {
+ $return .= '&#x' . strtoupper(dechex($unicode)) . ';';
+ }
+ return $return;
+ case 'decentity':
+ $return = '';
+ foreach ($this->mb_to_unicode($string, \Smarty\Smarty::$_CHARSET) as $unicode) {
+ $return .= '&#' . $unicode . ';';
+ }
+ return $return;
+ case 'javascript':
+ // escape quotes and backslashes, newlines, etc.
+ return strtr(
+ $string,
+ array(
+ '\\' => '\\\\',
+ "'" => "\\'",
+ '"' => '\\"',
+ "\r" => '\\r',
+ "\n" => '\\n',
+ '</' => '<\/',
+ // see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
+ '<!--' => '<\!--',
+ '<s' => '<\s',
+ '<S' => '<\S',
+ "`" => "\\\\`",
+ "\${" => "\\\\\\$\\{"
+ )
+ );
+ case 'mail':
+ return smarty_mb_str_replace(
+ array(
+ '@',
+ '.'
+ ),
+ array(
+ ' [AT] ',
+ ' [DOT] '
+ ),
+ $string
+ );
+ case 'nonstd':
+ // escape non-standard chars, such as ms document quotes
+ $return = '';
+ foreach ($this->mb_to_unicode($string, \Smarty\Smarty::$_CHARSET) as $unicode) {
+ if ($unicode >= 126) {
+ $return .= '&#' . $unicode . ';';
+ } else {
+ $return .= chr($unicode);
+ }
+ }
+ return $return;
+ default:
+ trigger_error("escape: unsupported type: $esc_type - returning unmodified string", E_USER_NOTICE);
+ return $string;
+ }
+ }
+
+
+ /**
+ * convert characters to their decimal unicode equivalents
+ *
+ * @link http://www.ibm.com/developerworks/library/os-php-unicode/index.html#listing3 for inspiration
+ *
+ * @param string $string characters to calculate unicode of
+ * @param string $encoding encoding of $string
+ *
+ * @return array sequence of unicodes
+ * @author Rodney Rehm
+ */
+ private function mb_to_unicode($string, $encoding = null) {
+ if ($encoding) {
+ $expanded = mb_convert_encoding($string, 'UTF-32BE', $encoding);
+ } else {
+ $expanded = mb_convert_encoding($string, 'UTF-32BE');
+ }
+ return unpack('N*', $expanded);
+ }
+
+ /**
+ * Smarty explode modifier plugin
+ * Type: modifier
+ * Name: explode
+ * Purpose: split a string by a string
+ *
+ * @param string $separator
+ * @param string $string
+ * @param int|null $limit
+ *
+ * @return array
+ */
+ public function smarty_modifier_explode($separator, $string, ?int $limit = null)
+ {
+ // provide $string default to prevent deprecation errors in PHP >=8.1
+ return explode($separator, $string ?? '', $limit ?? PHP_INT_MAX);
+ }
+
+ /**
+ * Smarty wordwrap modifier plugin
+ * Type: modifier
+ * Name: mb_wordwrap
+ * Purpose: Wrap a string to a given number of characters
+ *
+ * @link https://php.net/manual/en/function.wordwrap.php for similarity
+ *
+ * @param string $str the string to wrap
+ * @param int $width the width of the output
+ * @param string $break the character used to break the line
+ * @param boolean $cut ignored parameter, just for the sake of
+ *
+ * @return string wrapped string
+ * @author Rodney Rehm
+ */
+ public function smarty_modifier_mb_wordwrap($str, $width = 75, $break = "\n", $cut = false)
+ {
+ return smarty_mb_wordwrap($str, $width, $break, $cut);
+ }
+
+ /**
+ * Smarty number_format modifier plugin
+ * Type: modifier
+ * Name: number_format
+ * Purpose: Format a number with grouped thousands
+ *
+ * @param float|null $num
+ * @param int $decimals
+ * @param string|null $decimal_separator
+ * @param string|null $thousands_separator
+ *
+ * @return string
+ */
+ public function smarty_modifier_number_format(?float $num, int $decimals = 0, ?string $decimal_separator = ".", ?string $thousands_separator = ",")
+ {
+ // provide $num default to prevent deprecation errors in PHP >=8.1
+ return number_format($num ?? 0.0, $decimals, $decimal_separator, $thousands_separator);
+ }
+
+ /**
+ * Smarty regex_replace modifier plugin
+ * Type: modifier
+ * Name: regex_replace
+ * Purpose: regular expression search/replace
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.regex.replace.php
+ * regex_replace (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param string $string input string
+ * @param string|array $search regular expression(s) to search for
+ * @param string|array $replace string(s) that should be replaced
+ * @param int $limit the maximum number of replacements
+ *
+ * @return string
+ */
+ public function smarty_modifier_regex_replace($string, $search, $replace, $limit = -1)
+ {
+ if (is_array($search)) {
+ foreach ($search as $idx => $s) {
+ $search[ $idx ] = $this->regex_replace_check($s);
+ }
+ } else {
+ $search = $this->regex_replace_check($search);
+ }
+ return preg_replace($search, $replace, $string, $limit);
+ }
+
+ /**
+ * @param string $search string(s) that should be replaced
+ *
+ * @return string
+ * @ignore
+ */
+ private function regex_replace_check($search)
+ {
+ // null-byte injection detection
+ // anything behind the first null-byte is ignored
+ if (($pos = strpos($search, "\0")) !== false) {
+ $search = substr($search, 0, $pos);
+ }
+ // remove eval-modifier from $search
+ if (preg_match('!([a-zA-Z\s]+)$!s', $search, $match) && (strpos($match[ 1 ], 'e') !== false)) {
+ $search = substr($search, 0, -strlen($match[ 1 ])) . preg_replace('![e\s]+!', '', $match[ 1 ]);
+ }
+ return $search;
+ }
+
+ /**
+ * Smarty replace modifier plugin
+ * Type: modifier
+ * Name: replace
+ * Purpose: simple search/replace
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.replace.php replace (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author Uwe Tews
+ *
+ * @param string $string input string
+ * @param string $search text to search for
+ * @param string $replace replacement text
+ *
+ * @return string
+ */
+ public function smarty_modifier_replace($string, $search, $replace)
+ {
+ return smarty_mb_str_replace($search, $replace, $string);
+ }
+
+ /**
+ * Smarty truncate modifier plugin
+ * Type: modifier
+ * Name: truncate
+ * Purpose: Truncate a string to a certain length if necessary,
+ * optionally splitting in the middle of a word, and
+ * appending the $etc string or inserting $etc into the middle.
+ *
+ * @link https://www.smarty.net/manual/en/language.modifier.truncate.php truncate (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param string $string input string
+ * @param integer $length length of truncated text
+ * @param string $etc end string
+ * @param boolean $break_words truncate at word boundary
+ * @param boolean $middle truncate in the middle of text
+ *
+ * @return string truncated string
+ */
+ public function smarty_modifier_truncate($string, $length = 80, $etc = '...', $break_words = false, $middle = false)
+ {
+ if ($length === 0 || $string === null) {
+ return '';
+ }
+ if (mb_strlen($string, \Smarty\Smarty::$_CHARSET) > $length) {
+ $length -= min($length, mb_strlen($etc, \Smarty\Smarty::$_CHARSET));
+ if (!$break_words && !$middle) {
+ $string = preg_replace(
+ '/\s+?(\S+)?$/' . \Smarty\Smarty::$_UTF8_MODIFIER,
+ '',
+ mb_substr($string, 0, $length + 1, \Smarty\Smarty::$_CHARSET)
+ );
+ }
+ if (!$middle) {
+ return mb_substr($string, 0, $length, \Smarty\Smarty::$_CHARSET) . $etc;
+ }
+ return mb_substr($string, 0, intval($length / 2), \Smarty\Smarty::$_CHARSET) . $etc .
+ mb_substr($string, -intval($length / 2), $length, \Smarty\Smarty::$_CHARSET);
+ }
+ return $string;
+ }
+
+} \ No newline at end of file
diff --git a/src/Extension/ExtensionInterface.php b/src/Extension/ExtensionInterface.php
new file mode 100644
index 00000000..50752f5d
--- /dev/null
+++ b/src/Extension/ExtensionInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Smarty\Extension;
+
+interface ExtensionInterface {
+
+ public function getTagCompiler(string $tag): ?\Smarty\Compile\CompilerInterface;
+
+ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier\ModifierCompilerInterface;
+
+ public function getFunctionHandler(string $functionName): ?\Smarty\FunctionHandler\FunctionHandlerInterface;
+
+ public function getBlockHandler(string $blockTagName): ?\Smarty\BlockHandler\BlockHandlerInterface;
+
+ public function getModifierCallback(string $modifierName);
+
+ public function getPreFilters(): array;
+ public function getPostFilters(): array;
+ public function getOutputFilters(): array;
+
+} \ No newline at end of file
diff --git a/src/Filter/FilterInterface.php b/src/Filter/FilterInterface.php
new file mode 100644
index 00000000..42280e25
--- /dev/null
+++ b/src/Filter/FilterInterface.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Smarty\Filter;
+
+interface FilterInterface {
+
+ public function filter($code, \Smarty\Template $template);
+
+} \ No newline at end of file
diff --git a/src/Filter/FilterPluginWrapper.php b/src/Filter/FilterPluginWrapper.php
new file mode 100644
index 00000000..665ee385
--- /dev/null
+++ b/src/Filter/FilterPluginWrapper.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Smarty\Filter;
+
+class FilterPluginWrapper implements FilterInterface {
+
+ private $callback;
+
+ public function __construct($callback) {
+ $this->callback = $callback;
+ }
+ public function filter($code, \Smarty\Template $template) {
+ return call_user_func($this->callback, $code, $template);
+ }
+} \ No newline at end of file
diff --git a/src/Filter/Output/TrimWhitespace.php b/src/Filter/Output/TrimWhitespace.php
new file mode 100644
index 00000000..5803725e
--- /dev/null
+++ b/src/Filter/Output/TrimWhitespace.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Smarty\Filter\Output;
+
+/**
+ * Smarty trimwhitespace outputfilter plugin
+ * Trim unnecessary whitespace from HTML markup.
+ *
+ * @author Rodney Rehm
+ *
+ * @param string $source input string
+ *
+ * @return string filtered output
+ */
+class TrimWhitespace implements \Smarty\Filter\FilterInterface {
+
+ public function filter($code, \Smarty\Template $template) {
+
+ $source = $code;
+
+ $store = array();
+ $_store = 0;
+ $_offset = 0;
+ // Unify Line-Breaks to \n
+ $source = preg_replace('/\015\012|\015|\012/', "\n", $source);
+ // capture Internet Explorer and KnockoutJS Conditional Comments
+ if (preg_match_all(
+ '#<!--((\[[^\]]+\]>.*?<!\[[^\]]+\])|(\s*/?ko\s+.+))-->#is',
+ $source,
+ $matches,
+ PREG_OFFSET_CAPTURE | PREG_SET_ORDER
+ )
+ ) {
+ foreach ($matches as $match) {
+ $store[] = $match[ 0 ][ 0 ];
+ $_length = strlen($match[ 0 ][ 0 ]);
+ $replace = '@!@SMARTY:' . $_store . ':SMARTY@!@';
+ $source = substr_replace($source, $replace, $match[ 0 ][ 1 ] - $_offset, $_length);
+ $_offset += $_length - strlen($replace);
+ $_store++;
+ }
+ }
+ // Strip all HTML-Comments
+ // yes, even the ones in <script> - see https://stackoverflow.com/a/808850/515124
+ $source = preg_replace('#<!--.*?-->#ms', '', $source);
+ // capture html elements not to be messed with
+ $_offset = 0;
+ if (preg_match_all(
+ '#(<script[^>]*>.*?</script[^>]*>)|(<textarea[^>]*>.*?</textarea[^>]*>)|(<pre[^>]*>.*?</pre[^>]*>)#is',
+ $source,
+ $matches,
+ PREG_OFFSET_CAPTURE | PREG_SET_ORDER
+ )
+ ) {
+ foreach ($matches as $match) {
+ $store[] = $match[ 0 ][ 0 ];
+ $_length = strlen($match[ 0 ][ 0 ]);
+ $replace = '@!@SMARTY:' . $_store . ':SMARTY@!@';
+ $source = substr_replace($source, $replace, $match[ 0 ][ 1 ] - $_offset, $_length);
+ $_offset += $_length - strlen($replace);
+ $_store++;
+ }
+ }
+ $expressions = array(// replace multiple spaces between tags by a single space
+ // can't remove them entirely, because that might break poorly implemented CSS display:inline-block elements
+ '#(:SMARTY@!@|>)\s+(?=@!@SMARTY:|<)#s' => '\1 \2',
+ // remove spaces between attributes (but not in attribute values!)
+ '#(([a-z0-9]\s*=\s*("[^"]*?")|(\'[^\']*?\'))|<[a-z0-9_]+)\s+([a-z/>])#is' => '\1 \5',
+ // note: for some very weird reason trim() seems to remove spaces inside attributes.
+ // maybe a \0 byte or something is interfering?
+ '#^\s+<#Ss' => '<',
+ '#>\s+$#Ss' => '>',
+ );
+ $source = preg_replace(array_keys($expressions), array_values($expressions), $source);
+ // note: for some very weird reason trim() seems to remove spaces inside attributes.
+ // maybe a \0 byte or something is interfering?
+ // $source = trim( $source );
+ $_offset = 0;
+ if (preg_match_all('#@!@SMARTY:([0-9]+):SMARTY@!@#is', $source, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $_length = strlen($match[ 0 ][ 0 ]);
+ $replace = $store[ $match[ 1 ][ 0 ] ];
+ $source = substr_replace($source, $replace, $match[ 0 ][ 1 ] + $_offset, $_length);
+ $_offset += strlen($replace) - $_length;
+ $_store++;
+ }
+ }
+ return $source;
+ }
+
+} \ No newline at end of file
diff --git a/src/FunctionHandler/BCPluginWrapper.php b/src/FunctionHandler/BCPluginWrapper.php
new file mode 100644
index 00000000..04af07ed
--- /dev/null
+++ b/src/FunctionHandler/BCPluginWrapper.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+class BCPluginWrapper extends Base {
+
+ private $callback;
+
+ public function __construct($callback, bool $cacheable = true) {
+ $this->callback = $callback;
+ $this->cacheable = $cacheable;
+ }
+
+ public function handle($params, Template $template) {
+ $func = $this->callback;
+ return $func($params, $template);
+ }
+
+} \ No newline at end of file
diff --git a/src/FunctionHandler/Base.php b/src/FunctionHandler/Base.php
new file mode 100644
index 00000000..7cf19637
--- /dev/null
+++ b/src/FunctionHandler/Base.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+class Base implements FunctionHandlerInterface {
+
+ /**
+ * @var bool
+ */
+ protected $cacheable = true;
+
+ public function isCacheable(): bool {
+ return $this->cacheable;
+ }
+
+ public function handle($params, Template $template) {
+ // TODO: Implement handle() method.
+ }
+} \ No newline at end of file
diff --git a/src/FunctionHandler/Count.php b/src/FunctionHandler/Count.php
new file mode 100644
index 00000000..768d809b
--- /dev/null
+++ b/src/FunctionHandler/Count.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * count(Countable|array $value, int $mode = COUNT_NORMAL): int
+ * If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count() will recursively count the array.
+ * This is particularly useful for counting all the elements of a multidimensional array.
+ *
+ * Returns the number of elements in value. Prior to PHP 8.0.0, if the parameter was neither an array nor an object that
+ * implements the Countable interface, 1 would be returned, unless value was null, in which case 0 would be returned.
+ */
+class Count extends Base {
+
+ public function handle($params, Template $template) {
+
+ $params = array_values($params ?? []);
+
+ if (count($params) < 1 || count($params) > 2) {
+ throw new Exception("Invalid number of arguments for count. count expects 1 or 2 parameters.");
+ }
+
+ $value = $params[0];
+
+ if ($value instanceof \Countable) {
+ return $value->count();
+ }
+
+ $mode = count($params) == 2 ? (int) $params[1] : COUNT_NORMAL;
+ return count((array) $value, $mode);
+ }
+
+} \ No newline at end of file
diff --git a/src/FunctionHandler/Counter.php b/src/FunctionHandler/Counter.php
new file mode 100644
index 00000000..c6f9fdbe
--- /dev/null
+++ b/src/FunctionHandler/Counter.php
@@ -0,0 +1,63 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {counter} function plugin
+ * Type: function
+ * Name: counter
+ * Purpose: print out a counter value
+ *
+ * @param array $params parameters
+ * @param Template $template template object
+ *
+ * @return string|null
+ *@link https://www.smarty.net/manual/en/language.function.counter.php {counter}
+ * (Smarty online manual)
+ *
+ * @author Monte Ohrt <monte at ohrt dot com>
+ */
+class Counter extends Base {
+
+ private $counters = [];
+
+ public function handle($params, Template $template) {
+ $name = (isset($params['name'])) ? $params['name'] : 'default';
+ if (!isset($this->counters[$name])) {
+ $this->counters[$name] = ['start' => 1, 'skip' => 1, 'direction' => 'up', 'count' => 1];
+ }
+ $counter =& $this->counters[$name];
+ if (isset($params['start'])) {
+ $counter['start'] = $counter['count'] = (int)$params['start'];
+ }
+ if (!empty($params['assign'])) {
+ $counter['assign'] = $params['assign'];
+ }
+ if (isset($counter['assign'])) {
+ $template->assign($counter['assign'], $counter['count']);
+ }
+ if (isset($params['print'])) {
+ $print = (bool)$params['print'];
+ } else {
+ $print = empty($counter['assign']);
+ }
+ if ($print) {
+ $retval = $counter['count'];
+ } else {
+ $retval = null;
+ }
+ if (isset($params['skip'])) {
+ $counter['skip'] = $params['skip'];
+ }
+ if (isset($params['direction'])) {
+ $counter['direction'] = $params['direction'];
+ }
+ if ($counter['direction'] === 'down') {
+ $counter['count'] -= $counter['skip'];
+ } else {
+ $counter['count'] += $counter['skip'];
+ }
+ return $retval;
+ }
+} \ No newline at end of file
diff --git a/src/FunctionHandler/Cycle.php b/src/FunctionHandler/Cycle.php
new file mode 100644
index 00000000..756573fa
--- /dev/null
+++ b/src/FunctionHandler/Cycle.php
@@ -0,0 +1,92 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {cycle} function plugin
+ * Type: function
+ * Name: cycle
+ * Date: May 3, 2002
+ * Purpose: cycle through given values
+ * Params:
+ *
+ * - name - name of cycle (optional)
+ * - values - comma separated list of values to cycle, or an array of values to cycle
+ * (this can be left out for subsequent calls)
+ * - reset - boolean - resets given var to true
+ * - print - boolean - print var or not. default is true
+ * - advance - boolean - whether to advance the cycle
+ * - delimiter - the value delimiter, default is ","
+ * - assign - boolean, assigns to template var instead of printed.
+ *
+ * Examples:
+ *
+ * {cycle values="#eeeeee,#d0d0d0d"}
+ * {cycle name=row values="one,two,three" reset=true}
+ * {cycle name=row}
+ *
+ * @link https://www.smarty.net/manual/en/language.function.cycle.php {cycle}
+ * (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author credit to Mark Priatel <mpriatel@rogers.com>
+ * @author credit to Gerard <gerard@interfold.com>
+ * @author credit to Jason Sweat <jsweat_php@yahoo.com>
+ * @version 1.3
+ *
+ * @param array $params parameters
+ * @param Template $template template object
+ *
+ * @return string|null
+ */
+class Cycle extends Base {
+
+ public function handle($params, Template $template) {
+ static $cycle_vars;
+ $name = (empty($params['name'])) ? 'default' : $params['name'];
+ $print = !(isset($params['print'])) || (bool)$params['print'];
+ $advance = !(isset($params['advance'])) || (bool)$params['advance'];
+ $reset = isset($params['reset']) && (bool)$params['reset'];
+ if (!isset($params['values'])) {
+ if (!isset($cycle_vars[$name]['values'])) {
+ trigger_error('cycle: missing \'values\' parameter');
+ return;
+ }
+ } else {
+ if (isset($cycle_vars[$name]['values']) && $cycle_vars[$name]['values'] !== $params['values']) {
+ $cycle_vars[$name]['index'] = 0;
+ }
+ $cycle_vars[$name]['values'] = $params['values'];
+ }
+ if (isset($params['delimiter'])) {
+ $cycle_vars[$name]['delimiter'] = $params['delimiter'];
+ } elseif (!isset($cycle_vars[$name]['delimiter'])) {
+ $cycle_vars[$name]['delimiter'] = ',';
+ }
+ if (is_array($cycle_vars[$name]['values'])) {
+ $cycle_array = $cycle_vars[$name]['values'];
+ } else {
+ $cycle_array = explode($cycle_vars[$name]['delimiter'], $cycle_vars[$name]['values']);
+ }
+ if (!isset($cycle_vars[$name]['index']) || $reset) {
+ $cycle_vars[$name]['index'] = 0;
+ }
+ if (isset($params['assign'])) {
+ $print = false;
+ $template->assign($params['assign'], $cycle_array[$cycle_vars[$name]['index']]);
+ }
+ if ($print) {
+ $retval = $cycle_array[$cycle_vars[$name]['index']];
+ } else {
+ $retval = null;
+ }
+ if ($advance) {
+ if ($cycle_vars[$name]['index'] >= count($cycle_array) - 1) {
+ $cycle_vars[$name]['index'] = 0;
+ } else {
+ $cycle_vars[$name]['index']++;
+ }
+ }
+ return $retval;
+ }
+} \ No newline at end of file
diff --git a/src/FunctionHandler/Fetch.php b/src/FunctionHandler/Fetch.php
new file mode 100644
index 00000000..3031ac88
--- /dev/null
+++ b/src/FunctionHandler/Fetch.php
@@ -0,0 +1,205 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * Smarty {fetch} plugin
+ * Type: function
+ * Name: fetch
+ * Purpose: fetch file, web or ftp data and display results
+ *
+ * @link https://www.smarty.net/manual/en/language.function.fetch.php {fetch}
+ * (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param array $params parameters
+ * @param Template $template template object
+ *
+ * @throws Exception
+ * @return string|null if the assign parameter is passed, Smarty assigns the result to a template variable
+ */
+class Fetch extends Base {
+
+ public function handle($params, Template $template) {
+ if (empty($params['file'])) {
+ trigger_error('[plugin] fetch parameter \'file\' cannot be empty', E_USER_NOTICE);
+ return;
+ }
+ // strip file protocol
+ if (stripos($params['file'], 'file://') === 0) {
+ $params['file'] = substr($params['file'], 7);
+ }
+ $protocol = strpos($params['file'], '://');
+ if ($protocol !== false) {
+ $protocol = strtolower(substr($params['file'], 0, $protocol));
+ }
+ if (isset($template->getSmarty()->security_policy)) {
+ if ($protocol) {
+ // remote resource (or php stream, …)
+ if (!$template->getSmarty()->security_policy->isTrustedUri($params['file'])) {
+ return;
+ }
+ } else {
+ // local file
+ if (!$template->getSmarty()->security_policy->isTrustedResourceDir($params['file'])) {
+ return;
+ }
+ }
+ }
+ $content = '';
+ if ($protocol === 'http') {
+ // http fetch
+ if ($uri_parts = parse_url($params['file'])) {
+ // set defaults
+ $host = $server_name = $uri_parts['host'];
+ $timeout = 30;
+ $accept = 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*';
+ $agent = 'Smarty Template Engine ' . \Smarty\Smarty::SMARTY_VERSION;
+ $referer = '';
+ $uri = !empty($uri_parts['path']) ? $uri_parts['path'] : '/';
+ $uri .= !empty($uri_parts['query']) ? '?' . $uri_parts['query'] : '';
+ $_is_proxy = false;
+ if (empty($uri_parts['port'])) {
+ $port = 80;
+ } else {
+ $port = $uri_parts['port'];
+ }
+ if (!empty($uri_parts['user'])) {
+ $user = $uri_parts['user'];
+ }
+ if (!empty($uri_parts['pass'])) {
+ $pass = $uri_parts['pass'];
+ }
+ // loop through parameters, setup headers
+ foreach ($params as $param_key => $param_value) {
+ switch ($param_key) {
+ case 'file':
+ case 'assign':
+ case 'assign_headers':
+ break;
+ case 'user':
+ if (!empty($param_value)) {
+ $user = $param_value;
+ }
+ break;
+ case 'pass':
+ if (!empty($param_value)) {
+ $pass = $param_value;
+ }
+ break;
+ case 'accept':
+ if (!empty($param_value)) {
+ $accept = $param_value;
+ }
+ break;
+ case 'header':
+ if (!empty($param_value)) {
+ if (!preg_match('![\w\d-]+: .+!', $param_value)) {
+ trigger_error("[plugin] invalid header format '{$param_value}'", E_USER_NOTICE);
+ return;
+ } else {
+ $extra_headers[] = $param_value;
+ }
+ }
+ break;
+ case 'proxy_host':
+ if (!empty($param_value)) {
+ $proxy_host = $param_value;
+ }
+ break;
+ case 'proxy_port':
+ if (!preg_match('!\D!', $param_value)) {
+ $proxy_port = (int)$param_value;
+ } else {
+ trigger_error("[plugin] invalid value for attribute '{$param_key }'", E_USER_NOTICE);
+ return;
+ }
+ break;
+ case 'agent':
+ if (!empty($param_value)) {
+ $agent = $param_value;
+ }
+ break;
+ case 'referer':
+ if (!empty($param_value)) {
+ $referer = $param_value;
+ }
+ break;
+ case 'timeout':
+ if (!preg_match('!\D!', $param_value)) {
+ $timeout = (int)$param_value;
+ } else {
+ trigger_error("[plugin] invalid value for attribute '{$param_key}'", E_USER_NOTICE);
+ return;
+ }
+ break;
+ default:
+ trigger_error("[plugin] unrecognized attribute '{$param_key}'", E_USER_NOTICE);
+ return;
+ }
+ }
+ if (!empty($proxy_host) && !empty($proxy_port)) {
+ $_is_proxy = true;
+ $fp = fsockopen($proxy_host, $proxy_port, $errno, $errstr, $timeout);
+ } else {
+ $fp = fsockopen($server_name, $port, $errno, $errstr, $timeout);
+ }
+ if (!$fp) {
+ trigger_error("[plugin] unable to fetch: $errstr ($errno)", E_USER_NOTICE);
+ return;
+ } else {
+ if ($_is_proxy) {
+ fputs($fp, 'GET ' . $params['file'] . " HTTP/1.0\r\n");
+ } else {
+ fputs($fp, "GET $uri HTTP/1.0\r\n");
+ }
+ if (!empty($host)) {
+ fputs($fp, "Host: $host\r\n");
+ }
+ if (!empty($accept)) {
+ fputs($fp, "Accept: $accept\r\n");
+ }
+ if (!empty($agent)) {
+ fputs($fp, "User-Agent: $agent\r\n");
+ }
+ if (!empty($referer)) {
+ fputs($fp, "Referer: $referer\r\n");
+ }
+ if (isset($extra_headers) && is_array($extra_headers)) {
+ foreach ($extra_headers as $curr_header) {
+ fputs($fp, $curr_header . "\r\n");
+ }
+ }
+ if (!empty($user) && !empty($pass)) {
+ fputs($fp, 'Authorization: BASIC ' . base64_encode("$user:$pass") . "\r\n");
+ }
+ fputs($fp, "\r\n");
+ while (!feof($fp)) {
+ $content .= fgets($fp, 4096);
+ }
+ fclose($fp);
+ $csplit = preg_split("!\r\n\r\n!", $content, 2);
+ $content = $csplit[1];
+ if (!empty($params['assign_headers'])) {
+ $template->assign($params['assign_headers'], preg_split("!\r\n!", $csplit[0]));
+ }
+ }
+ } else {
+ trigger_error("[plugin fetch] unable to parse URL, check syntax", E_USER_NOTICE);
+ return;
+ }
+ } else {
+ $content = @file_get_contents($params['file']);
+ if ($content === false) {
+ throw new Exception("{fetch} cannot read resource '" . $params['file'] . "'");
+ }
+ }
+ if (!empty($params['assign'])) {
+ $template->assign($params['assign'], $content);
+ } else {
+ return $content;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/FunctionHandler/FunctionHandlerInterface.php b/src/FunctionHandler/FunctionHandlerInterface.php
new file mode 100644
index 00000000..ce77e13d
--- /dev/null
+++ b/src/FunctionHandler/FunctionHandlerInterface.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+interface FunctionHandlerInterface {
+ public function handle($params, Template $template);
+ public function isCacheable(): bool;
+} \ No newline at end of file
diff --git a/src/FunctionHandler/HtmlBase.php b/src/FunctionHandler/HtmlBase.php
new file mode 100644
index 00000000..99f8e6c7
--- /dev/null
+++ b/src/FunctionHandler/HtmlBase.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+class HtmlBase extends Base {
+
+ /**
+ * @param $inputType
+ * @param $name
+ * @param $value
+ * @param $output
+ * @param $ismultiselect
+ * @param $selected
+ * @param $extra
+ * @param $separator
+ * @param $labels
+ * @param $label_ids
+ * @param bool $escape
+ *
+ * @return string
+ */
+ protected function getHtmlForInput(
+ $inputType,
+ $name,
+ $value,
+ $output,
+ $ismultiselect,
+ $selected,
+ $extra,
+ $separator,
+ $labels,
+ $label_ids,
+ $escape = true
+ ) {
+
+ $_output = '';
+ if (is_object($value)) {
+ if (method_exists($value, '__toString')) {
+ $value = (string)$value->__toString();
+ } else {
+ trigger_error(
+ 'value is an object of class \'' . get_class($value) .
+ '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ return '';
+ }
+ } else {
+ $value = (string)$value;
+ }
+ if (is_object($output)) {
+ if (method_exists($output, '__toString')) {
+ $output = (string)$output->__toString();
+ } else {
+ trigger_error(
+ 'output is an object of class \'' . get_class($output) .
+ '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ return '';
+ }
+ } else {
+ $output = (string)$output;
+ }
+ if ($labels) {
+ if ($label_ids) {
+ $_id = smarty_function_escape_special_chars(
+ preg_replace(
+ '![^\w\-\.]!' . \Smarty\Smarty::$_UTF8_MODIFIER,
+ '_',
+ $name . '_' . $value
+ )
+ );
+ $_output .= '<label for="' . $_id . '">';
+ } else {
+ $_output .= '<label>';
+ }
+ }
+ $name = smarty_function_escape_special_chars($name);
+ $value = smarty_function_escape_special_chars($value);
+ if ($escape) {
+ $output = smarty_function_escape_special_chars($output);
+ }
+ $_output .= '<input type="' . $inputType . '" name="' . $name;
+ if ($ismultiselect) {
+ $_output .= '[]';
+ }
+ $_output .= '" value="' . $value . '"';
+ if ($labels && $label_ids) {
+ $_output .= ' id="' . $_id . '"';
+ }
+ if ($ismultiselect && is_array($selected)) {
+ if (isset($selected[ $value ])) {
+ $_output .= ' checked="checked"';
+ }
+ } elseif ($value === $selected) {
+ $_output .= ' checked="checked"';
+ }
+ $_output .= $extra . ' />' . $output;
+ if ($labels) {
+ $_output .= '</label>';
+ }
+ $_output .= $separator;
+ return $_output;
+ }
+
+} \ No newline at end of file
diff --git a/src/FunctionHandler/HtmlCheckboxes.php b/src/FunctionHandler/HtmlCheckboxes.php
new file mode 100644
index 00000000..a32b48b2
--- /dev/null
+++ b/src/FunctionHandler/HtmlCheckboxes.php
@@ -0,0 +1,191 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {html_checkboxes} function plugin
+ * File: HtmlCheckboxes.php
+ * Type: function
+ * Name: html_checkboxes
+ * Date: 24.Feb.2003
+ * Purpose: Prints out a list of checkbox input types
+ * Examples:
+ *
+ * {html_checkboxes values=$ids output=$names}
+ * {html_checkboxes values=$ids name='box' separator='<br>' output=$names}
+ * {html_checkboxes values=$ids checked=$checked separator='<br>' output=$names}
+ *
+ * Params:
+ *
+ * - name (optional) - string default "checkbox"
+ * - values (required) - array
+ * - options (optional) - associative array
+ * - checked (optional) - array default not set
+ * - separator (optional) - ie <br> or &nbsp;
+ * - output (optional) - the output next to each checkbox
+ * - assign (optional) - assign the output as an array to this variable
+ * - escape (optional) - escape the content (not value), defaults to true
+ *
+ * @link https://www.smarty.net/manual/en/language.function.html.checkboxes.php {html_checkboxes}
+ * (Smarty online manual)
+ * @author Christopher Kvarme <christopher.kvarme@flashjab.com>
+ * @author credits to Monte Ohrt <monte at ohrt dot com>
+ * @version 1.0
+ *
+ * @param array $params parameters
+ * @param Template $template template object
+ *
+ * @return string
+ * @uses smarty_function_escape_special_chars()
+ * @throws \Smarty\Exception
+ */
+class HtmlCheckboxes extends HtmlBase {
+
+ public function handle($params, Template $template) {
+ $name = 'checkbox';
+ $values = null;
+ $options = null;
+ $selected = [];
+ $separator = '';
+ $escape = true;
+ $labels = true;
+ $label_ids = false;
+ $output = null;
+ $extra = '';
+ foreach ($params as $_key => $_val) {
+ switch ($_key) {
+ case 'name':
+ case 'separator':
+ $$_key = (string)$_val;
+ break;
+ case 'escape':
+ case 'labels':
+ case 'label_ids':
+ $$_key = (bool)$_val;
+ break;
+ case 'options':
+ $$_key = (array)$_val;
+ break;
+ case 'values':
+ case 'output':
+ $$_key = array_values((array)$_val);
+ break;
+ case 'checked':
+ case 'selected':
+ if (is_array($_val)) {
+ $selected = [];
+ foreach ($_val as $_sel) {
+ if (is_object($_sel)) {
+ if (method_exists($_sel, '__toString')) {
+ $_sel = smarty_function_escape_special_chars((string)$_sel->__toString());
+ } else {
+ trigger_error(
+ 'html_checkboxes: selected attribute contains an object of class \'' .
+ get_class($_sel) . '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ continue;
+ }
+ } else {
+ $_sel = smarty_function_escape_special_chars((string)$_sel);
+ }
+ $selected[$_sel] = true;
+ }
+ } elseif (is_object($_val)) {
+ if (method_exists($_val, '__toString')) {
+ $selected = smarty_function_escape_special_chars((string)$_val->__toString());
+ } else {
+ trigger_error(
+ 'html_checkboxes: selected attribute is an object of class \'' . get_class($_val) .
+ '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ }
+ } else {
+ $selected = smarty_function_escape_special_chars((string)$_val);
+ }
+ break;
+ case 'checkboxes':
+ trigger_error(
+ 'html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead',
+ E_USER_WARNING
+ );
+ $options = (array)$_val;
+ break;
+ case 'strict':
+ case 'assign':
+ break;
+ case 'disabled':
+ case 'readonly':
+ if (!empty($params['strict'])) {
+ if (!is_scalar($_val)) {
+ trigger_error(
+ "html_options: {$_key} attribute must be a scalar, only boolean true or string '{$_key}' will actually add the attribute",
+ E_USER_NOTICE
+ );
+ }
+ if ($_val === true || $_val === $_key) {
+ $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_key) . '"';
+ }
+ break;
+ }
+ // omit break; to fall through!
+ // no break
+ default:
+ if (!is_array($_val)) {
+ $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"';
+ } else {
+ trigger_error("html_checkboxes: extra attribute '{$_key}' cannot be an array", E_USER_NOTICE);
+ }
+ break;
+ }
+ }
+ if (!isset($options) && !isset($values)) {
+ return '';
+ } /* raise error here? */
+ $_html_result = [];
+ if (isset($options)) {
+ foreach ($options as $_key => $_val) {
+ $_html_result[] =
+ $this->getHtmlForInput(
+ 'checkbox',
+ $name,
+ $_key,
+ $_val,
+ true,
+ $selected,
+ $extra,
+ $separator,
+ $labels,
+ $label_ids,
+ $escape
+ );
+ }
+ } else {
+ foreach ($values as $_i => $_key) {
+ $_val = isset($output[$_i]) ? $output[$_i] : '';
+ $_html_result[] =
+ $this->getHtmlForInput(
+ 'checkbox',
+ $name,
+ $_key,
+ $_val,
+ true,
+ $selected,
+ $extra,
+ $separator,
+ $labels,
+ $label_ids,
+ $escape
+ );
+ }
+ }
+ if (!empty($params['assign'])) {
+ $template->assign($params['assign'], $_html_result);
+ } else {
+ return implode("\n", $_html_result);
+ }
+ }
+
+}
diff --git a/src/FunctionHandler/HtmlImage.php b/src/FunctionHandler/HtmlImage.php
new file mode 100644
index 00000000..a524eef9
--- /dev/null
+++ b/src/FunctionHandler/HtmlImage.php
@@ -0,0 +1,151 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * Smarty {html_image} function plugin
+ * Type: function
+ * Name: html_image
+ * Date: Feb 24, 2003
+ * Purpose: format HTML tags for the image
+ * Examples: {html_image file="/images/masthead.gif"}
+ * Output: <img src="/images/masthead.gif" width=400 height=23>
+ * Params:
+ *
+ * - file - (required) - file (and path) of image
+ * - height - (optional) - image height (default actual height)
+ * - width - (optional) - image width (default actual width)
+ * - basedir - (optional) - base directory for absolute paths, default is environment variable DOCUMENT_ROOT
+ * - path_prefix - prefix for path output (optional, default empty)
+ *
+ * @link https://www.smarty.net/manual/en/language.function.html.image.php {html_image}
+ * (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author credits to Duda <duda@big.hu>
+ * @version 1.0
+ *
+ * @param array $params parameters
+ * @param Template $template template object
+ *
+ * @throws Exception
+ * @return string
+ * @uses smarty_function_escape_special_chars()
+ */
+class HtmlImage extends Base {
+
+ public function handle($params, Template $template) {
+ $alt = '';
+ $file = '';
+ $height = '';
+ $width = '';
+ $extra = '';
+ $prefix = '';
+ $suffix = '';
+ $path_prefix = '';
+ $basedir = $_SERVER['DOCUMENT_ROOT'] ?? '';
+ foreach ($params as $_key => $_val) {
+ switch ($_key) {
+ case 'file':
+ case 'height':
+ case 'width':
+ case 'dpi':
+ case 'path_prefix':
+ case 'basedir':
+ $$_key = $_val;
+ break;
+ case 'alt':
+ if (!is_array($_val)) {
+ $$_key = smarty_function_escape_special_chars($_val);
+ } else {
+ throw new Exception(
+ "html_image: extra attribute '{$_key}' cannot be an array",
+ E_USER_NOTICE
+ );
+ }
+ break;
+ case 'link':
+ case 'href':
+ $prefix = '<a href="' . $_val . '">';
+ $suffix = '</a>';
+ break;
+ default:
+ if (!is_array($_val)) {
+ $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"';
+ } else {
+ throw new Exception(
+ "html_image: extra attribute '{$_key}' cannot be an array",
+ E_USER_NOTICE
+ );
+ }
+ break;
+ }
+ }
+ if (empty($file)) {
+ trigger_error('html_image: missing \'file\' parameter', E_USER_NOTICE);
+ return;
+ }
+ if ($file[0] === '/') {
+ $_image_path = $basedir . $file;
+ } else {
+ $_image_path = $file;
+ }
+ // strip file protocol
+ if (stripos($params['file'], 'file://') === 0) {
+ $params['file'] = substr($params['file'], 7);
+ }
+ $protocol = strpos($params['file'], '://');
+ if ($protocol !== false) {
+ $protocol = strtolower(substr($params['file'], 0, $protocol));
+ }
+ if (isset($template->getSmarty()->security_policy)) {
+ if ($protocol) {
+ // remote resource (or php stream, …)
+ if (!$template->getSmarty()->security_policy->isTrustedUri($params['file'])) {
+ return;
+ }
+ } else {
+ // local file
+ if (!$template->getSmarty()->security_policy->isTrustedResourceDir($_image_path)) {
+ return;
+ }
+ }
+ }
+ if (!isset($params['width']) || !isset($params['height'])) {
+ // FIXME: (rodneyrehm) getimagesize() loads the complete file off a remote resource, use custom [jpg,png,gif]header reader!
+ if (!$_image_data = @getimagesize($_image_path)) {
+ if (!file_exists($_image_path)) {
+ trigger_error("html_image: unable to find '{$_image_path}'", E_USER_NOTICE);
+ return;
+ } elseif (!is_readable($_image_path)) {
+ trigger_error("html_image: unable to read '{$_image_path}'", E_USER_NOTICE);
+ return;
+ } else {
+ trigger_error("html_image: '{$_image_path}' is not a valid image file", E_USER_NOTICE);
+ return;
+ }
+ }
+ if (!isset($params['width'])) {
+ $width = $_image_data[0];
+ }
+ if (!isset($params['height'])) {
+ $height = $_image_data[1];
+ }
+ }
+ if (isset($params['dpi'])) {
+ if (strstr($_SERVER['HTTP_USER_AGENT'], 'Mac')) {
+ // FIXME: (rodneyrehm) wrong dpi assumption
+ // don't know who thought this up… even if it was true in 1998, it's definitely wrong in 2011.
+ $dpi_default = 72;
+ } else {
+ $dpi_default = 96;
+ }
+ $_resize = $dpi_default / $params['dpi'];
+ $width = round($width * $_resize);
+ $height = round($height * $_resize);
+ }
+ return $prefix . '<img src="' . $path_prefix . $file . '" alt="' . $alt . '" width="' . $width . '" height="' .
+ $height . '"' . $extra . ' />' . $suffix;
+ }
+} \ No newline at end of file
diff --git a/src/FunctionHandler/HtmlOptions.php b/src/FunctionHandler/HtmlOptions.php
new file mode 100644
index 00000000..c008766d
--- /dev/null
+++ b/src/FunctionHandler/HtmlOptions.php
@@ -0,0 +1,225 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {html_options} function plugin
+ * Type: function
+ * Name: html_options
+ * Purpose: Prints the list of <option> tags generated from
+ * the passed parameters
+ * Params:
+ *
+ * - name (optional) - string default "select"
+ * - values (required) - if no options supplied) - array
+ * - options (required) - if no values supplied) - associative array
+ * - selected (optional) - string default not set
+ * - output (required) - if not options supplied) - array
+ * - id (optional) - string default not set
+ * - class (optional) - string default not set
+ *
+ * @link https://www.smarty.net/manual/en/language.function.html.options.php {html_image}
+ * (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author Ralf Strehle (minor optimization) <ralf dot strehle at yahoo dot de>
+ *
+ * @param array $params parameters
+ *
+ * @param \Smarty\Template $template
+ *
+ * @return string
+ * @uses smarty_function_escape_special_chars()
+ * @throws \Smarty\Exception
+ */
+class HtmlOptions extends Base {
+
+ public function handle($params, Template $template) {
+ $name = null;
+ $values = null;
+ $options = null;
+ $selected = null;
+ $output = null;
+ $id = null;
+ $class = null;
+ $extra = '';
+ foreach ($params as $_key => $_val) {
+ switch ($_key) {
+ case 'name':
+ case 'class':
+ case 'id':
+ $$_key = (string)$_val;
+ break;
+ case 'options':
+ $options = (array)$_val;
+ break;
+ case 'values':
+ case 'output':
+ $$_key = array_values((array)$_val);
+ break;
+ case 'selected':
+ if (is_array($_val)) {
+ $selected = [];
+ foreach ($_val as $_sel) {
+ if (is_object($_sel)) {
+ if (method_exists($_sel, '__toString')) {
+ $_sel = smarty_function_escape_special_chars((string)$_sel->__toString());
+ } else {
+ trigger_error(
+ 'html_options: selected attribute contains an object of class \'' .
+ get_class($_sel) . '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ continue;
+ }
+ } else {
+ $_sel = smarty_function_escape_special_chars((string)$_sel);
+ }
+ $selected[$_sel] = true;
+ }
+ } elseif (is_object($_val)) {
+ if (method_exists($_val, '__toString')) {
+ $selected = smarty_function_escape_special_chars((string)$_val->__toString());
+ } else {
+ trigger_error(
+ 'html_options: selected attribute is an object of class \'' . get_class($_val) .
+ '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ }
+ } else {
+ $selected = smarty_function_escape_special_chars((string)$_val);
+ }
+ break;
+ case 'strict':
+ break;
+ case 'disabled':
+ case 'readonly':
+ if (!empty($params['strict'])) {
+ if (!is_scalar($_val)) {
+ trigger_error(
+ "html_options: {$_key} attribute must be a scalar, only boolean true or string '{$_key}' will actually add the attribute",
+ E_USER_NOTICE
+ );
+ }
+ if ($_val === true || $_val === $_key) {
+ $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_key) . '"';
+ }
+ break;
+ }
+ // omit break; to fall through!
+ // no break
+ default:
+ if (!is_array($_val)) {
+ $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"';
+ } else {
+ trigger_error("html_options: extra attribute '{$_key}' cannot be an array", E_USER_NOTICE);
+ }
+ break;
+ }
+ }
+ if (!isset($options) && !isset($values)) {
+ /* raise error here? */
+ return '';
+ }
+ $_html_result = '';
+ $_idx = 0;
+ if (isset($options)) {
+ foreach ($options as $_key => $_val) {
+ $_html_result .= $this->output($_key, $_val, $selected, $id, $class, $_idx);
+ }
+ } else {
+ foreach ($values as $_i => $_key) {
+ $_val = $output[$_i] ?? '';
+ $_html_result .= $this->output($_key, $_val, $selected, $id, $class, $_idx);
+ }
+ }
+ if (!empty($name)) {
+ $_html_class = !empty($class) ? ' class="' . $class . '"' : '';
+ $_html_id = !empty($id) ? ' id="' . $id . '"' : '';
+ $_html_result =
+ '<select name="' . $name . '"' . $_html_class . $_html_id . $extra . '>' . "\n" . $_html_result .
+ '</select>' . "\n";
+ }
+ return $_html_result;
+ }
+
+
+ /**
+ * @param $key
+ * @param $value
+ * @param $selected
+ * @param $id
+ * @param $class
+ * @param $idx
+ *
+ * @return string
+ */
+ private function output($key, $value, $selected, $id, $class, &$idx)
+ {
+ if (!is_array($value)) {
+ $_key = smarty_function_escape_special_chars($key);
+ $_html_result = '<option value="' . $_key . '"';
+ if (is_array($selected)) {
+ if (isset($selected[ $_key ])) {
+ $_html_result .= ' selected="selected"';
+ }
+ } elseif ($_key === $selected) {
+ $_html_result .= ' selected="selected"';
+ }
+ $_html_class = !empty($class) ? ' class="' . $class . ' option"' : '';
+ $_html_id = !empty($id) ? ' id="' . $id . '-' . $idx . '"' : '';
+ if (is_object($value)) {
+ if (method_exists($value, '__toString')) {
+ $value = smarty_function_escape_special_chars((string)$value->__toString());
+ } else {
+ trigger_error(
+ 'html_options: value is an object of class \'' . get_class($value) .
+ '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ return '';
+ }
+ } else {
+ $value = smarty_function_escape_special_chars((string)$value);
+ }
+ $_html_result .= $_html_class . $_html_id . '>' . $value . '</option>' . "\n";
+ $idx++;
+ } else {
+ $_idx = 0;
+ $_html_result =
+ $this->getHtmlForOptGroup(
+ $key,
+ $value,
+ $selected,
+ !empty($id) ? ($id . '-' . $idx) : null,
+ $class,
+ $_idx
+ );
+ $idx++;
+ }
+ return $_html_result;
+ }
+
+ /**
+ * @param $key
+ * @param $values
+ * @param $selected
+ * @param $id
+ * @param $class
+ * @param $idx
+ *
+ * @return string
+ */
+ private function getHtmlForOptGroup($key, $values, $selected, $id, $class, &$idx)
+ {
+ $optgroup_html = '<optgroup label="' . smarty_function_escape_special_chars($key) . '">' . "\n";
+ foreach ($values as $key => $value) {
+ $optgroup_html .= $this->output($key, $value, $selected, $id, $class, $idx);
+ }
+ $optgroup_html .= "</optgroup>\n";
+ return $optgroup_html;
+ }
+
+}
+
diff --git a/src/FunctionHandler/HtmlRadios.php b/src/FunctionHandler/HtmlRadios.php
new file mode 100644
index 00000000..0cc95609
--- /dev/null
+++ b/src/FunctionHandler/HtmlRadios.php
@@ -0,0 +1,176 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {html_radios} function plugin
+ * File: HtmlRadios.php
+ * Type: function
+ * Name: html_radios
+ * Date: 24.Feb.2003
+ * Purpose: Prints out a list of radio input types
+ * Params:
+ *
+ * - name (optional) - string default "radio"
+ * - values (required) - array
+ * - options (required) - associative array
+ * - checked (optional) - array default not set
+ * - separator (optional) - ie <br> or &nbsp;
+ * - output (optional) - the output next to each radio button
+ * - assign (optional) - assign the output as an array to this variable
+ * - escape (optional) - escape the content (not value), defaults to true
+ *
+ * Examples:
+ *
+ * {html_radios values=$ids output=$names}
+ * {html_radios values=$ids name='box' separator='<br>' output=$names}
+ * {html_radios values=$ids checked=$checked separator='<br>' output=$names}
+ *
+ * @link https://www.smarty.net/manual/en/language.function.html.radios.php {html_radios}
+ * (Smarty online manual)
+ * @author Christopher Kvarme <christopher.kvarme@flashjab.com>
+ * @author credits to Monte Ohrt <monte at ohrt dot com>
+ * @version 1.0
+ *
+ * @param array $params parameters
+ * @param Template $template template object
+ *
+ * @return string
+ * @uses smarty_function_escape_special_chars()
+ * @throws \Smarty\Exception
+ */
+class HtmlRadios extends HtmlBase {
+
+ public function handle($params, Template $template) {
+ $name = 'radio';
+ $values = null;
+ $options = null;
+ $selected = null;
+ $separator = '';
+ $escape = true;
+ $labels = true;
+ $label_ids = false;
+ $output = null;
+ $extra = '';
+ foreach ($params as $_key => $_val) {
+ switch ($_key) {
+ case 'name':
+ case 'separator':
+ $$_key = (string)$_val;
+ break;
+ case 'checked':
+ case 'selected':
+ if (is_array($_val)) {
+ trigger_error('html_radios: the "' . $_key . '" attribute cannot be an array', E_USER_WARNING);
+ } elseif (is_object($_val)) {
+ if (method_exists($_val, '__toString')) {
+ $selected = smarty_function_escape_special_chars((string)$_val->__toString());
+ } else {
+ trigger_error(
+ 'html_radios: selected attribute is an object of class \'' . get_class($_val) .
+ '\' without __toString() method',
+ E_USER_NOTICE
+ );
+ }
+ } else {
+ $selected = (string)$_val;
+ }
+ break;
+ case 'escape':
+ case 'labels':
+ case 'label_ids':
+ $$_key = (bool)$_val;
+ break;
+ case 'options':
+ $$_key = (array)$_val;
+ break;
+ case 'values':
+ case 'output':
+ $$_key = array_values((array)$_val);
+ break;
+ case 'radios':
+ trigger_error(
+ 'html_radios: the use of the "radios" attribute is deprecated, use "options" instead',
+ E_USER_WARNING
+ );
+ $options = (array)$_val;
+ break;
+ case 'strict':
+ case 'assign':
+ break;
+ case 'disabled':
+ case 'readonly':
+ if (!empty($params['strict'])) {
+ if (!is_scalar($_val)) {
+ trigger_error(
+ "html_options: {$_key} attribute must be a scalar, only boolean true or string '$_key' will actually add the attribute",
+ E_USER_NOTICE
+ );
+ }
+ if ($_val === true || $_val === $_key) {
+ $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_key) . '"';
+ }
+ break;
+ }
+ // omit break; to fall through!
+ // no break
+ default:
+ if (!is_array($_val)) {
+ $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"';
+ } else {
+ trigger_error("html_radios: extra attribute '{$_key}' cannot be an array", E_USER_NOTICE);
+ }
+ break;
+ }
+ }
+ if (!isset($options) && !isset($values)) {
+ /* raise error here? */
+ return '';
+ }
+ $_html_result = [];
+ if (isset($options)) {
+ foreach ($options as $_key => $_val) {
+ $_html_result[] =
+ $this->getHtmlForInput(
+ 'radio',
+ $name,
+ $_key,
+ $_val,
+ false,
+ $selected,
+ $extra,
+ $separator,
+ $labels,
+ $label_ids,
+ $escape
+ );
+ }
+ } else {
+ foreach ($values as $_i => $_key) {
+ $_val = $output[$_i] ?? '';
+ $_html_result[] =
+ $this->getHtmlForInput(
+ 'radio',
+ $name,
+ $_key,
+ $_val,
+ false,
+ $selected,
+ $extra,
+ $separator,
+ $labels,
+ $label_ids,
+ $escape
+ );
+ }
+ }
+ if (!empty($params['assign'])) {
+ $template->assign($params['assign'], $_html_result);
+ } else {
+ return implode("\n", $_html_result);
+ }
+ }
+
+
+}
diff --git a/src/FunctionHandler/HtmlSelectDate.php b/src/FunctionHandler/HtmlSelectDate.php
new file mode 100644
index 00000000..55e36641
--- /dev/null
+++ b/src/FunctionHandler/HtmlSelectDate.php
@@ -0,0 +1,383 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {html_select_date} plugin
+ * Type: function
+ * Name: html_select_date
+ * Purpose: Prints the dropdowns for date selection.
+ * ChangeLog:
+ *
+ * - 1.0 initial release
+ * - 1.1 added support for +/- N syntax for begin
+ * and end year values. (Monte)
+ * - 1.2 added support for yyyy-mm-dd syntax for
+ * time value. (Jan Rosier)
+ * - 1.3 added support for choosing format for
+ * month values (Gary Loescher)
+ * - 1.3.1 added support for choosing format for
+ * day values (Marcus Bointon)
+ * - 1.3.2 support negative timestamps, force year
+ * dropdown to include given date unless explicitly set (Monte)
+ * - 1.3.4 fix behaviour of 0000-00-00 00:00:00 dates to match that
+ * of 0000-00-00 dates (cybot, boots)
+ * - 2.0 complete rewrite for performance,
+ * added attributes month_names, *_id
+ *
+ * @link https://www.smarty.net/manual/en/language.function.html.select.date.php {html_select_date}
+ * (Smarty online manual)
+ * @version 2.0
+ * @author Andrei Zmievski
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author Rodney Rehm
+ *
+ * @param array $params parameters
+ *
+ * @param \Smarty\Template $template
+ *
+ * @return string
+ * @throws \Smarty\Exception
+ */
+class HtmlSelectDate extends Base {
+
+ public function handle($params, Template $template) {
+ // generate timestamps used for month names only
+ static $_month_timestamps = null;
+ static $_current_year = null;
+ if ($_month_timestamps === null) {
+ $_current_year = date('Y');
+ $_month_timestamps = [];
+ for ($i = 1; $i <= 12; $i++) {
+ $_month_timestamps[$i] = mktime(0, 0, 0, $i, 1, 2000);
+ }
+ }
+ /* Default values. */
+ $prefix = 'Date_';
+ $start_year = null;
+ $end_year = null;
+ $display_days = true;
+ $display_months = true;
+ $display_years = true;
+ $month_format = '%B';
+ /* Write months as numbers by default GL */
+ $month_value_format = '%m';
+ $day_format = '%02d';
+ /* Write day values using this format MB */
+ $day_value_format = '%d';
+ $year_as_text = false;
+ /* Display years in reverse order? Ie. 2000,1999,.... */
+ $reverse_years = false;
+ /* Should the select boxes be part of an array when returned from PHP?
+ e.g. setting it to "birthday", would create "birthday[Day]",
+ "birthday[Month]" & "birthday[Year]". Can be combined with prefix */
+ $field_array = null;
+ /* <select size>'s of the different <select> tags.
+ If not set, uses default dropdown. */
+ $day_size = null;
+ $month_size = null;
+ $year_size = null;
+ /* Unparsed attributes common to *ALL* the <select>/<input> tags.
+ An example might be in the template: all_extra ='class ="foo"'. */
+ $all_extra = null;
+ /* Separate attributes for the tags. */
+ $day_extra = null;
+ $month_extra = null;
+ $year_extra = null;
+ /* Order in which to display the fields.
+ "D" -> day, "M" -> month, "Y" -> year. */
+ $field_order = 'MDY';
+ /* String printed between the different fields. */
+ $field_separator = "\n";
+ $option_separator = "\n";
+ $time = null;
+
+ // $all_empty = null;
+ // $day_empty = null;
+ // $month_empty = null;
+ // $year_empty = null;
+ $extra_attrs = '';
+ $all_id = null;
+ $day_id = null;
+ $month_id = null;
+ $year_id = null;
+ foreach ($params as $_key => $_value) {
+ switch ($_key) {
+ case 'time':
+ $$_key = $_value; // we'll handle conversion below
+ break;
+ case 'month_names':
+ if (is_array($_value) && count($_value) === 12) {
+ $$_key = $_value;
+ } else {
+ trigger_error('html_select_date: month_names must be an array of 12 strings', E_USER_NOTICE);
+ }
+ break;
+ case 'prefix':
+ case 'field_array':
+ case 'start_year':
+ case 'end_year':
+ case 'day_format':
+ case 'day_value_format':
+ case 'month_format':
+ case 'month_value_format':
+ case 'day_size':
+ case 'month_size':
+ case 'year_size':
+ case 'all_extra':
+ case 'day_extra':
+ case 'month_extra':
+ case 'year_extra':
+ case 'field_order':
+ case 'field_separator':
+ case 'option_separator':
+ case 'all_empty':
+ case 'month_empty':
+ case 'day_empty':
+ case 'year_empty':
+ case 'all_id':
+ case 'month_id':
+ case 'day_id':
+ case 'year_id':
+ $$_key = (string)$_value;
+ break;
+ case 'display_days':
+ case 'display_months':
+ case 'display_years':
+ case 'year_as_text':
+ case 'reverse_years':
+ $$_key = (bool)$_value;
+ break;
+ default:
+ if (!is_array($_value)) {
+ $extra_attrs .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_value) . '"';
+ } else {
+ trigger_error("html_select_date: extra attribute '{$_key}' cannot be an array", E_USER_NOTICE);
+ }
+ break;
+ }
+ }
+ // Note: date() is faster than strftime()
+ // Note: explode(date()) is faster than date() date() date()
+
+ if (isset($time) && is_array($time)) {
+ if (isset($time[$prefix . 'Year'])) {
+ // $_REQUEST[$field_array] given
+ foreach ([
+ 'Y' => 'Year',
+ 'm' => 'Month',
+ 'd' => 'Day'
+ ] as $_elementKey => $_elementName) {
+ $_variableName = '_' . strtolower($_elementName);
+ $$_variableName =
+ $time[$prefix . $_elementName] ?? date($_elementKey);
+ }
+ } elseif (isset($time[$field_array][$prefix . 'Year'])) {
+ // $_REQUEST given
+ foreach ([
+ 'Y' => 'Year',
+ 'm' => 'Month',
+ 'd' => 'Day'
+ ] as $_elementKey => $_elementName) {
+ $_variableName = '_' . strtolower($_elementName);
+ $$_variableName = $time[$field_array][$prefix . $_elementName] ?? date($_elementKey);
+ }
+ } else {
+ // no date found, use NOW
+ [$_year, $_month, $_day] = explode('-', date('Y-m-d'));
+ }
+ } elseif (isset($time) && preg_match("/(\d*)-(\d*)-(\d*)/", $time, $matches)) {
+ $_year = $_month = $_day = null;
+ if ($matches[1] > '') {
+ $_year = (int)$matches[1];
+ }
+ if ($matches[2] > '') {
+ $_month = (int)$matches[2];
+ }
+ if ($matches[3] > '') {
+ $_day = (int)$matches[3];
+ }
+ } elseif ($time === null) {
+ if (array_key_exists('time', $params)) {
+ $_year = $_month = $_day = null;
+ } else {
+ [$_year, $_month, $_day] = explode('-', date('Y-m-d'));
+ }
+ } else {
+ $time = smarty_make_timestamp($time);
+ [$_year, $_month, $_day] = explode('-', date('Y-m-d', $time));
+ }
+
+ // make syntax "+N" or "-N" work with $start_year and $end_year
+ // Note preg_match('!^(\+|\-)\s*(\d+)$!', $end_year, $match) is slower than trim+substr
+ foreach ([
+ 'start',
+ 'end'
+ ] as $key) {
+ $key .= '_year';
+ $t = $$key;
+ if ($t === null) {
+ $$key = (int)$_current_year;
+ } elseif ($t[0] === '+') {
+ $$key = (int)($_current_year + (int)trim(substr($t, 1)));
+ } elseif ($t[0] === '-') {
+ $$key = (int)($_current_year - (int)trim(substr($t, 1)));
+ } else {
+ $$key = (int)$$key;
+ }
+ }
+ // flip for ascending or descending
+ if (($start_year > $end_year && !$reverse_years) || ($start_year < $end_year && $reverse_years)) {
+ $t = $end_year;
+ $end_year = $start_year;
+ $start_year = $t;
+ }
+ // generate year <select> or <input>
+ if ($display_years) {
+ $_extra = '';
+ $_name = $field_array ? ($field_array . '[' . $prefix . 'Year]') : ($prefix . 'Year');
+ if ($all_extra) {
+ $_extra .= ' ' . $all_extra;
+ }
+ if ($year_extra) {
+ $_extra .= ' ' . $year_extra;
+ }
+ if ($year_as_text) {
+ $_html_years =
+ '<input type="text" name="' . $_name . '" value="' . $_year . '" size="4" maxlength="4"' . $_extra .
+ $extra_attrs . ' />';
+ } else {
+ $_html_years = '<select name="' . $_name . '"';
+ if ($year_id !== null || $all_id !== null) {
+ $_html_years .= ' id="' . smarty_function_escape_special_chars(
+ $year_id !== null ?
+ ($year_id ? $year_id : $_name) :
+ ($all_id ? ($all_id . $_name) :
+ $_name)
+ ) . '"';
+ }
+ if ($year_size) {
+ $_html_years .= ' size="' . $year_size . '"';
+ }
+ $_html_years .= $_extra . $extra_attrs . '>' . $option_separator;
+ if (isset($year_empty) || isset($all_empty)) {
+ $_html_years .= '<option value="">' . (isset($year_empty) ? $year_empty : $all_empty) . '</option>' .
+ $option_separator;
+ }
+ $op = $start_year > $end_year ? -1 : 1;
+ for ($i = $start_year; $op > 0 ? $i <= $end_year : $i >= $end_year; $i += $op) {
+ $_html_years .= '<option value="' . $i . '"' . ($_year == $i ? ' selected="selected"' : '') . '>' . $i .
+ '</option>' . $option_separator;
+ }
+ $_html_years .= '</select>';
+ }
+ }
+ // generate month <select> or <input>
+ if ($display_months) {
+ $_extra = '';
+ $_name = $field_array ? ($field_array . '[' . $prefix . 'Month]') : ($prefix . 'Month');
+ if ($all_extra) {
+ $_extra .= ' ' . $all_extra;
+ }
+ if ($month_extra) {
+ $_extra .= ' ' . $month_extra;
+ }
+ $_html_months = '<select name="' . $_name . '"';
+ if ($month_id !== null || $all_id !== null) {
+ $_html_months .= ' id="' . smarty_function_escape_special_chars(
+ $month_id !== null ?
+ ($month_id ? $month_id : $_name) :
+ ($all_id ? ($all_id . $_name) :
+ $_name)
+ ) . '"';
+ }
+ if ($month_size) {
+ $_html_months .= ' size="' . $month_size . '"';
+ }
+ $_html_months .= $_extra . $extra_attrs . '>' . $option_separator;
+ if (isset($month_empty) || isset($all_empty)) {
+ $_html_months .= '<option value="">' . (isset($month_empty) ? $month_empty : $all_empty) . '</option>' .
+ $option_separator;
+ }
+ for ($i = 1; $i <= 12; $i++) {
+ $_val = sprintf('%02d', $i);
+ $_text = isset($month_names) ? smarty_function_escape_special_chars($month_names[$i]) :
+ ($month_format === '%m' ? $_val : @strftime($month_format, $_month_timestamps[$i]));
+ $_value = $month_value_format === '%m' ? $_val : @strftime($month_value_format, $_month_timestamps[$i]);
+ $_html_months .= '<option value="' . $_value . '"' . ($_val == $_month ? ' selected="selected"' : '') .
+ '>' . $_text . '</option>' . $option_separator;
+ }
+ $_html_months .= '</select>';
+ }
+ // generate day <select> or <input>
+ if ($display_days) {
+ $_extra = '';
+ $_name = $field_array ? ($field_array . '[' . $prefix . 'Day]') : ($prefix . 'Day');
+ if ($all_extra) {
+ $_extra .= ' ' . $all_extra;
+ }
+ if ($day_extra) {
+ $_extra .= ' ' . $day_extra;
+ }
+ $_html_days = '<select name="' . $_name . '"';
+ if ($day_id !== null || $all_id !== null) {
+ $_html_days .= ' id="' .
+ smarty_function_escape_special_chars(
+ $day_id !== null ? ($day_id ? $day_id : $_name) :
+ ($all_id ? ($all_id . $_name) : $_name)
+ ) . '"';
+ }
+ if ($day_size) {
+ $_html_days .= ' size="' . $day_size . '"';
+ }
+ $_html_days .= $_extra . $extra_attrs . '>' . $option_separator;
+ if (isset($day_empty) || isset($all_empty)) {
+ $_html_days .= '<option value="">' . (isset($day_empty) ? $day_empty : $all_empty) . '</option>' .
+ $option_separator;
+ }
+ for ($i = 1; $i <= 31; $i++) {
+ $_val = sprintf('%02d', $i);
+ $_text = $day_format === '%02d' ? $_val : sprintf($day_format, $i);
+ $_value = $day_value_format === '%02d' ? $_val : sprintf($day_value_format, $i);
+ $_html_days .= '<option value="' . $_value . '"' . ($_val == $_day ? ' selected="selected"' : '') . '>' .
+ $_text . '</option>' . $option_separator;
+ }
+ $_html_days .= '</select>';
+ }
+ // order the fields for output
+ $_html = '';
+ for ($i = 0; $i <= 2; $i++) {
+ switch ($field_order[$i]) {
+ case 'Y':
+ case 'y':
+ if (isset($_html_years)) {
+ if ($_html) {
+ $_html .= $field_separator;
+ }
+ $_html .= $_html_years;
+ }
+ break;
+ case 'm':
+ case 'M':
+ if (isset($_html_months)) {
+ if ($_html) {
+ $_html .= $field_separator;
+ }
+ $_html .= $_html_months;
+ }
+ break;
+ case 'd':
+ case 'D':
+ if (isset($_html_days)) {
+ if ($_html) {
+ $_html .= $field_separator;
+ }
+ $_html .= $_html_days;
+ }
+ break;
+ }
+ }
+ return $_html;
+ }
+} \ No newline at end of file
diff --git a/src/FunctionHandler/HtmlSelectTime.php b/src/FunctionHandler/HtmlSelectTime.php
new file mode 100644
index 00000000..6e4f679d
--- /dev/null
+++ b/src/FunctionHandler/HtmlSelectTime.php
@@ -0,0 +1,336 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {html_select_time} function plugin
+ * Type: function
+ * Name: html_select_time
+ * Purpose: Prints the dropdowns for time selection
+ *
+ * @link https://www.smarty.net/manual/en/language.function.html.select.time.php {html_select_time}
+ * (Smarty online manual)
+ * @author Roberto Berto <roberto@berto.net>
+ * @author Monte Ohrt <monte AT ohrt DOT com>
+ *
+ * @param array $params parameters
+ *
+ * @param \Smarty\Template $template
+ *
+ * @return string
+ * @uses smarty_make_timestamp()
+ * @throws \Smarty\Exception
+ */
+class HtmlSelectTime extends Base {
+
+ public function handle($params, Template $template) {
+ $prefix = 'Time_';
+ $field_array = null;
+ $field_separator = "\n";
+ $option_separator = "\n";
+ $time = null;
+ $display_hours = true;
+ $display_minutes = true;
+ $display_seconds = true;
+ $display_meridian = true;
+ $hour_format = '%02d';
+ $hour_value_format = '%02d';
+ $minute_format = '%02d';
+ $minute_value_format = '%02d';
+ $second_format = '%02d';
+ $second_value_format = '%02d';
+ $hour_size = null;
+ $minute_size = null;
+ $second_size = null;
+ $meridian_size = null;
+ $all_empty = null;
+ $hour_empty = null;
+ $minute_empty = null;
+ $second_empty = null;
+ $meridian_empty = null;
+ $all_id = null;
+ $hour_id = null;
+ $minute_id = null;
+ $second_id = null;
+ $meridian_id = null;
+ $use_24_hours = true;
+ $minute_interval = 1;
+ $second_interval = 1;
+ $extra_attrs = '';
+ $all_extra = null;
+ $hour_extra = null;
+ $minute_extra = null;
+ $second_extra = null;
+ $meridian_extra = null;
+ foreach ($params as $_key => $_value) {
+ switch ($_key) {
+ case 'time':
+ if (!is_array($_value) && $_value !== null) {
+ $time = smarty_make_timestamp($_value);
+ }
+ break;
+ case 'prefix':
+ case 'field_array':
+ case 'field_separator':
+ case 'option_separator':
+ case 'all_extra':
+ case 'hour_extra':
+ case 'minute_extra':
+ case 'second_extra':
+ case 'meridian_extra':
+ case 'all_empty':
+ case 'hour_empty':
+ case 'minute_empty':
+ case 'second_empty':
+ case 'meridian_empty':
+ case 'all_id':
+ case 'hour_id':
+ case 'minute_id':
+ case 'second_id':
+ case 'meridian_id':
+ case 'hour_format':
+ case 'hour_value_format':
+ case 'minute_format':
+ case 'minute_value_format':
+ case 'second_format':
+ case 'second_value_format':
+ $$_key = (string)$_value;
+ break;
+ case 'display_hours':
+ case 'display_minutes':
+ case 'display_seconds':
+ case 'display_meridian':
+ case 'use_24_hours':
+ $$_key = (bool)$_value;
+ break;
+ case 'minute_interval':
+ case 'second_interval':
+ case 'hour_size':
+ case 'minute_size':
+ case 'second_size':
+ case 'meridian_size':
+ $$_key = (int)$_value;
+ break;
+ default:
+ if (!is_array($_value)) {
+ $extra_attrs .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_value) . '"';
+ } else {
+ trigger_error("html_select_date: extra attribute '{$_key}' cannot be an array", E_USER_NOTICE);
+ }
+ break;
+ }
+ }
+ if (isset($params['time']) && is_array($params['time'])) {
+ if (isset($params['time'][$prefix . 'Hour'])) {
+ // $_REQUEST[$field_array] given
+ foreach ([
+ 'H' => 'Hour',
+ 'i' => 'Minute',
+ 's' => 'Second'
+ ] as $_elementKey => $_elementName) {
+ $_variableName = '_' . strtolower($_elementName);
+ $$_variableName =
+ $params['time'][$prefix . $_elementName] ?? date($_elementKey);
+ }
+ $_meridian =
+ isset($params['time'][$prefix . 'Meridian']) ? (' ' . $params['time'][$prefix . 'Meridian']) :
+ '';
+ $time = strtotime($_hour . ':' . $_minute . ':' . $_second . $_meridian);
+ [$_hour, $_minute, $_second] = $time = explode('-', date('H-i-s', $time));
+ } elseif (isset($params['time'][$field_array][$prefix . 'Hour'])) {
+ // $_REQUEST given
+ foreach ([
+ 'H' => 'Hour',
+ 'i' => 'Minute',
+ 's' => 'Second'
+ ] as $_elementKey => $_elementName) {
+ $_variableName = '_' . strtolower($_elementName);
+ $$_variableName = $params['time'][$field_array][$prefix . $_elementName] ?? date($_elementKey);
+ }
+ $_meridian = isset($params['time'][$field_array][$prefix . 'Meridian']) ?
+ (' ' . $params['time'][$field_array][$prefix . 'Meridian']) : '';
+ $time = strtotime($_hour . ':' . $_minute . ':' . $_second . $_meridian);
+ [$_hour, $_minute, $_second] = $time = explode('-', date('H-i-s', $time));
+ } else {
+ // no date found, use NOW
+ [$_year, $_month, $_day] = $time = explode('-', date('Y-m-d'));
+ }
+ } elseif ($time === null) {
+ if (array_key_exists('time', $params)) {
+ $_hour = $_minute = $_second = $time = null;
+ } else {
+ [$_hour, $_minute, $_second] = $time = explode('-', date('H-i-s'));
+ }
+ } else {
+ [$_hour, $_minute, $_second] = $time = explode('-', date('H-i-s', $time));
+ }
+ // generate hour <select>
+ if ($display_hours) {
+ $_html_hours = '';
+ $_extra = '';
+ $_name = $field_array ? ($field_array . '[' . $prefix . 'Hour]') : ($prefix . 'Hour');
+ if ($all_extra) {
+ $_extra .= ' ' . $all_extra;
+ }
+ if ($hour_extra) {
+ $_extra .= ' ' . $hour_extra;
+ }
+ $_html_hours = '<select name="' . $_name . '"';
+ if ($hour_id !== null || $all_id !== null) {
+ $_html_hours .= ' id="' .
+ smarty_function_escape_special_chars(
+ $hour_id !== null ? ($hour_id ? $hour_id : $_name) :
+ ($all_id ? ($all_id . $_name) : $_name)
+ ) . '"';
+ }
+ if ($hour_size) {
+ $_html_hours .= ' size="' . $hour_size . '"';
+ }
+ $_html_hours .= $_extra . $extra_attrs . '>' . $option_separator;
+ if (isset($hour_empty) || isset($all_empty)) {
+ $_html_hours .= '<option value="">' . (isset($hour_empty) ? $hour_empty : $all_empty) . '</option>' .
+ $option_separator;
+ }
+ $start = $use_24_hours ? 0 : 1;
+ $end = $use_24_hours ? 23 : 12;
+ for ($i = $start; $i <= $end; $i++) {
+ $_val = sprintf('%02d', $i);
+ $_text = $hour_format === '%02d' ? $_val : sprintf($hour_format, $i);
+ $_value = $hour_value_format === '%02d' ? $_val : sprintf($hour_value_format, $i);
+ if (!$use_24_hours) {
+ $_hour12 = $_hour == 0 ? 12 : ($_hour <= 12 ? $_hour : $_hour - 12);
+ }
+ $selected = $_hour !== null ? ($use_24_hours ? $_hour == $_val : $_hour12 == $_val) : null;
+ $_html_hours .= '<option value="' . $_value . '"' . ($selected ? ' selected="selected"' : '') . '>' .
+ $_text . '</option>' . $option_separator;
+ }
+ $_html_hours .= '</select>';
+ }
+ // generate minute <select>
+ if ($display_minutes) {
+ $_html_minutes = '';
+ $_extra = '';
+ $_name = $field_array ? ($field_array . '[' . $prefix . 'Minute]') : ($prefix . 'Minute');
+ if ($all_extra) {
+ $_extra .= ' ' . $all_extra;
+ }
+ if ($minute_extra) {
+ $_extra .= ' ' . $minute_extra;
+ }
+ $_html_minutes = '<select name="' . $_name . '"';
+ if ($minute_id !== null || $all_id !== null) {
+ $_html_minutes .= ' id="' . smarty_function_escape_special_chars(
+ $minute_id !== null ?
+ ($minute_id ? $minute_id : $_name) :
+ ($all_id ? ($all_id . $_name) :
+ $_name)
+ ) . '"';
+ }
+ if ($minute_size) {
+ $_html_minutes .= ' size="' . $minute_size . '"';
+ }
+ $_html_minutes .= $_extra . $extra_attrs . '>' . $option_separator;
+ if (isset($minute_empty) || isset($all_empty)) {
+ $_html_minutes .= '<option value="">' . (isset($minute_empty) ? $minute_empty : $all_empty) . '</option>' .
+ $option_separator;
+ }
+ $selected = $_minute !== null ? ($_minute - $_minute % $minute_interval) : null;
+ for ($i = 0; $i <= 59; $i += $minute_interval) {
+ $_val = sprintf('%02d', $i);
+ $_text = $minute_format === '%02d' ? $_val : sprintf($minute_format, $i);
+ $_value = $minute_value_format === '%02d' ? $_val : sprintf($minute_value_format, $i);
+ $_html_minutes .= '<option value="' . $_value . '"' . ($selected === $i ? ' selected="selected"' : '') .
+ '>' . $_text . '</option>' . $option_separator;
+ }
+ $_html_minutes .= '</select>';
+ }
+ // generate second <select>
+ if ($display_seconds) {
+ $_html_seconds = '';
+ $_extra = '';
+ $_name = $field_array ? ($field_array . '[' . $prefix . 'Second]') : ($prefix . 'Second');
+ if ($all_extra) {
+ $_extra .= ' ' . $all_extra;
+ }
+ if ($second_extra) {
+ $_extra .= ' ' . $second_extra;
+ }
+ $_html_seconds = '<select name="' . $_name . '"';
+ if ($second_id !== null || $all_id !== null) {
+ $_html_seconds .= ' id="' . smarty_function_escape_special_chars(
+ $second_id !== null ?
+ ($second_id ? $second_id : $_name) :
+ ($all_id ? ($all_id . $_name) :
+ $_name)
+ ) . '"';
+ }
+ if ($second_size) {
+ $_html_seconds .= ' size="' . $second_size . '"';
+ }
+ $_html_seconds .= $_extra . $extra_attrs . '>' . $option_separator;
+ if (isset($second_empty) || isset($all_empty)) {
+ $_html_seconds .= '<option value="">' . (isset($second_empty) ? $second_empty : $all_empty) . '</option>' .
+ $option_separator;
+ }
+ $selected = $_second !== null ? ($_second - $_second % $second_interval) : null;
+ for ($i = 0; $i <= 59; $i += $second_interval) {
+ $_val = sprintf('%02d', $i);
+ $_text = $second_format === '%02d' ? $_val : sprintf($second_format, $i);
+ $_value = $second_value_format === '%02d' ? $_val : sprintf($second_value_format, $i);
+ $_html_seconds .= '<option value="' . $_value . '"' . ($selected === $i ? ' selected="selected"' : '') .
+ '>' . $_text . '</option>' . $option_separator;
+ }
+ $_html_seconds .= '</select>';
+ }
+ // generate meridian <select>
+ if ($display_meridian && !$use_24_hours) {
+ $_html_meridian = '';
+ $_extra = '';
+ $_name = $field_array ? ($field_array . '[' . $prefix . 'Meridian]') : ($prefix . 'Meridian');
+ if ($all_extra) {
+ $_extra .= ' ' . $all_extra;
+ }
+ if ($meridian_extra) {
+ $_extra .= ' ' . $meridian_extra;
+ }
+ $_html_meridian = '<select name="' . $_name . '"';
+ if ($meridian_id !== null || $all_id !== null) {
+ $_html_meridian .= ' id="' . smarty_function_escape_special_chars(
+ $meridian_id !== null ?
+ ($meridian_id ? $meridian_id :
+ $_name) :
+ ($all_id ? ($all_id . $_name) :
+ $_name)
+ ) . '"';
+ }
+ if ($meridian_size) {
+ $_html_meridian .= ' size="' . $meridian_size . '"';
+ }
+ $_html_meridian .= $_extra . $extra_attrs . '>' . $option_separator;
+ if (isset($meridian_empty) || isset($all_empty)) {
+ $_html_meridian .= '<option value="">' . (isset($meridian_empty) ? $meridian_empty : $all_empty) .
+ '</option>' . $option_separator;
+ }
+ $_html_meridian .= '<option value="am"' . ($_hour > 0 && $_hour < 12 ? ' selected="selected"' : '') .
+ '>AM</option>' . $option_separator . '<option value="pm"' .
+ ($_hour < 12 ? '' : ' selected="selected"') . '>PM</option>' . $option_separator .
+ '</select>';
+ }
+ $_html = '';
+ foreach ([
+ '_html_hours',
+ '_html_minutes',
+ '_html_seconds',
+ '_html_meridian'
+ ] as $k) {
+ if (isset($$k)) {
+ if ($_html) {
+ $_html .= $field_separator;
+ }
+ $_html .= $$k;
+ }
+ }
+ return $_html;
+ }
+}
diff --git a/src/FunctionHandler/HtmlTable.php b/src/FunctionHandler/HtmlTable.php
new file mode 100644
index 00000000..1dadc7f1
--- /dev/null
+++ b/src/FunctionHandler/HtmlTable.php
@@ -0,0 +1,163 @@
+<?php
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {html_table} function plugin
+ * Type: function
+ * Name: html_table
+ * Date: Feb 17, 2003
+ * Purpose: make an html table from an array of data
+ * Params:
+ *
+ * - loop - array to loop through
+ * - cols - number of columns, comma separated list of column names
+ * or array of column names
+ * - rows - number of rows
+ * - table_attr - table attributes
+ * - th_attr - table heading attributes (arrays are cycled)
+ * - tr_attr - table row attributes (arrays are cycled)
+ * - td_attr - table cell attributes (arrays are cycled)
+ * - trailpad - value to pad trailing cells with
+ * - caption - text for caption element
+ * - vdir - vertical direction (default: "down", means top-to-bottom)
+ * - hdir - horizontal direction (default: "right", means left-to-right)
+ * - inner - inner loop (default "cols": print $loop line by line,
+ * $loop will be printed column by column otherwise)
+ *
+ * Examples:
+ *
+ * {table loop=$data}
+ * {table loop=$data cols=4 tr_attr='"bgcolor=red"'}
+ * {table loop=$data cols="first,second,third" tr_attr=$colors}
+ *
+ * @param array $params parameters
+ *
+ * @return string
+ *@author credit to boots <boots dot smarty at yahoo dot com>
+ * @version 1.1
+ * @link https://www.smarty.net/manual/en/language.function.html.table.php {html_table}
+ * (Smarty online manual)
+ *
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author credit to Messju Mohr <messju at lammfellpuschen dot de>
+ */
+class HtmlTable extends Base {
+
+ public function handle($params, Template $template) {
+ $table_attr = 'border="1"';
+ $tr_attr = '';
+ $th_attr = '';
+ $td_attr = '';
+ $cols = $cols_count = 3;
+ $rows = 3;
+ $trailpad = '&nbsp;';
+ $vdir = 'down';
+ $hdir = 'right';
+ $inner = 'cols';
+ $caption = '';
+ $loop = null;
+ if (!isset($params['loop'])) {
+ trigger_error("html_table: missing 'loop' parameter", E_USER_WARNING);
+ return;
+ }
+ foreach ($params as $_key => $_value) {
+ switch ($_key) {
+ case 'loop':
+ $$_key = (array)$_value;
+ break;
+ case 'cols':
+ if (is_array($_value) && !empty($_value)) {
+ $cols = $_value;
+ $cols_count = count($_value);
+ } elseif (!is_numeric($_value) && is_string($_value) && !empty($_value)) {
+ $cols = explode(',', $_value);
+ $cols_count = count($cols);
+ } elseif (!empty($_value)) {
+ $cols_count = (int)$_value;
+ } else {
+ $cols_count = $cols;
+ }
+ break;
+ case 'rows':
+ $$_key = (int)$_value;
+ break;
+ case 'table_attr':
+ case 'trailpad':
+ case 'hdir':
+ case 'vdir':
+ case 'inner':
+ case 'caption':
+ $$_key = (string)$_value;
+ break;
+ case 'tr_attr':
+ case 'td_attr':
+ case 'th_attr':
+ $$_key = $_value;
+ break;
+ }
+ }
+ $loop_count = count($loop);
+ if (empty($params['rows'])) {
+ /* no rows specified */
+ $rows = ceil($loop_count / $cols_count);
+ } elseif (empty($params['cols'])) {
+ if (!empty($params['rows'])) {
+ /* no cols specified, but rows */
+ $cols_count = ceil($loop_count / $rows);
+ }
+ }
+ $output = "<table $table_attr>\n";
+ if (!empty($caption)) {
+ $output .= '<caption>' . $caption . "</caption>\n";
+ }
+ if (is_array($cols)) {
+ $cols = ($hdir === 'right') ? $cols : array_reverse($cols);
+ $output .= "<thead><tr>\n";
+ for ($r = 0; $r < $cols_count; $r++) {
+ $output .= '<th' . $this->cycle('th', $th_attr, $r) . '>';
+ $output .= $cols[$r];
+ $output .= "</th>\n";
+ }
+ $output .= "</tr></thead>\n";
+ }
+ $output .= "<tbody>\n";
+ for ($r = 0; $r < $rows; $r++) {
+ $output .= "<tr" . $this->cycle('tr', $tr_attr, $r) . ">\n";
+ $rx = ($vdir === 'down') ? $r * $cols_count : ($rows - 1 - $r) * $cols_count;
+ for ($c = 0; $c < $cols_count; $c++) {
+ $x = ($hdir === 'right') ? $rx + $c : $rx + $cols_count - 1 - $c;
+ if ($inner !== 'cols') {
+ /* shuffle x to loop over rows*/
+ $x = floor($x / $cols_count) + ($x % $cols_count) * $rows;
+ }
+ if ($x < $loop_count) {
+ $output .= "<td" . $this->cycle('td', $td_attr, $c) . ">" . $loop[$x] . "</td>\n";
+ } else {
+ $output .= "<td" . $this->cycle('td', $td_attr, $c) . ">$trailpad</td>\n";
+ }
+ }
+ $output .= "</tr>\n";
+ }
+ $output .= "</tbody>\n";
+ $output .= "</table>\n";
+ return $output;
+ }
+
+ /**
+ * @param $name
+ * @param $var
+ * @param $no
+ *
+ * @return string
+ */
+ private function cycle($name, $var, $no) {
+ if (!is_array($var)) {
+ $ret = $var;
+ } else {
+ $ret = $var[$no % count($var)];
+ }
+ return ($ret) ? ' ' . $ret : '';
+ }
+}
diff --git a/src/FunctionHandler/InArray.php b/src/FunctionHandler/InArray.php
new file mode 100644
index 00000000..1e630eb3
--- /dev/null
+++ b/src/FunctionHandler/InArray.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * in_array(mixed $needle, array $haystack, bool $strict = false): bool
+ * Returns true if needle is found in the array, false otherwise
+ */
+class InArray extends Base {
+
+ public function handle($params, Template $template) {
+
+ $params = array_values($params ?? []);
+
+ if (count($params) < 2 || count($params) > 3) {
+ throw new Exception("Invalid number of arguments for in_array. in_arrays expects 2 or 3 parameters.");
+ }
+
+ // default to false, true if param 3 is set to true
+ $needle = $params[0];
+ $haystack = (array) $params[1];
+ $strict = count($params) == 3 && $params[2];
+
+ return in_array($needle, $haystack, $strict);
+ }
+
+} \ No newline at end of file
diff --git a/src/FunctionHandler/IsArray.php b/src/FunctionHandler/IsArray.php
new file mode 100644
index 00000000..06cd585d
--- /dev/null
+++ b/src/FunctionHandler/IsArray.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * is_array(mixed $value): bool
+ * Returns true if value is an array, false otherwise.
+ */
+class IsArray extends Base {
+
+ public function handle($params, Template $template) {
+ if (count($params) !== 1) {
+ throw new Exception("Invalid number of arguments for is_array. is_array expects exactly 1 parameter.");
+ }
+ return is_array(reset($params));
+ }
+
+} \ No newline at end of file
diff --git a/src/FunctionHandler/Mailto.php b/src/FunctionHandler/Mailto.php
new file mode 100644
index 00000000..8f7b821a
--- /dev/null
+++ b/src/FunctionHandler/Mailto.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {mailto} function plugin
+ * Type: function
+ * Name: mailto
+ * Date: May 21, 2002
+ * Purpose: automate mailto address link creation, and optionally encode them.
+ * Params:
+ *
+ * - address - (required) - e-mail address
+ * - text - (optional) - text to display, default is address
+ * - encode - (optional) - can be one of:
+ * * none : no encoding (default)
+ * * javascript : encode with javascript
+ * * javascript_charcode : encode with javascript charcode
+ * * hex : encode with hexadecimal (no javascript)
+ * - cc - (optional) - address(es) to carbon copy
+ * - bcc - (optional) - address(es) to blind carbon copy
+ * - subject - (optional) - e-mail subject
+ * - newsgroups - (optional) - newsgroup(s) to post to
+ * - followupto - (optional) - address(es) to follow up to
+ * - extra - (optional) - extra tags for the href link
+ *
+ * Examples:
+ *
+ * {mailto address="me@domain.com"}
+ * {mailto address="me@domain.com" encode="javascript"}
+ * {mailto address="me@domain.com" encode="hex"}
+ * {mailto address="me@domain.com" subject="Hello to you!"}
+ * {mailto address="me@domain.com" cc="you@domain.com,they@domain.com"}
+ * {mailto address="me@domain.com" extra='class="mailto"'}
+ *
+ * @link https://www.smarty.net/manual/en/language.function.mailto.php {mailto}
+ * (Smarty online manual)
+ * @version 1.2
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author credits to Jason Sweat (added cc, bcc and subject functionality)
+ *
+ * @param array $params parameters
+ *
+ * @return string
+ */
+class Mailto extends Base {
+
+ public function handle($params, Template $template) {
+ static $_allowed_encoding = [
+ 'javascript' => true,
+ 'javascript_charcode' => true,
+ 'hex' => true,
+ 'none' => true
+ ];
+
+ $extra = '';
+ if (empty($params['address'])) {
+ trigger_error("mailto: missing 'address' parameter", E_USER_WARNING);
+ return;
+ } else {
+ $address = $params['address'];
+ }
+
+ $text = $address;
+
+ // netscape and mozilla do not decode %40 (@) in BCC field (bug?)
+ // so, don't encode it.
+ $mail_parms = [];
+ foreach ($params as $var => $value) {
+ switch ($var) {
+ case 'cc':
+ case 'bcc':
+ case 'followupto':
+ if (!empty($value)) {
+ $mail_parms[] = $var . '=' . str_replace(['%40', '%2C'], ['@', ','], rawurlencode($value));
+ }
+ break;
+ case 'subject':
+ case 'newsgroups':
+ $mail_parms[] = $var . '=' . rawurlencode($value);
+ break;
+ case 'extra':
+ case 'text':
+ $$var = $value;
+ // no break
+ default:
+ }
+ }
+
+ if ($mail_parms) {
+ $address .= '?' . join('&', $mail_parms);
+ }
+ $encode = (empty($params['encode'])) ? 'none' : $params['encode'];
+ if (!isset($_allowed_encoding[$encode])) {
+ trigger_error(
+ "mailto: 'encode' parameter must be none, javascript, javascript_charcode or hex",
+ E_USER_WARNING
+ );
+ return;
+ }
+
+ $string = '<a href="mailto:' . htmlspecialchars($address, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, \Smarty\Smarty::$_CHARSET) .
+ '" ' . $extra . '>' . htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, \Smarty\Smarty::$_CHARSET) . '</a>';
+
+ if ($encode === 'javascript') {
+ $js_encode = '';
+ for ($x = 0, $_length = strlen($string); $x < $_length; $x++) {
+ $js_encode .= '%' . bin2hex($string[$x]);
+ }
+ return '<script>document.write(unescape(\'' . $js_encode . '\'))</script>';
+ } elseif ($encode === 'javascript_charcode') {
+ for ($x = 0, $_length = strlen($string); $x < $_length; $x++) {
+ $ord[] = ord($string[$x]);
+ }
+ return '<script>document.write(String.fromCharCode(' . implode(',', $ord) . '))</script>';
+ } elseif ($encode === 'hex') {
+ preg_match('!^(.*)(\?.*)$!', $address, $match);
+ if (!empty($match[2])) {
+ trigger_error("mailto: hex encoding does not work with extra attributes. Try javascript.", E_USER_WARNING);
+ return;
+ }
+ $address_encode = '';
+ for ($x = 0, $_length = strlen($address); $x < $_length; $x++) {
+ if (preg_match('!\w!' . \Smarty\Smarty::$_UTF8_MODIFIER, $address[$x])) {
+ $address_encode .= '%' . bin2hex($address[$x]);
+ } else {
+ $address_encode .= $address[$x];
+ }
+ }
+ $text_encode = '';
+ for ($x = 0, $_length = strlen($text); $x < $_length; $x++) {
+ $text_encode .= '&#x' . bin2hex($text[$x]) . ';';
+ }
+ $mailto = "&#109;&#97;&#105;&#108;&#116;&#111;&#58;";
+ return '<a href="' . $mailto . $address_encode . '" ' . $extra . '>' . $text_encode . '</a>';
+ } else {
+ // no encoding
+ return $string;
+ }
+ }
+}
diff --git a/src/FunctionHandler/Math.php b/src/FunctionHandler/Math.php
new file mode 100644
index 00000000..aed1cb92
--- /dev/null
+++ b/src/FunctionHandler/Math.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Template;
+
+/**
+ * Smarty {math} function plugin
+ * Type: function
+ * Name: math
+ * Purpose: handle math computations in template
+ *
+ * @link https://www.smarty.net/manual/en/language.function.math.php {math}
+ * (Smarty online manual)
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param array $params parameters
+ * @param Template $template template object
+ *
+ * @return string|null
+ */
+class Math extends Base {
+
+ public function handle($params, Template $template) {
+ static $_allowed_funcs =
+ [
+ 'int' => true,
+ 'abs' => true,
+ 'ceil' => true,
+ 'acos' => true,
+ 'acosh' => true,
+ 'cos' => true,
+ 'cosh' => true,
+ 'deg2rad' => true,
+ 'rad2deg' => true,
+ 'exp' => true,
+ 'floor' => true,
+ 'log' => true,
+ 'log10' => true,
+ 'max' => true,
+ 'min' => true,
+ 'pi' => true,
+ 'pow' => true,
+ 'rand' => true,
+ 'round' => true,
+ 'asin' => true,
+ 'asinh' => true,
+ 'sin' => true,
+ 'sinh' => true,
+ 'sqrt' => true,
+ 'srand' => true,
+ 'atan' => true,
+ 'atanh' => true,
+ 'tan' => true,
+ 'tanh' => true
+ ];
+
+ // be sure equation parameter is present
+ if (empty($params['equation'])) {
+ trigger_error("math: missing equation parameter", E_USER_WARNING);
+ return;
+ }
+ $equation = $params['equation'];
+
+ // Remove whitespaces
+ $equation = preg_replace('/\s+/', '', $equation);
+
+ // Adapted from https://www.php.net/manual/en/function.eval.php#107377
+ $number = '(?:\d+(?:[,.]\d+)?|pi|Ï€)'; // What is a number
+ $functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))';
+ $operators = '[,+\/*\^%-]'; // Allowed math operators
+ $regexp = '/^((' . $number . '|' . $functionsOrVars . '|(' . $functionsOrVars . '\s*\((?1)*\)|\((?1)*\)))(?:' . $operators . '(?1))?)+$/';
+
+ if (!preg_match($regexp, $equation)) {
+ trigger_error("math: illegal characters", E_USER_WARNING);
+ return;
+ }
+
+ // make sure parenthesis are balanced
+ if (substr_count($equation, '(') !== substr_count($equation, ')')) {
+ trigger_error("math: unbalanced parenthesis", E_USER_WARNING);
+ return;
+ }
+
+ // disallow backticks
+ if (strpos($equation, '`') !== false) {
+ trigger_error("math: backtick character not allowed in equation", E_USER_WARNING);
+ return;
+ }
+
+ // also disallow dollar signs
+ if (strpos($equation, '$') !== false) {
+ trigger_error("math: dollar signs not allowed in equation", E_USER_WARNING);
+ return;
+ }
+ foreach ($params as $key => $val) {
+ if ($key !== 'equation' && $key !== 'format' && $key !== 'assign') {
+ // make sure value is not empty
+ if (strlen($val) === 0) {
+ trigger_error("math: parameter '{$key}' is empty", E_USER_WARNING);
+ return;
+ }
+ if (!is_numeric($val)) {
+ trigger_error("math: parameter '{$key}' is not numeric", E_USER_WARNING);
+ return;
+ }
+ }
+ }
+ // match all vars in equation, make sure all are passed
+ preg_match_all('!(?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)!', $equation, $match);
+ foreach ($match[1] as $curr_var) {
+ if ($curr_var && !isset($params[$curr_var]) && !isset($_allowed_funcs[$curr_var])) {
+ trigger_error(
+ "math: function call '{$curr_var}' not allowed, or missing parameter '{$curr_var}'",
+ E_USER_WARNING
+ );
+ return;
+ }
+ }
+ foreach ($params as $key => $val) {
+ if ($key !== 'equation' && $key !== 'format' && $key !== 'assign') {
+ $equation = preg_replace("/\b$key\b/", " \$params['$key'] ", $equation);
+ }
+ }
+ $smarty_math_result = null;
+ eval("\$smarty_math_result = " . $equation . ";");
+
+ if (empty($params['format'])) {
+ if (empty($params['assign'])) {
+ return $smarty_math_result;
+ } else {
+ $template->assign($params['assign'], $smarty_math_result);
+ }
+ } else {
+ if (empty($params['assign'])) {
+ printf($params['format'], $smarty_math_result);
+ } else {
+ $template->assign($params['assign'], sprintf($params['format'], $smarty_math_result));
+ }
+ }
+ }
+}
diff --git a/src/FunctionHandler/Strlen.php b/src/FunctionHandler/Strlen.php
new file mode 100644
index 00000000..f1ae47f0
--- /dev/null
+++ b/src/FunctionHandler/Strlen.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * Get string length
+ *
+ * strlen(string $string): int
+ *
+ * Returns length of the string on success, and 0 if the string is empty.
+ */
+class Strlen extends Base {
+
+ public function handle($params, Template $template) {
+
+ $params = array_values($params ?? []);
+
+ if (count($params) !== 1) {
+ throw new Exception("Invalid number of arguments for strlen. strlen expects exactly 1 parameter.");
+ }
+
+ return strlen((string) $params[0]);
+ }
+
+} \ No newline at end of file
diff --git a/src/FunctionHandler/Time.php b/src/FunctionHandler/Time.php
new file mode 100644
index 00000000..feebca25
--- /dev/null
+++ b/src/FunctionHandler/Time.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Smarty\FunctionHandler;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * is_array(mixed $value): bool
+ * Returns true if value is an array, false otherwise.
+ */
+class Time extends Base {
+
+ public function handle($params, Template $template) {
+ if (count($params) > 0) {
+ throw new Exception("Invalid number of arguments for time. time expects no parameters.");
+ }
+ return time();
+ }
+
+} \ No newline at end of file
diff --git a/src/Lexer/ConfigfileLexer.php b/src/Lexer/ConfigfileLexer.php
new file mode 100644
index 00000000..d592c823
--- /dev/null
+++ b/src/Lexer/ConfigfileLexer.php
@@ -0,0 +1,707 @@
+<?php
+
+namespace Smarty\Lexer;
+
+/**
+* Smarty Internal Plugin ConfigfileLexer
+*
+* This is the lexer to break the config file source into tokens
+* @package Smarty
+* @subpackage Config
+* @author Uwe Tews
+*/
+/**
+* ConfigfileLexer
+*
+* This is the config file lexer.
+* It is generated from the ConfigfileLexer.plex file
+*
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews
+*/
+class ConfigfileLexer
+{
+ /**
+ * Source
+ *
+ * @var string
+ */
+ public $data;
+ /**
+ * Source length
+ *
+ * @var int
+ */
+ public $dataLength = null;
+ /**
+ * byte counter
+ *
+ * @var int
+ */
+ public $counter;
+ /**
+ * token number
+ *
+ * @var int
+ */
+ public $token;
+ /**
+ * token value
+ *
+ * @var string
+ */
+ public $value;
+ /**
+ * current line
+ *
+ * @var int
+ */
+ public $line;
+ /**
+ * state number
+ *
+ * @var int
+ */
+ public $state = 1;
+ /**
+ * Smarty object
+ *
+ * @var Smarty
+ */
+ public $smarty = null;
+ /**
+ * compiler object
+ *
+ * @var \Smarty\Compiler\Configfile
+ */
+ private $compiler = null;
+ /**
+ * copy of config_booleanize
+ *
+ * @var bool
+ */
+ private $configBooleanize = false;
+ /**
+ * trace file
+ *
+ * @var resource
+ */
+ public $yyTraceFILE;
+ /**
+ * trace prompt
+ *
+ * @var string
+ */
+ public $yyTracePrompt;
+ /**
+ * state names
+ *
+ * @var array
+ */
+ public $state_name = array(1 => 'START', 2 => 'VALUE', 3 => 'NAKED_STRING_VALUE', 4 => 'COMMENT', 5 => 'SECTION', 6 => 'TRIPPLE');
+
+ /**
+ * storage for assembled token patterns
+ *
+ * @var string
+ */
+ private $yy_global_pattern1 = null;
+ private $yy_global_pattern2 = null;
+ private $yy_global_pattern3 = null;
+ private $yy_global_pattern4 = null;
+ private $yy_global_pattern5 = null;
+ private $yy_global_pattern6 = null;
+
+ /**
+ * token names
+ *
+ * @var array
+ */
+ public $smarty_token_names = array( // Text for parser error messages
+ );
+
+ /**
+ * constructor
+ *
+ * @param string $data template source
+ * @param \Smarty\Compiler\Configfile $compiler
+ */
+ public function __construct($data, \Smarty\Compiler\Configfile $compiler)
+ {
+ $this->data = $data . "\n"; //now all lines are \n-terminated
+ $this->dataLength = strlen($data);
+ $this->counter = 0;
+ if (preg_match('/^\xEF\xBB\xBF/', $this->data, $match)) {
+ $this->counter += strlen($match[0]);
+ }
+ $this->line = 1;
+ $this->compiler = $compiler;
+ $this->smarty = $compiler->smarty;
+ $this->configBooleanize = $this->smarty->config_booleanize;
+ }
+
+ public function replace ($input) {
+ return $input;
+ }
+
+ public function PrintTrace()
+ {
+ $this->yyTraceFILE = fopen('php://output', 'w');
+ $this->yyTracePrompt = '<br>';
+ }
+
+
+
+ private $_yy_state = 1;
+ private $_yy_stack = array();
+
+ public function yylex()
+ {
+ return $this->{'yylex' . $this->_yy_state}();
+ }
+
+ public function yypushstate($state)
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sState push %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ array_push($this->_yy_stack, $this->_yy_state);
+ $this->_yy_state = $state;
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ }
+
+ public function yypopstate()
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sState pop %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ $this->_yy_state = array_pop($this->_yy_stack);
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+
+ }
+
+ public function yybegin($state)
+ {
+ $this->_yy_state = $state;
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sState set %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ }
+
+
+
+
+ public function yylex1()
+ {
+ if (!isset($this->yy_global_pattern1)) {
+ $this->yy_global_pattern1 = $this->replace("/\G(#|;)|\G(\\[)|\G(\\])|\G(=)|\G([ \t\r]+)|\G(\n)|\G([0-9]*[a-zA-Z_]\\w*)|\G([\S\s])/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern1,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state START');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r1_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const START = 1;
+ public function yy_r1_1()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_COMMENTSTART;
+ $this->yypushstate(self::COMMENT);
+ }
+ public function yy_r1_2()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_OPENB;
+ $this->yypushstate(self::SECTION);
+ }
+ public function yy_r1_3()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_CLOSEB;
+ }
+ public function yy_r1_4()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_EQUAL;
+ $this->yypushstate(self::VALUE);
+ }
+ public function yy_r1_5()
+ {
+
+ return false;
+ }
+ public function yy_r1_6()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NEWLINE;
+ }
+ public function yy_r1_7()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_ID;
+ }
+ public function yy_r1_8()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_OTHER;
+ }
+
+
+
+ public function yylex2()
+ {
+ if (!isset($this->yy_global_pattern2)) {
+ $this->yy_global_pattern2 = $this->replace("/\G([ \t\r]+)|\G(\\d+\\.\\d+(?=[ \t\r]*[\n#;]))|\G(\\d+(?=[ \t\r]*[\n#;]))|\G(\"\"\")|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*'(?=[ \t\r]*[\n#;]))|\G(\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"(?=[ \t\r]*[\n#;]))|\G([a-zA-Z]+(?=[ \t\r]*[\n#;]))|\G([^\n]+?(?=[ \t\r]*\n))|\G(\n)/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern2,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state VALUE');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r2_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const VALUE = 2;
+ public function yy_r2_1()
+ {
+
+ return false;
+ }
+ public function yy_r2_2()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_FLOAT;
+ $this->yypopstate();
+ }
+ public function yy_r2_3()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_INT;
+ $this->yypopstate();
+ }
+ public function yy_r2_4()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_TRIPPLE_QUOTES;
+ $this->yypushstate(self::TRIPPLE);
+ }
+ public function yy_r2_5()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_SINGLE_QUOTED_STRING;
+ $this->yypopstate();
+ }
+ public function yy_r2_6()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_DOUBLE_QUOTED_STRING;
+ $this->yypopstate();
+ }
+ public function yy_r2_7()
+ {
+
+ if (!$this->configBooleanize || !in_array(strtolower($this->value), array('true', 'false', 'on', 'off', 'yes', 'no')) ) {
+ $this->yypopstate();
+ $this->yypushstate(self::NAKED_STRING_VALUE);
+ return true; //reprocess in new state
+ } else {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_BOOL;
+ $this->yypopstate();
+ }
+ }
+ public function yy_r2_8()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+ $this->yypopstate();
+ }
+ public function yy_r2_9()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+ $this->value = '';
+ $this->yypopstate();
+ }
+
+
+
+ public function yylex3()
+ {
+ if (!isset($this->yy_global_pattern3)) {
+ $this->yy_global_pattern3 = $this->replace("/\G([^\n]+?(?=[ \t\r]*\n))/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern3,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state NAKED_STRING_VALUE');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r3_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const NAKED_STRING_VALUE = 3;
+ public function yy_r3_1()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+ $this->yypopstate();
+ }
+
+
+
+ public function yylex4()
+ {
+ if (!isset($this->yy_global_pattern4)) {
+ $this->yy_global_pattern4 = $this->replace("/\G([ \t\r]+)|\G([^\n]+?(?=[ \t\r]*\n))|\G(\n)/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern4,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state COMMENT');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r4_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const COMMENT = 4;
+ public function yy_r4_1()
+ {
+
+ return false;
+ }
+ public function yy_r4_2()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+ }
+ public function yy_r4_3()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NEWLINE;
+ $this->yypopstate();
+ }
+
+
+
+ public function yylex5()
+ {
+ if (!isset($this->yy_global_pattern5)) {
+ $this->yy_global_pattern5 = $this->replace("/\G(\\.)|\G(.*?(?=[\.=[\]\r\n]))/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern5,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state SECTION');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r5_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const SECTION = 5;
+ public function yy_r5_1()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_DOT;
+ }
+ public function yy_r5_2()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_SECTION;
+ $this->yypopstate();
+ }
+
+
+ public function yylex6()
+ {
+ if (!isset($this->yy_global_pattern6)) {
+ $this->yy_global_pattern6 = $this->replace("/\G(\"\"\"(?=[ \t\r]*[\n#;]))|\G([\S\s])/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern6,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state TRIPPLE');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r6_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const TRIPPLE = 6;
+ public function yy_r6_1()
+ {
+
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_TRIPPLE_QUOTES_END;
+ $this->yypopstate();
+ $this->yypushstate(self::START);
+ }
+ public function yy_r6_2()
+ {
+
+ $to = strlen($this->data);
+ preg_match("/\"\"\"[ \t\r]*[\n#;]/",$this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1];
+ } else {
+ $this->compiler->trigger_config_file_error ('missing or misspelled literal closing tag');
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_TRIPPLE_TEXT;
+ }
+
+
+}
diff --git a/src/Lexer/ConfigfileLexer.plex b/src/Lexer/ConfigfileLexer.plex
new file mode 100644
index 00000000..a895050c
--- /dev/null
+++ b/src/Lexer/ConfigfileLexer.plex
@@ -0,0 +1,321 @@
+<?php
+
+namespace Smarty\Lexer;
+
+/**
+* Smarty Internal Plugin ConfigfileLexer
+*
+* This is the lexer to break the config file source into tokens
+* @package Smarty
+* @subpackage Config
+* @author Uwe Tews
+*/
+/**
+* ConfigfileLexer
+*
+* This is the config file lexer.
+* It is generated from the ConfigfileLexer.plex file
+*
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews
+*/
+class ConfigfileLexer
+{
+ /**
+ * Source
+ *
+ * @var string
+ */
+ public $data;
+ /**
+ * Source length
+ *
+ * @var int
+ */
+ public $dataLength = null;
+ /**
+ * byte counter
+ *
+ * @var int
+ */
+ public $counter;
+ /**
+ * token number
+ *
+ * @var int
+ */
+ public $token;
+ /**
+ * token value
+ *
+ * @var string
+ */
+ public $value;
+ /**
+ * current line
+ *
+ * @var int
+ */
+ public $line;
+ /**
+ * state number
+ *
+ * @var int
+ */
+ public $state = 1;
+ /**
+ * Smarty object
+ *
+ * @var Smarty
+ */
+ public $smarty = null;
+ /**
+ * compiler object
+ *
+ * @var \Smarty\Compiler\Configfile
+ */
+ private $compiler = null;
+ /**
+ * copy of config_booleanize
+ *
+ * @var bool
+ */
+ private $configBooleanize = false;
+ /**
+ * trace file
+ *
+ * @var resource
+ */
+ public $yyTraceFILE;
+ /**
+ * trace prompt
+ *
+ * @var string
+ */
+ public $yyTracePrompt;
+ /**
+ * state names
+ *
+ * @var array
+ */
+ public $state_name = array(1 => 'START', 2 => 'VALUE', 3 => 'NAKED_STRING_VALUE', 4 => 'COMMENT', 5 => 'SECTION', 6 => 'TRIPPLE');
+
+ /**
+ * storage for assembled token patterns
+ *
+ * @var string
+ */
+ private $yy_global_pattern1 = null;
+ private $yy_global_pattern2 = null;
+ private $yy_global_pattern3 = null;
+ private $yy_global_pattern4 = null;
+ private $yy_global_pattern5 = null;
+ private $yy_global_pattern6 = null;
+
+ /**
+ * token names
+ *
+ * @var array
+ */
+ public $smarty_token_names = array( // Text for parser error messages
+ );
+
+ /**
+ * constructor
+ *
+ * @param string $data template source
+ * @param \Smarty\Compiler\Configfile $compiler
+ */
+ public function __construct($data, \Smarty\Compiler\Configfile $compiler)
+ {
+ $this->data = $data . "\n"; //now all lines are \n-terminated
+ $this->dataLength = strlen($data);
+ $this->counter = 0;
+ if (preg_match('/^\xEF\xBB\xBF/', $this->data, $match)) {
+ $this->counter += strlen($match[0]);
+ }
+ $this->line = 1;
+ $this->compiler = $compiler;
+ $this->smarty = $compiler->smarty;
+ $this->configBooleanize = $this->smarty->config_booleanize;
+ }
+
+ public function replace ($input) {
+ return $input;
+ }
+
+ public function PrintTrace()
+ {
+ $this->yyTraceFILE = fopen('php://output', 'w');
+ $this->yyTracePrompt = '<br>';
+ }
+
+
+/*!lex2php
+%input $this->data
+%counter $this->counter
+%token $this->token
+%value $this->value
+%line $this->line
+commentstart = /#|;/
+openB = /\[/
+closeB = /\]/
+section = /.*?(?=[\.=\[\]\r\n])/
+equal = /=/
+whitespace = /[ \t\r]+/
+dot = /\./
+id = /[0-9]*[a-zA-Z_]\w*/
+newline = /\n/
+single_quoted_string = /'[^'\\]*(?:\\.[^'\\]*)*'(?=[ \t\r]*[\n#;])/
+double_quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"(?=[ \t\r]*[\n#;])/
+tripple_quotes = /"""/
+tripple_quotes_end = /"""(?=[ \t\r]*[\n#;])/
+text = /[\S\s]/
+float = /\d+\.\d+(?=[ \t\r]*[\n#;])/
+int = /\d+(?=[ \t\r]*[\n#;])/
+maybe_bool = /[a-zA-Z]+(?=[ \t\r]*[\n#;])/
+naked_string = /[^\n]+?(?=[ \t\r]*\n)/
+*/
+
+/*!lex2php
+%statename START
+
+commentstart {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_COMMENTSTART;
+ $this->yypushstate(self::COMMENT);
+}
+openB {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_OPENB;
+ $this->yypushstate(self::SECTION);
+}
+closeB {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_CLOSEB;
+}
+equal {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_EQUAL;
+ $this->yypushstate(self::VALUE);
+}
+whitespace {
+ return false;
+}
+newline {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NEWLINE;
+}
+id {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_ID;
+}
+text {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_OTHER;
+}
+
+*/
+
+/*!lex2php
+%statename VALUE
+
+whitespace {
+ return false;
+}
+float {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_FLOAT;
+ $this->yypopstate();
+}
+int {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_INT;
+ $this->yypopstate();
+}
+tripple_quotes {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_TRIPPLE_QUOTES;
+ $this->yypushstate(self::TRIPPLE);
+}
+single_quoted_string {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_SINGLE_QUOTED_STRING;
+ $this->yypopstate();
+}
+double_quoted_string {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_DOUBLE_QUOTED_STRING;
+ $this->yypopstate();
+}
+maybe_bool {
+ if (!$this->configBooleanize || !in_array(strtolower($this->value), array('true', 'false', 'on', 'off', 'yes', 'no')) ) {
+ $this->yypopstate();
+ $this->yypushstate(self::NAKED_STRING_VALUE);
+ return true; //reprocess in new state
+ } else {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_BOOL;
+ $this->yypopstate();
+ }
+}
+naked_string {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+ $this->yypopstate();
+}
+newline {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+ $this->value = '';
+ $this->yypopstate();
+}
+
+*/
+
+/*!lex2php
+%statename NAKED_STRING_VALUE
+
+naked_string {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+ $this->yypopstate();
+}
+
+*/
+
+/*!lex2php
+%statename COMMENT
+
+whitespace {
+ return false;
+}
+naked_string {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NAKED_STRING;
+}
+newline {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_NEWLINE;
+ $this->yypopstate();
+}
+
+*/
+
+/*!lex2php
+%statename SECTION
+
+dot {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_DOT;
+}
+section {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_SECTION;
+ $this->yypopstate();
+}
+
+*/
+/*!lex2php
+%statename TRIPPLE
+
+tripple_quotes_end {
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_TRIPPLE_QUOTES_END;
+ $this->yypopstate();
+ $this->yypushstate(self::START);
+}
+text {
+ $to = strlen($this->data);
+ preg_match("/\"\"\"[ \t\r]*[\n#;]/",$this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1];
+ } else {
+ $this->compiler->trigger_config_file_error ('missing or misspelled literal closing tag');
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\ConfigfileParser::TPC_TRIPPLE_TEXT;
+}
+*/
+
+}
diff --git a/src/Lexer/TemplateLexer.php b/src/Lexer/TemplateLexer.php
new file mode 100644
index 00000000..5c7253ba
--- /dev/null
+++ b/src/Lexer/TemplateLexer.php
@@ -0,0 +1,1083 @@
+<?php
+
+namespace Smarty\Lexer;
+
+/*
+ * This file is part of Smarty.
+ *
+ * (c) 2015 Uwe Tews
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * TemplateLexer
+ * This is the template file lexer.
+ * It is generated from the TemplateLexer.plex file
+ *
+ *
+ * @author Uwe Tews <uwe.tews@googlemail.com>
+ */
+class TemplateLexer
+{
+ /**
+ * Source
+ *
+ * @var string
+ */
+ public $data;
+
+ /**
+ * Source length
+ *
+ * @var int
+ */
+ public $dataLength = null;
+
+ /**
+ * byte counter
+ *
+ * @var int
+ */
+ public $counter;
+
+ /**
+ * token number
+ *
+ * @var int
+ */
+ public $token;
+
+ /**
+ * token value
+ *
+ * @var string
+ */
+ public $value;
+
+ /**
+ * current line
+ *
+ * @var int
+ */
+ public $line;
+
+ /**
+ * tag start line
+ *
+ * @var
+ */
+ public $taglineno;
+
+ /**
+ * state number
+ *
+ * @var int
+ */
+ public $state = 1;
+
+ /**
+ * Smarty object
+ *
+ * @var Smarty
+ */
+ public $smarty = null;
+
+ /**
+ * compiler object
+ *
+ * @var \Smarty\Compiler\Template
+ */
+ public $compiler = null;
+
+ /**
+ * trace file
+ *
+ * @var resource
+ */
+ public $yyTraceFILE;
+
+ /**
+ * trace prompt
+ *
+ * @var string
+ */
+ public $yyTracePrompt;
+
+ /**
+ * XML flag true while processing xml
+ *
+ * @var bool
+ */
+ public $is_xml = false;
+
+ /**
+ * state names
+ *
+ * @var array
+ */
+ public $state_name = array(1 => 'TEXT', 2 => 'TAG', 3 => 'TAGBODY', 4 => 'LITERAL', 5 => 'DOUBLEQUOTEDSTRING',);
+
+ /**
+ * token names
+ *
+ * @var array
+ */
+ public $smarty_token_names = array( // Text for parser error messages
+ 'NOT' => '(!,not)',
+ 'OPENP' => '(',
+ 'CLOSEP' => ')',
+ 'OPENB' => '[',
+ 'CLOSEB' => ']',
+ 'PTR' => '->',
+ 'APTR' => '=>',
+ 'EQUAL' => '=',
+ 'NUMBER' => 'number',
+ 'UNIMATH' => '+" , "-',
+ 'MATH' => '*" , "/" , "%',
+ 'INCDEC' => '++" , "--',
+ 'SPACE' => ' ',
+ 'DOLLAR' => '$',
+ 'SEMICOLON' => ';',
+ 'COLON' => ':',
+ 'DOUBLECOLON' => '::',
+ 'AT' => '@',
+ 'HATCH' => '#',
+ 'QUOTE' => '"',
+ 'BACKTICK' => '`',
+ 'VERT' => '"|" modifier',
+ 'DOT' => '.',
+ 'COMMA' => '","',
+ 'QMARK' => '"?"',
+ 'ID' => 'id, name',
+ 'TEXT' => 'text',
+ 'LDELSLASH' => '{/..} closing tag',
+ 'LDEL' => '{...} Smarty tag',
+ 'COMMENT' => 'comment',
+ 'AS' => 'as',
+ 'TO' => 'to',
+ 'LOGOP' => '"<", "==" ... logical operator',
+ 'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition',
+ 'SCOND' => '"is even" ... if condition',
+ );
+
+ /**
+ * literal tag nesting level
+ *
+ * @var int
+ */
+ private $literal_cnt = 0;
+
+ /**
+ * preg token pattern for state TEXT
+ *
+ * @var string
+ */
+ private $yy_global_pattern1 = null;
+
+ /**
+ * preg token pattern for state TAG
+ *
+ * @var string
+ */
+ private $yy_global_pattern2 = null;
+
+ /**
+ * preg token pattern for state TAGBODY
+ *
+ * @var string
+ */
+ private $yy_global_pattern3 = null;
+
+ /**
+ * preg token pattern for state LITERAL
+ *
+ * @var string
+ */
+ private $yy_global_pattern4 = null;
+
+ /**
+ * preg token pattern for state DOUBLEQUOTEDSTRING
+ *
+ * @var null
+ */
+ private $yy_global_pattern5 = null;
+
+ /**
+ * preg token pattern for text
+ *
+ * @var null
+ */
+ private $yy_global_text = null;
+
+ /**
+ * preg token pattern for literal
+ *
+ * @var null
+ */
+ private $yy_global_literal = null;
+
+ /**
+ * constructor
+ *
+ * @param string $source template source
+ * @param \Smarty\Compiler\Template $compiler
+ */
+ public function __construct($source, \Smarty\Compiler\Template $compiler)
+ {
+ $this->data = $source;
+ $this->dataLength = strlen($this->data);
+ $this->counter = 0;
+ if (preg_match('/^\xEF\xBB\xBF/i', $this->data, $match)) {
+ $this->counter += strlen($match[0]);
+ }
+ $this->line = 1;
+ $this->smarty = $compiler->getTemplate()->getSmarty();
+ $this->compiler = $compiler;
+ $this->compiler->initDelimiterPreg();
+ $this->smarty_token_names['LDEL'] = $this->smarty->getLeftDelimiter();
+ $this->smarty_token_names['RDEL'] = $this->smarty->getRightDelimiter();
+ }
+
+ /**
+ * open lexer/parser trace file
+ *
+ */
+ public function PrintTrace()
+ {
+ $this->yyTraceFILE = fopen('php://output', 'w');
+ $this->yyTracePrompt = '<br>';
+ }
+
+ /**
+ * replace placeholders with runtime preg code
+ *
+ * @param string $preg
+ *
+ * @return string
+ */
+ public function replace($preg)
+ {
+ return $this->compiler->replaceDelimiter($preg);
+ }
+
+ /**
+ * check if current value is an autoliteral left delimiter
+ *
+ * @return bool
+ */
+ public function isAutoLiteral()
+ {
+ return $this->smarty->getAutoLiteral() && isset($this->value[ $this->compiler->getLdelLength() ]) ?
+ strpos(" \n\t\r", $this->value[ $this->compiler->getLdelLength() ]) !== false : false;
+ }
+
+
+ private $_yy_state = 1;
+ private $_yy_stack = array();
+
+ public function yylex()
+ {
+ return $this->{'yylex' . $this->_yy_state}();
+ }
+
+ public function yypushstate($state)
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sState push %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ array_push($this->_yy_stack, $this->_yy_state);
+ $this->_yy_state = $state;
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ }
+
+ public function yypopstate()
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sState pop %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ $this->_yy_state = array_pop($this->_yy_stack);
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%snew State %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+
+ }
+
+ public function yybegin($state)
+ {
+ $this->_yy_state = $state;
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sState set %s\n", $this->yyTracePrompt, isset($this->state_name[$this->_yy_state]) ? $this->state_name[$this->_yy_state] : $this->_yy_state);
+ }
+ }
+
+
+
+ public function yylex1()
+ {
+ if (!isset($this->yy_global_pattern1)) {
+ $this->yy_global_pattern1 = $this->replace("/\G([{][}])|\G((SMARTYldel)SMARTYal[*])|\G((SMARTYldel)SMARTYautoliteral\\s+SMARTYliteral)|\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\S\s])/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern1,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state TEXT');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r1_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const TEXT = 1;
+ public function yy_r1_1()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ public function yy_r1_2()
+ {
+
+ $to = $this->dataLength;
+ preg_match("/[*]{$this->compiler->getRdelPreg()}[\n]?/",$this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1] + strlen($match[0][0]);
+ } else {
+ $this->compiler->trigger_template_error ("missing or misspelled comment closing tag '{$this->smarty->getRightDelimiter()}'");
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ return false;
+ }
+ public function yy_r1_4()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ public function yy_r1_6()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERALSTART;
+ $this->yypushstate(self::LITERAL);
+ }
+ public function yy_r1_8()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERALEND;
+ $this->yypushstate(self::LITERAL);
+ }
+ public function yy_r1_10()
+ {
+
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ public function yy_r1_12()
+ {
+
+ if (!isset($this->yy_global_text)) {
+ $this->yy_global_text = $this->replace('/(SMARTYldel)SMARTYal/isS');
+ }
+ $to = $this->dataLength;
+ preg_match($this->yy_global_text, $this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1];
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+
+
+ public function yylex2()
+ {
+ if (!isset($this->yy_global_pattern2)) {
+ $this->yy_global_pattern2 = $this->replace("/\G((SMARTYldel)SMARTYal(if|elseif|else if|while)\\s+)|\G((SMARTYldel)SMARTYalfor\\s+)|\G((SMARTYldel)SMARTYalforeach(?![^\s]))|\G((SMARTYldel)SMARTYalsetfilter\\s+)|\G((SMARTYldel)SMARTYal[0-9]*[a-zA-Z_]\\w*(\\s+nocache)?\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[$]smarty\\.block\\.(child|parent)\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/][0-9]*[a-zA-Z_]\\w*\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[$][0-9]*[a-zA-Z_]\\w*(\\s+nocache)?\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/])|\G((SMARTYldel)SMARTYal)/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern2,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state TAG');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r2_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const TAG = 2;
+ public function yy_r2_1()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELIF;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_4()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELFOR;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_6()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELFOREACH;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_8()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELSETFILTER;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_10()
+ {
+
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_SIMPLETAG;
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_13()
+ {
+
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_SMARTYBLOCKCHILDPARENT;
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_16()
+ {
+
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_CLOSETAG;
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_18()
+ {
+
+ if ($this->_yy_stack[count($this->_yy_stack)-1] === self::TEXT) {
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_SIMPELOUTPUT;
+ $this->taglineno = $this->line;
+ } else {
+ $this->value = $this->smarty->getLeftDelimiter();
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDEL;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ }
+ public function yy_r2_21()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELSLASH;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ public function yy_r2_23()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDEL;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+
+
+ public function yylex3()
+ {
+ if (!isset($this->yy_global_pattern3)) {
+ $this->yy_global_pattern3 = $this->replace("/\G(\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(\\s+is\\s+in\\s+)|\G(\\s+as\\s+)|\G(\\s+to\\s+)|\G(\\s+step\\s+)|\G(\\s+instanceof\\s+)|\G(\\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\\s*)|\G(\\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even|div)\\s+by\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even))|\G([!]\\s*|not\\s+)|\G([(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\\s*)|\G(\\s*[(]\\s*)|\G(\\s*[)])|\G(\\[\\s*)|\G(\\s*\\])|\G(\\s*[-][>]\\s*)|\G(\\s*[=][>]\\s*)|\G(\\s*[=]\\s*)|\G(([+]|[-]){2})|\G(\\s*([+]|[-])\\s*)|\G(\\s*([*]{1,2}|[%\/^&]|[<>]{2})\\s*)|\G([@])|\G(array\\s*[(]\\s*)|\G([#])|\G(\\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\\s*[=]\\s*)|\G(([0-9]*[a-zA-Z_]\\w*)?(\\\\[0-9]*[a-zA-Z_]\\w*)+)|\G([0-9]*[a-zA-Z_]\\w*)|\G(\\d+)|\G([`])|\G([|][@]?)|\G([.])|\G(\\s*[,]\\s*)|\G(\\s*[;]\\s*)|\G([:]{2})|\G(\\s*[:]\\s*)|\G(\\s*[?]\\s*)|\G(0[xX][0-9a-fA-F]+)|\G(\\s+)|\G([\S\s])/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern3,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state TAGBODY');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r3_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const TAGBODY = 3;
+ public function yy_r3_1()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_RDEL;
+ $this->yypopstate();
+ }
+ public function yy_r3_2()
+ {
+
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ public function yy_r3_4()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_QUOTE;
+ $this->yypushstate(self::DOUBLEQUOTEDSTRING);
+ $this->compiler->enterDoubleQuote();
+ }
+ public function yy_r3_5()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_SINGLEQUOTESTRING;
+ }
+ public function yy_r3_6()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOLLARID;
+ }
+ public function yy_r3_7()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOLLAR;
+ }
+ public function yy_r3_8()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_ISIN;
+ }
+ public function yy_r3_9()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_AS;
+ }
+ public function yy_r3_10()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TO;
+ }
+ public function yy_r3_11()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_STEP;
+ }
+ public function yy_r3_12()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_INSTANCEOF;
+ }
+ public function yy_r3_13()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LOGOP;
+ }
+ public function yy_r3_15()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_SLOGOP;
+ }
+ public function yy_r3_17()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TLOGOP;
+ }
+ public function yy_r3_20()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_SINGLECOND;
+ }
+ public function yy_r3_23()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_NOT;
+ }
+ public function yy_r3_24()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TYPECAST;
+ }
+ public function yy_r3_28()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_OPENP;
+ }
+ public function yy_r3_29()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_CLOSEP;
+ }
+ public function yy_r3_30()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_OPENB;
+ }
+ public function yy_r3_31()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_CLOSEB;
+ }
+ public function yy_r3_32()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_PTR;
+ }
+ public function yy_r3_33()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_APTR;
+ }
+ public function yy_r3_34()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_EQUAL;
+ }
+ public function yy_r3_35()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_INCDEC;
+ }
+ public function yy_r3_37()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_UNIMATH;
+ }
+ public function yy_r3_39()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_MATH;
+ }
+ public function yy_r3_41()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_AT;
+ }
+ public function yy_r3_42()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_ARRAYOPEN;
+ }
+ public function yy_r3_43()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_HATCH;
+ }
+ public function yy_r3_44()
+ {
+
+ // resolve conflicts with shorttag and right_delimiter starting with '='
+ if (substr($this->data, $this->counter + strlen($this->value) - 1, $this->compiler->getRdelLength()) === $this->smarty->getRightDelimiter()) {
+ preg_match('/\s+/',$this->value,$match);
+ $this->value = $match[0];
+ $this->token = \Smarty\Parser\TemplateParser::TP_SPACE;
+ } else {
+ $this->token = \Smarty\Parser\TemplateParser::TP_ATTR;
+ }
+ }
+ public function yy_r3_45()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_NAMESPACE;
+ }
+ public function yy_r3_48()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_ID;
+ }
+ public function yy_r3_49()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_INTEGER;
+ }
+ public function yy_r3_50()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_BACKTICK;
+ $this->yypopstate();
+ }
+ public function yy_r3_51()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_VERT;
+ }
+ public function yy_r3_52()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOT;
+ }
+ public function yy_r3_53()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_COMMA;
+ }
+ public function yy_r3_54()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_SEMICOLON;
+ }
+ public function yy_r3_55()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOUBLECOLON;
+ }
+ public function yy_r3_56()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_COLON;
+ }
+ public function yy_r3_57()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_QMARK;
+ }
+ public function yy_r3_58()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_HEX;
+ }
+ public function yy_r3_59()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_SPACE;
+ }
+ public function yy_r3_60()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+
+
+
+ public function yylex4()
+ {
+ if (!isset($this->yy_global_pattern4)) {
+ $this->yy_global_pattern4 = $this->replace("/\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G([\S\s])/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern4,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state LITERAL');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r4_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const LITERAL = 4;
+ public function yy_r4_1()
+ {
+
+ $this->literal_cnt++;
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERAL;
+ }
+ public function yy_r4_3()
+ {
+
+ if ($this->literal_cnt) {
+ $this->literal_cnt--;
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERAL;
+ } else {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERALEND;
+ $this->yypopstate();
+ }
+ }
+ public function yy_r4_5()
+ {
+
+ if (!isset($this->yy_global_literal)) {
+ $this->yy_global_literal = $this->replace('/(SMARTYldel)SMARTYal[\/]?literalSMARTYrdel/isS');
+ }
+ $to = $this->dataLength;
+ preg_match($this->yy_global_literal, $this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1];
+ } else {
+ $this->compiler->trigger_template_error ("missing or misspelled literal closing tag");
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERAL;
+ }
+
+
+ public function yylex5()
+ {
+ if (!isset($this->yy_global_pattern5)) {
+ $this->yy_global_pattern5 = $this->replace("/\G((SMARTYldel)SMARTYautoliteral\\s+SMARTYliteral)|\G((SMARTYldel)SMARTYalliteral\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/]literal\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal[\/])|\G((SMARTYldel)SMARTYal[0-9]*[a-zA-Z_]\\w*)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G([`][$])|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(([^\"\\\\]*?)((?:\\\\.[^\"\\\\]*?)*?)(?=((SMARTYldel)SMARTYal|\\$|`\\$|\"SMARTYliteral)))|\G([\S\s])/isS");
+ }
+ if (!isset($this->dataLength)) {
+ $this->dataLength = strlen($this->data);
+ }
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+
+ do {
+ if (preg_match($this->yy_global_pattern5,$this->data, $yymatches, 0, $this->counter)) {
+ if (!isset($yymatches[ 0 ][1])) {
+ $yymatches = preg_grep("/(.|\s)+/", $yymatches);
+ } else {
+ $yymatches = array_filter($yymatches);
+ }
+ if (empty($yymatches)) {
+ throw new Exception('Error: lexing failed because a rule matched' .
+ ' an empty string. Input "' . substr($this->data,
+ $this->counter, 5) . '... state DOUBLEQUOTEDSTRING');
+ }
+ next($yymatches); // skip global match
+ $this->token = key($yymatches); // token number
+ $this->value = current($yymatches); // token value
+ $r = $this->{'yy_r5_' . $this->token}();
+ if ($r === null) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ // accept this token
+ return true;
+ } elseif ($r === true) {
+ // we have changed state
+ // process this token in the new state
+ return $this->yylex();
+ } elseif ($r === false) {
+ $this->counter += strlen($this->value);
+ $this->line += substr_count($this->value, "\n");
+ if ($this->counter >= $this->dataLength) {
+ return false; // end of input
+ }
+ // skip this token
+ continue;
+ } } else {
+ throw new Exception('Unexpected input at line' . $this->line .
+ ': ' . $this->data[$this->counter]);
+ }
+ break;
+ } while (true);
+
+ } // end function
+
+
+ const DOUBLEQUOTEDSTRING = 5;
+ public function yy_r5_1()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ public function yy_r5_3()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ public function yy_r5_5()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ public function yy_r5_7()
+ {
+
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ public function yy_r5_9()
+ {
+
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ public function yy_r5_11()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDEL;
+ $this->taglineno = $this->line;
+ $this->yypushstate(self::TAGBODY);
+ }
+ public function yy_r5_13()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_QUOTE;
+ $this->yypopstate();
+ }
+ public function yy_r5_14()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_BACKTICK;
+ $this->value = substr($this->value,0,-1);
+ $this->yypushstate(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ public function yy_r5_15()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOLLARID;
+ }
+ public function yy_r5_16()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ public function yy_r5_17()
+ {
+
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ public function yy_r5_22()
+ {
+
+ $to = $this->dataLength;
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+
+ }
+
+ \ No newline at end of file
diff --git a/src/Lexer/TemplateLexer.plex b/src/Lexer/TemplateLexer.plex
new file mode 100644
index 00000000..d29b6559
--- /dev/null
+++ b/src/Lexer/TemplateLexer.plex
@@ -0,0 +1,677 @@
+<?php
+
+namespace Smarty\Lexer;
+
+/*
+ * This file is part of Smarty.
+ *
+ * (c) 2015 Uwe Tews
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * TemplateLexer
+ * This is the template file lexer.
+ * It is generated from the TemplateLexer.plex file
+ *
+ *
+ * @author Uwe Tews <uwe.tews@googlemail.com>
+ */
+class TemplateLexer
+{
+ /**
+ * Source
+ *
+ * @var string
+ */
+ public $data;
+
+ /**
+ * Source length
+ *
+ * @var int
+ */
+ public $dataLength = null;
+
+ /**
+ * byte counter
+ *
+ * @var int
+ */
+ public $counter;
+
+ /**
+ * token number
+ *
+ * @var int
+ */
+ public $token;
+
+ /**
+ * token value
+ *
+ * @var string
+ */
+ public $value;
+
+ /**
+ * current line
+ *
+ * @var int
+ */
+ public $line;
+
+ /**
+ * tag start line
+ *
+ * @var
+ */
+ public $taglineno;
+
+ /**
+ * state number
+ *
+ * @var int
+ */
+ public $state = 1;
+
+ /**
+ * Smarty object
+ *
+ * @var Smarty
+ */
+ public $smarty = null;
+
+ /**
+ * compiler object
+ *
+ * @var \Smarty\Compiler\Template
+ */
+ public $compiler = null;
+
+ /**
+ * trace file
+ *
+ * @var resource
+ */
+ public $yyTraceFILE;
+
+ /**
+ * trace prompt
+ *
+ * @var string
+ */
+ public $yyTracePrompt;
+
+ /**
+ * XML flag true while processing xml
+ *
+ * @var bool
+ */
+ public $is_xml = false;
+
+ /**
+ * state names
+ *
+ * @var array
+ */
+ public $state_name = array(1 => 'TEXT', 2 => 'TAG', 3 => 'TAGBODY', 4 => 'LITERAL', 5 => 'DOUBLEQUOTEDSTRING',);
+
+ /**
+ * token names
+ *
+ * @var array
+ */
+ public $smarty_token_names = array( // Text for parser error messages
+ 'NOT' => '(!,not)',
+ 'OPENP' => '(',
+ 'CLOSEP' => ')',
+ 'OPENB' => '[',
+ 'CLOSEB' => ']',
+ 'PTR' => '->',
+ 'APTR' => '=>',
+ 'EQUAL' => '=',
+ 'NUMBER' => 'number',
+ 'UNIMATH' => '+" , "-',
+ 'MATH' => '*" , "/" , "%',
+ 'INCDEC' => '++" , "--',
+ 'SPACE' => ' ',
+ 'DOLLAR' => '$',
+ 'SEMICOLON' => ';',
+ 'COLON' => ':',
+ 'DOUBLECOLON' => '::',
+ 'AT' => '@',
+ 'HATCH' => '#',
+ 'QUOTE' => '"',
+ 'BACKTICK' => '`',
+ 'VERT' => '"|" modifier',
+ 'DOT' => '.',
+ 'COMMA' => '","',
+ 'QMARK' => '"?"',
+ 'ID' => 'id, name',
+ 'TEXT' => 'text',
+ 'LDELSLASH' => '{/..} closing tag',
+ 'LDEL' => '{...} Smarty tag',
+ 'COMMENT' => 'comment',
+ 'AS' => 'as',
+ 'TO' => 'to',
+ 'LOGOP' => '"<", "==" ... logical operator',
+ 'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition',
+ 'SCOND' => '"is even" ... if condition',
+ );
+
+ /**
+ * literal tag nesting level
+ *
+ * @var int
+ */
+ private $literal_cnt = 0;
+
+ /**
+ * preg token pattern for state TEXT
+ *
+ * @var string
+ */
+ private $yy_global_pattern1 = null;
+
+ /**
+ * preg token pattern for state TAG
+ *
+ * @var string
+ */
+ private $yy_global_pattern2 = null;
+
+ /**
+ * preg token pattern for state TAGBODY
+ *
+ * @var string
+ */
+ private $yy_global_pattern3 = null;
+
+ /**
+ * preg token pattern for state LITERAL
+ *
+ * @var string
+ */
+ private $yy_global_pattern4 = null;
+
+ /**
+ * preg token pattern for state DOUBLEQUOTEDSTRING
+ *
+ * @var null
+ */
+ private $yy_global_pattern5 = null;
+
+ /**
+ * preg token pattern for text
+ *
+ * @var null
+ */
+ private $yy_global_text = null;
+
+ /**
+ * preg token pattern for literal
+ *
+ * @var null
+ */
+ private $yy_global_literal = null;
+
+ /**
+ * constructor
+ *
+ * @param string $source template source
+ * @param \Smarty\Compiler\Template $compiler
+ */
+ public function __construct($source, \Smarty\Compiler\Template $compiler)
+ {
+ $this->data = $source;
+ $this->dataLength = strlen($this->data);
+ $this->counter = 0;
+ if (preg_match('/^\xEF\xBB\xBF/i', $this->data, $match)) {
+ $this->counter += strlen($match[0]);
+ }
+ $this->line = 1;
+ $this->smarty = $compiler->getTemplate()->getSmarty();
+ $this->compiler = $compiler;
+ $this->compiler->initDelimiterPreg();
+ $this->smarty_token_names['LDEL'] = $this->smarty->getLeftDelimiter();
+ $this->smarty_token_names['RDEL'] = $this->smarty->getRightDelimiter();
+ }
+
+ /**
+ * open lexer/parser trace file
+ *
+ */
+ public function PrintTrace()
+ {
+ $this->yyTraceFILE = fopen('php://output', 'w');
+ $this->yyTracePrompt = '<br>';
+ }
+
+ /**
+ * replace placeholders with runtime preg code
+ *
+ * @param string $preg
+ *
+ * @return string
+ */
+ public function replace($preg)
+ {
+ return $this->compiler->replaceDelimiter($preg);
+ }
+
+ /**
+ * check if current value is an autoliteral left delimiter
+ *
+ * @return bool
+ */
+ public function isAutoLiteral()
+ {
+ return $this->smarty->getAutoLiteral() && isset($this->value[ $this->compiler->getLdelLength() ]) ?
+ strpos(" \n\t\r", $this->value[ $this->compiler->getLdelLength() ]) !== false : false;
+ }
+
+ /*!lex2php
+ %input $this->data
+ %counter $this->counter
+ %token $this->token
+ %value $this->value
+ %line $this->line
+ userliteral = ~(SMARTYldel)SMARTYautoliteral\s+SMARTYliteral~
+ char = ~[\S\s]~
+ textdoublequoted = ~([^"\\]*?)((?:\\.[^"\\]*?)*?)(?=((SMARTYldel)SMARTYal|\$|`\$|"SMARTYliteral))~
+ namespace = ~([0-9]*[a-zA-Z_]\w*)?(\\[0-9]*[a-zA-Z_]\w*)+~
+ emptyjava = ~[{][}]~
+ slash = ~[/]~
+ ldel = ~(SMARTYldel)SMARTYal~
+ rdel = ~\s*SMARTYrdel~
+ nocacherdel = ~(\s+nocache)?\s*SMARTYrdel~
+ smartyblockchildparent = ~[\$]smarty\.block\.(child|parent)~
+ integer = ~\d+~
+ hex = ~0[xX][0-9a-fA-F]+~
+ math = ~\s*([*]{1,2}|[%/^&]|[<>]{2})\s*~
+ comment = ~(SMARTYldel)SMARTYal[*]~
+ incdec = ~([+]|[-]){2}~
+ unimath = ~\s*([+]|[-])\s*~
+ openP = ~\s*[(]\s*~
+ closeP = ~\s*[)]~
+ openB = ~\[\s*~
+ closeB = ~\s*\]~
+ dollar = ~[$]~
+ dot = ~[.]~
+ comma = ~\s*[,]\s*~
+ doublecolon = ~[:]{2}~
+ colon = ~\s*[:]\s*~
+ at = ~[@]~
+ hatch = ~[#]~
+ semicolon = ~\s*[;]\s*~
+ equal = ~\s*[=]\s*~
+ space = ~\s+~
+ ptr = ~\s*[-][>]\s*~
+ aptr = ~\s*[=][>]\s*~
+ singlequotestring = ~'[^'\\]*(?:\\.[^'\\]*)*'~
+ backtick = ~[`]~
+ vert = ~[|][@]?~
+ qmark = ~\s*[?]\s*~
+ constant = ~[_]+[A-Z0-9][0-9A-Z_]*|[A-Z][0-9A-Z_]*(?![0-9A-Z_]*[a-z])~
+ attr = ~\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\s*[=]\s*~
+ id = ~[0-9]*[a-zA-Z_]\w*~
+ literal = ~literal~
+ strip = ~strip~
+ lop = ~\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\s*~
+ slop = ~\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\s+~
+ tlop = ~\s+is\s+(not\s+)?(odd|even|div)\s+by\s+~
+ scond = ~\s+is\s+(not\s+)?(odd|even)~
+ isin = ~\s+is\s+in\s+~
+ as = ~\s+as\s+~
+ to = ~\s+to\s+~
+ step = ~\s+step\s+~
+ if = ~(if|elseif|else if|while)\s+~
+ for = ~for\s+~
+ array = ~array~
+ foreach = ~foreach(?![^\s])~
+ setfilter = ~setfilter\s+~
+ instanceof = ~\s+instanceof\s+~
+ not = ~[!]\s*|not\s+~
+ typecast = ~[(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\s*~
+ double_quote = ~["]~
+ */
+ /*!lex2php
+ %statename TEXT
+ emptyjava {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ comment {
+ $to = $this->dataLength;
+ preg_match("/[*]{$this->compiler->getRdelPreg()}[\n]?/",$this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1] + strlen($match[0][0]);
+ } else {
+ $this->compiler->trigger_template_error ("missing or misspelled comment closing tag '{$this->smarty->getRightDelimiter()}'");
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ return false;
+ }
+ userliteral {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ ldel literal rdel {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERALSTART;
+ $this->yypushstate(self::LITERAL);
+ }
+ ldel slash literal rdel {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERALEND;
+ $this->yypushstate(self::LITERAL);
+ }
+ ldel {
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ char {
+ if (!isset($this->yy_global_text)) {
+ $this->yy_global_text = $this->replace('/(SMARTYldel)SMARTYal/isS');
+ }
+ $to = $this->dataLength;
+ preg_match($this->yy_global_text, $this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1];
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ */
+ /*!lex2php
+ %statename TAG
+ ldel if {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELIF;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ ldel for {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELFOR;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ ldel foreach {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELFOREACH;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ ldel setfilter {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELSETFILTER;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ ldel id nocacherdel {
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_SIMPLETAG;
+ $this->taglineno = $this->line;
+ }
+ ldel smartyblockchildparent rdel {
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_SMARTYBLOCKCHILDPARENT;
+ $this->taglineno = $this->line;
+ }
+ ldel slash id rdel {
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_CLOSETAG;
+ $this->taglineno = $this->line;
+ }
+ ldel dollar id nocacherdel {
+ if ($this->_yy_stack[count($this->_yy_stack)-1] === self::TEXT) {
+ $this->yypopstate();
+ $this->token = \Smarty\Parser\TemplateParser::TP_SIMPELOUTPUT;
+ $this->taglineno = $this->line;
+ } else {
+ $this->value = $this->smarty->getLeftDelimiter();
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDEL;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ }
+ ldel slash {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDELSLASH;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ ldel {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDEL;
+ $this->yybegin(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ */
+ /*!lex2php
+ %statename TAGBODY
+ rdel {
+ $this->token = \Smarty\Parser\TemplateParser::TP_RDEL;
+ $this->yypopstate();
+ }
+ ldel {
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ double_quote {
+ $this->token = \Smarty\Parser\TemplateParser::TP_QUOTE;
+ $this->yypushstate(self::DOUBLEQUOTEDSTRING);
+ $this->compiler->enterDoubleQuote();
+ }
+ singlequotestring {
+ $this->token = \Smarty\Parser\TemplateParser::TP_SINGLEQUOTESTRING;
+ }
+ dollar id {
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOLLARID;
+ }
+ dollar {
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOLLAR;
+ }
+ isin {
+ $this->token = \Smarty\Parser\TemplateParser::TP_ISIN;
+ }
+ as {
+ $this->token = \Smarty\Parser\TemplateParser::TP_AS;
+ }
+ to {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TO;
+ }
+ step {
+ $this->token = \Smarty\Parser\TemplateParser::TP_STEP;
+ }
+ instanceof {
+ $this->token = \Smarty\Parser\TemplateParser::TP_INSTANCEOF;
+ }
+ lop {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LOGOP;
+ }
+ slop {
+ $this->token = \Smarty\Parser\TemplateParser::TP_SLOGOP;
+ }
+ tlop {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TLOGOP;
+ }
+ scond {
+ $this->token = \Smarty\Parser\TemplateParser::TP_SINGLECOND;
+ }
+ not{
+ $this->token = \Smarty\Parser\TemplateParser::TP_NOT;
+ }
+ typecast {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TYPECAST;
+ }
+ openP {
+ $this->token = \Smarty\Parser\TemplateParser::TP_OPENP;
+ }
+ closeP {
+ $this->token = \Smarty\Parser\TemplateParser::TP_CLOSEP;
+ }
+ openB {
+ $this->token = \Smarty\Parser\TemplateParser::TP_OPENB;
+ }
+ closeB {
+ $this->token = \Smarty\Parser\TemplateParser::TP_CLOSEB;
+ }
+ ptr {
+ $this->token = \Smarty\Parser\TemplateParser::TP_PTR;
+ }
+ aptr {
+ $this->token = \Smarty\Parser\TemplateParser::TP_APTR;
+ }
+ equal {
+ $this->token = \Smarty\Parser\TemplateParser::TP_EQUAL;
+ }
+ incdec {
+ $this->token = \Smarty\Parser\TemplateParser::TP_INCDEC;
+ }
+ unimath {
+ $this->token = \Smarty\Parser\TemplateParser::TP_UNIMATH;
+ }
+ math {
+ $this->token = \Smarty\Parser\TemplateParser::TP_MATH;
+ }
+ at {
+ $this->token = \Smarty\Parser\TemplateParser::TP_AT;
+ }
+ array openP {
+ $this->token = \Smarty\Parser\TemplateParser::TP_ARRAYOPEN;
+ }
+ hatch {
+ $this->token = \Smarty\Parser\TemplateParser::TP_HATCH;
+ }
+ attr {
+ // resolve conflicts with shorttag and right_delimiter starting with '='
+ if (substr($this->data, $this->counter + strlen($this->value) - 1, $this->compiler->getRdelLength()) === $this->smarty->getRightDelimiter()) {
+ preg_match('/\s+/',$this->value,$match);
+ $this->value = $match[0];
+ $this->token = \Smarty\Parser\TemplateParser::TP_SPACE;
+ } else {
+ $this->token = \Smarty\Parser\TemplateParser::TP_ATTR;
+ }
+ }
+ namespace {
+ $this->token = \Smarty\Parser\TemplateParser::TP_NAMESPACE;
+ }
+ id {
+ $this->token = \Smarty\Parser\TemplateParser::TP_ID;
+ }
+ integer {
+ $this->token = \Smarty\Parser\TemplateParser::TP_INTEGER;
+ }
+ backtick {
+ $this->token = \Smarty\Parser\TemplateParser::TP_BACKTICK;
+ $this->yypopstate();
+ }
+ vert {
+ $this->token = \Smarty\Parser\TemplateParser::TP_VERT;
+ }
+ dot {
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOT;
+ }
+ comma {
+ $this->token = \Smarty\Parser\TemplateParser::TP_COMMA;
+ }
+ semicolon {
+ $this->token = \Smarty\Parser\TemplateParser::TP_SEMICOLON;
+ }
+ doublecolon {
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOUBLECOLON;
+ }
+ colon {
+ $this->token = \Smarty\Parser\TemplateParser::TP_COLON;
+ }
+ qmark {
+ $this->token = \Smarty\Parser\TemplateParser::TP_QMARK;
+ }
+ hex {
+ $this->token = \Smarty\Parser\TemplateParser::TP_HEX;
+ }
+ space {
+ $this->token = \Smarty\Parser\TemplateParser::TP_SPACE;
+ }
+ char {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ */
+
+ /*!lex2php
+ %statename LITERAL
+ ldel literal rdel {
+ $this->literal_cnt++;
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERAL;
+ }
+ ldel slash literal rdel {
+ if ($this->literal_cnt) {
+ $this->literal_cnt--;
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERAL;
+ } else {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERALEND;
+ $this->yypopstate();
+ }
+ }
+ char {
+ if (!isset($this->yy_global_literal)) {
+ $this->yy_global_literal = $this->replace('/(SMARTYldel)SMARTYal[\/]?literalSMARTYrdel/isS');
+ }
+ $to = $this->dataLength;
+ preg_match($this->yy_global_literal, $this->data,$match,PREG_OFFSET_CAPTURE,$this->counter);
+ if (isset($match[0][1])) {
+ $to = $match[0][1];
+ } else {
+ $this->compiler->trigger_template_error ("missing or misspelled literal closing tag");
+ }
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\TemplateParser::TP_LITERAL;
+ }
+ */
+ /*!lex2php
+ %statename DOUBLEQUOTEDSTRING
+ userliteral {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ ldel literal rdel {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ ldel slash literal rdel {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ ldel slash {
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ ldel id {
+ $this->yypushstate(self::TAG);
+ return true;
+ }
+ ldel {
+ $this->token = \Smarty\Parser\TemplateParser::TP_LDEL;
+ $this->taglineno = $this->line;
+ $this->yypushstate(self::TAGBODY);
+ }
+ double_quote {
+ $this->token = \Smarty\Parser\TemplateParser::TP_QUOTE;
+ $this->yypopstate();
+ }
+ backtick dollar {
+ $this->token = \Smarty\Parser\TemplateParser::TP_BACKTICK;
+ $this->value = substr($this->value,0,-1);
+ $this->yypushstate(self::TAGBODY);
+ $this->taglineno = $this->line;
+ }
+ dollar id {
+ $this->token = \Smarty\Parser\TemplateParser::TP_DOLLARID;
+ }
+ dollar {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ textdoublequoted {
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ char {
+ $to = $this->dataLength;
+ $this->value = substr($this->data,$this->counter,$to-$this->counter);
+ $this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+ }
+ */
+ }
+
+ \ No newline at end of file
diff --git a/src/ParseTree/Base.php b/src/ParseTree/Base.php
new file mode 100644
index 00000000..e1140c15
--- /dev/null
+++ b/src/ParseTree/Base.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Smarty\ParseTree;
+
+/**
+ * Smarty Internal Plugin Templateparser ParseTree
+ * These are classes to build parsetree in the template parser
+ *
+
+
+ * @author Thue Kristensen
+ * @author Uwe Tews
+ */
+
+/**
+
+
+ * @ignore
+ */
+abstract class Base
+{
+ /**
+ * Buffer content
+ *
+ * @var mixed
+ */
+ public $data;
+
+ /**
+ * Subtree array
+ *
+ * @var array
+ */
+ public $subtrees = array();
+
+ /**
+ * Return buffer
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string buffer content
+ */
+ abstract public function to_smarty_php(\Smarty\Parser\TemplateParser $parser);
+
+}
diff --git a/src/ParseTree/Code.php b/src/ParseTree/Code.php
new file mode 100644
index 00000000..b39f22c8
--- /dev/null
+++ b/src/ParseTree/Code.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Smarty\ParseTree;
+
+/**
+ * Smarty Internal Plugin Templateparser Parse Tree
+ * These are classes to build parse trees in the template parser
+ *
+
+
+ * @author Thue Kristensen
+ * @author Uwe Tews
+ */
+
+/**
+ * Code fragment inside a tag .
+ *
+
+
+ * @ignore
+ */
+class Code extends Base
+{
+ /**
+ * Create parse tree buffer for code fragment
+ *
+ * @param string $data content
+ */
+ public function __construct($data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Return buffer content in parentheses
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string content
+ */
+ public function to_smarty_php(\Smarty\Parser\TemplateParser $parser)
+ {
+ return sprintf('(%s)', $this->data);
+ }
+}
diff --git a/src/ParseTree/Dq.php b/src/ParseTree/Dq.php
new file mode 100644
index 00000000..27c3db9f
--- /dev/null
+++ b/src/ParseTree/Dq.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Smarty\ParseTree;
+/**
+ * Double-quoted string inside a tag.
+ *
+
+
+ * @ignore
+ */
+
+/**
+ * Double quoted string inside a tag.
+ *
+
+
+ * @ignore
+ */
+class Dq extends Base
+{
+ /**
+ * Create parse tree buffer for double-quoted string subtrees
+ *
+ * @param object $parser parser object
+ * @param Base $subtree parse tree buffer
+ */
+ public function __construct($parser, Base $subtree)
+ {
+ $this->subtrees[] = $subtree;
+ if ($subtree instanceof Tag) {
+ $parser->block_nesting_level = $parser->compiler->getTagStackCount();
+ }
+ }
+
+ /**
+ * Append buffer to subtree
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ * @param Base $subtree parse tree buffer
+ */
+ public function append_subtree(\Smarty\Parser\TemplateParser $parser, Base $subtree)
+ {
+ $last_subtree = count($this->subtrees) - 1;
+ if ($last_subtree >= 0 && $this->subtrees[ $last_subtree ] instanceof Tag
+ && $this->subtrees[ $last_subtree ]->saved_block_nesting < $parser->block_nesting_level
+ ) {
+ if ($subtree instanceof Code) {
+ $this->subtrees[ $last_subtree ]->data =
+ $parser->compiler->appendCode(
+ $this->subtrees[ $last_subtree ]->data,
+ '<?php echo ' . $subtree->data . ';?>'
+ );
+ } elseif ($subtree instanceof DqContent) {
+ $this->subtrees[ $last_subtree ]->data =
+ $parser->compiler->appendCode(
+ $this->subtrees[ $last_subtree ]->data,
+ '<?php echo "' . $subtree->data . '";?>'
+ );
+ } else {
+ $this->subtrees[ $last_subtree ]->data =
+ $parser->compiler->appendCode($this->subtrees[ $last_subtree ]->data, $subtree->data);
+ }
+ } else {
+ $this->subtrees[] = $subtree;
+ }
+ if ($subtree instanceof Tag) {
+ $parser->block_nesting_level = $parser->compiler->getTagStackCount();
+ }
+ }
+
+ /**
+ * Merge subtree buffer content together
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string compiled template code
+ */
+ public function to_smarty_php(\Smarty\Parser\TemplateParser $parser)
+ {
+ $code = '';
+ foreach ($this->subtrees as $subtree) {
+ if ($code !== '') {
+ $code .= '.';
+ }
+ if ($subtree instanceof Tag) {
+ $more_php = $subtree->assign_to_var($parser);
+ } else {
+ $more_php = $subtree->to_smarty_php($parser);
+ }
+ $code .= $more_php;
+ if (!$subtree instanceof DqContent) {
+ $parser->compiler->has_variable_string = true;
+ }
+ }
+ return $code;
+ }
+}
diff --git a/src/ParseTree/DqContent.php b/src/ParseTree/DqContent.php
new file mode 100644
index 00000000..f0a4b069
--- /dev/null
+++ b/src/ParseTree/DqContent.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Smarty\ParseTree;
+/**
+ * Smarty Internal Plugin Templateparser Parse Tree
+ * These are classes to build parse tree in the template parser
+ *
+
+
+ * @author Thue Kristensen
+ * @author Uwe Tews
+ */
+
+/**
+ * Raw chars as part of a double-quoted string.
+ *
+
+
+ * @ignore
+ */
+class DqContent extends Base
+{
+ /**
+ * Create parse tree buffer with string content
+ *
+ * @param string $data string section
+ */
+ public function __construct($data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Return content as double-quoted string
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string doubled quoted string
+ */
+ public function to_smarty_php(\Smarty\Parser\TemplateParser $parser)
+ {
+ return '"' . $this->data . '"';
+ }
+}
diff --git a/src/ParseTree/Tag.php b/src/ParseTree/Tag.php
new file mode 100644
index 00000000..f4bdee25
--- /dev/null
+++ b/src/ParseTree/Tag.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Smarty\ParseTree;
+
+/**
+ * Smarty Internal Plugin Templateparser Parse Tree
+ * These are classes to build parse tree in the template parser
+ *
+
+
+ * @author Thue Kristensen
+ * @author Uwe Tews
+ */
+
+/**
+ * A complete smarty tag.
+ *
+
+
+ * @ignore
+ */
+class Tag extends Base
+{
+ /**
+ * Saved block nesting level
+ *
+ * @var int
+ */
+ public $saved_block_nesting;
+
+ /**
+ * Create parse tree buffer for Smarty tag
+ *
+ * @param \Smarty\Parser\TemplateParser $parser parser object
+ * @param string $data content
+ */
+ public function __construct(\Smarty\Parser\TemplateParser $parser, $data)
+ {
+ $this->data = $data;
+ $this->saved_block_nesting = $parser->block_nesting_level;
+ }
+
+ /**
+ * Return buffer content
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string content
+ */
+ public function to_smarty_php(\Smarty\Parser\TemplateParser $parser)
+ {
+ return $this->data;
+ }
+
+ /**
+ * Return complied code that loads the evaluated output of buffer content into a temporary variable
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string template code
+ */
+ public function assign_to_var(\Smarty\Parser\TemplateParser $parser)
+ {
+ $var = $parser->compiler->getNewPrefixVariable();
+ $tmp = $parser->compiler->appendCode('<?php ob_start();?>', $this->data);
+ $tmp = $parser->compiler->appendCode($tmp, "<?php {$var}=ob_get_clean();?>");
+ $parser->compiler->appendPrefixCode((string) $tmp);
+ return $var;
+ }
+}
diff --git a/src/ParseTree/Template.php b/src/ParseTree/Template.php
new file mode 100644
index 00000000..47096392
--- /dev/null
+++ b/src/ParseTree/Template.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace Smarty\ParseTree;
+
+/**
+ * Smarty Internal Plugin Templateparser Parse Tree
+ * These are classes to build parse tree in the template parser
+ *
+
+
+ * @author Thue Kristensen
+ * @author Uwe Tews
+ */
+
+/**
+ * Template element
+ *
+
+
+ * @ignore
+ */
+class Template extends Base
+{
+ /**
+ * Array of template elements
+ *
+ * @var array
+ */
+ public $subtrees = array();
+
+ /**
+ * Create root of parse tree for template elements
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Append buffer to subtree
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ * @param Base $subtree
+ */
+ public function append_subtree(\Smarty\Parser\TemplateParser $parser, Base $subtree)
+ {
+ if (!empty($subtree->subtrees)) {
+ $this->subtrees = array_merge($this->subtrees, $subtree->subtrees);
+ } else {
+ if ($subtree->data !== '') {
+ $this->subtrees[] = $subtree;
+ }
+ }
+ }
+
+ /**
+ * Append array to subtree
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ * @param Base[] $array
+ */
+ public function append_array(\Smarty\Parser\TemplateParser $parser, $array = array())
+ {
+ if (!empty($array)) {
+ $this->subtrees = array_merge($this->subtrees, (array)$array);
+ }
+ }
+
+ /**
+ * Prepend array to subtree
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ * @param Base[] $array
+ */
+ public function prepend_array(\Smarty\Parser\TemplateParser $parser, $array = array())
+ {
+ if (!empty($array)) {
+ $this->subtrees = array_merge((array)$array, $this->subtrees);
+ }
+ }
+
+ /**
+ * Sanitize and merge subtree buffers together
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string template code content
+ */
+ public function to_smarty_php(\Smarty\Parser\TemplateParser $parser)
+ {
+ $code = '';
+
+ foreach ($this->getChunkedSubtrees() as $chunk) {
+ $text = '';
+ switch ($chunk['mode']) {
+ case 'textstripped':
+ foreach ($chunk['subtrees'] as $subtree) {
+ $text .= $subtree->to_smarty_php($parser);
+ }
+ $code .= preg_replace(
+ '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/',
+ "<?php echo '\$1'; ?>\n",
+ $parser->compiler->processText($text)
+ );
+ break;
+ case 'text':
+ foreach ($chunk['subtrees'] as $subtree) {
+ $text .= $subtree->to_smarty_php($parser);
+ }
+ $code .= preg_replace(
+ '/((<%)|(%>)|(<\?php)|(<\?)|(\?>)|(<\/?script))/',
+ "<?php echo '\$1'; ?>\n",
+ $text
+ );
+ break;
+ case 'tag':
+ foreach ($chunk['subtrees'] as $subtree) {
+ $text = $parser->compiler->appendCode($text, $subtree->to_smarty_php($parser));
+ }
+ $code .= $text;
+ break;
+ default:
+ foreach ($chunk['subtrees'] as $subtree) {
+ $text = $subtree->to_smarty_php($parser);
+ }
+ $code .= $text;
+
+ }
+ }
+ return $code;
+ }
+
+ private function getChunkedSubtrees() {
+ $chunks = array();
+ $currentMode = null;
+ $currentChunk = array();
+ for ($key = 0, $cnt = count($this->subtrees); $key < $cnt; $key++) {
+
+ if ($this->subtrees[ $key ]->data === '' && in_array($currentMode, array('textstripped', 'text', 'tag'))) {
+ continue;
+ }
+
+ if ($this->subtrees[ $key ] instanceof Text
+ && $this->subtrees[ $key ]->isToBeStripped()) {
+ $newMode = 'textstripped';
+ } elseif ($this->subtrees[ $key ] instanceof Text) {
+ $newMode = 'text';
+ } elseif ($this->subtrees[ $key ] instanceof Tag) {
+ $newMode = 'tag';
+ } else {
+ $newMode = 'other';
+ }
+
+ if ($newMode == $currentMode) {
+ $currentChunk[] = $this->subtrees[ $key ];
+ } else {
+ $chunks[] = array(
+ 'mode' => $currentMode,
+ 'subtrees' => $currentChunk
+ );
+ $currentMode = $newMode;
+ $currentChunk = array($this->subtrees[ $key ]);
+ }
+ }
+ if ($currentMode && $currentChunk) {
+ $chunks[] = array(
+ 'mode' => $currentMode,
+ 'subtrees' => $currentChunk
+ );
+ }
+ return $chunks;
+ }
+}
diff --git a/src/ParseTree/Text.php b/src/ParseTree/Text.php
new file mode 100644
index 00000000..e6131407
--- /dev/null
+++ b/src/ParseTree/Text.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Smarty\ParseTree;
+
+/**
+ * Smarty Internal Plugin Templateparser Parse Tree
+ * These are classes to build parse tree in the template parser
+ *
+
+
+ * @author Thue Kristensen
+ * @author Uwe Tews
+ * *
+ * template text
+
+
+ * @ignore
+ */
+class Text extends Base
+{
+
+ /**
+ * Wether this section should be stripped on output to smarty php
+ * @var bool
+ */
+ private $toBeStripped = false;
+
+ /**
+ * Create template text buffer
+ *
+ * @param string $data text
+ * @param bool $toBeStripped wether this section should be stripped on output to smarty php
+ */
+ public function __construct($data, $toBeStripped = false)
+ {
+ $this->data = $data;
+ $this->toBeStripped = $toBeStripped;
+ }
+
+ /**
+ * Wether this section should be stripped on output to smarty php
+ * @return bool
+ */
+ public function isToBeStripped() {
+ return $this->toBeStripped;
+ }
+
+ /**
+ * Return buffer content
+ *
+ * @param \Smarty\Parser\TemplateParser $parser
+ *
+ * @return string text
+ */
+ public function to_smarty_php(\Smarty\Parser\TemplateParser $parser)
+ {
+ return $this->data;
+ }
+}
diff --git a/src/Parser/ConfigfileParser.php b/src/Parser/ConfigfileParser.php
new file mode 100644
index 00000000..7997a098
--- /dev/null
+++ b/src/Parser/ConfigfileParser.php
@@ -0,0 +1,972 @@
+<?php
+
+// line 12 "src/Parser/ConfigfileParser.y"
+
+
+namespace Smarty\Parser;
+
+use \Smarty\Lexer\ConfigfileLexer as Lexer;
+use \Smarty\Compiler\Configfile as Configfile;
+
+/**
+* Smarty Internal Plugin Configfileparse
+*
+* This is the config file parser.
+* It is generated from the ConfigfileParser.y file
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews
+*/
+class ConfigfileParser
+{
+// line 31 "src/Parser/ConfigfileParser.y"
+
+ /**
+ * result status
+ *
+ * @var bool
+ */
+ public $successful = true;
+ /**
+ * return value
+ *
+ * @var mixed
+ */
+ public $retvalue = 0;
+ /**
+ * @var
+ */
+ public $yymajor;
+ /**
+ * lexer object
+ *
+ * @var Lexer
+ */
+ private $lex;
+ /**
+ * internal error flag
+ *
+ * @var bool
+ */
+ private $internalError = false;
+ /**
+ * compiler object
+ *
+ * @var Configfile
+ */
+ public $compiler = null;
+ /**
+ * smarty object
+ *
+ * @var Smarty
+ */
+ public $smarty = null;
+ /**
+ * copy of config_overwrite property
+ *
+ * @var bool
+ */
+ private $configOverwrite = false;
+ /**
+ * copy of config_read_hidden property
+ *
+ * @var bool
+ */
+ private $configReadHidden = false;
+ /**
+ * helper map
+ *
+ * @var array
+ */
+ private static $escapes_single = array('\\' => '\\',
+ '\'' => '\'');
+
+ /**
+ * constructor
+ *
+ * @param Lexer $lex
+ * @param Configfile $compiler
+ */
+ public function __construct(Lexer $lex, Configfile $compiler)
+ {
+ $this->lex = $lex;
+ $this->smarty = $compiler->getSmarty();
+ $this->compiler = $compiler;
+ $this->configOverwrite = $this->smarty->config_overwrite;
+ $this->configReadHidden = $this->smarty->config_read_hidden;
+ }
+
+ /**
+ * parse optional boolean keywords
+ *
+ * @param string $str
+ *
+ * @return bool
+ */
+ private function parse_bool($str)
+ {
+ $str = strtolower($str);
+ if (in_array($str, array('on', 'yes', 'true'))) {
+ $res = true;
+ } else {
+ $res = false;
+ }
+ return $res;
+ }
+
+ /**
+ * parse single quoted string
+ * remove outer quotes
+ * unescape inner quotes
+ *
+ * @param string $qstr
+ *
+ * @return string
+ */
+ private static function parse_single_quoted_string($qstr)
+ {
+ $escaped_string = substr($qstr, 1, strlen($qstr) - 2); //remove outer quotes
+
+ $ss = preg_split('/(\\\\.)/', $escaped_string, - 1, PREG_SPLIT_DELIM_CAPTURE);
+
+ $str = '';
+ foreach ($ss as $s) {
+ if (strlen($s) === 2 && $s[0] === '\\') {
+ if (isset(self::$escapes_single[$s[1]])) {
+ $s = self::$escapes_single[$s[1]];
+ }
+ }
+ $str .= $s;
+ }
+ return $str;
+ }
+
+ /**
+ * parse double quoted string
+ *
+ * @param string $qstr
+ *
+ * @return string
+ */
+ private static function parse_double_quoted_string($qstr)
+ {
+ $inner_str = substr($qstr, 1, strlen($qstr) - 2);
+ return stripcslashes($inner_str);
+ }
+
+ /**
+ * parse triple quoted string
+ *
+ * @param string $qstr
+ *
+ * @return string
+ */
+ private static function parse_tripple_double_quoted_string($qstr)
+ {
+ return stripcslashes($qstr);
+ }
+
+ /**
+ * set a config variable in target array
+ *
+ * @param array $var
+ * @param array $target_array
+ */
+ private function set_var(array $var, array &$target_array)
+ {
+ $key = $var['key'];
+ $value = $var['value'];
+
+ if ($this->configOverwrite || !isset($target_array['vars'][$key])) {
+ $target_array['vars'][$key] = $value;
+ } else {
+ settype($target_array['vars'][$key], 'array');
+ $target_array['vars'][$key][] = $value;
+ }
+ }
+
+ /**
+ * add config variable to global vars
+ *
+ * @param array $vars
+ */
+ private function add_global_vars(array $vars)
+ {
+ if (!isset($this->compiler->config_data['vars'])) {
+ $this->compiler->config_data['vars'] = array();
+ }
+ foreach ($vars as $var) {
+ $this->set_var($var, $this->compiler->config_data);
+ }
+ }
+
+ /**
+ * add config variable to section
+ *
+ * @param string $section_name
+ * @param array $vars
+ */
+ private function add_section_vars($section_name, array $vars)
+ {
+ if (!isset($this->compiler->config_data['sections'][$section_name]['vars'])) {
+ $this->compiler->config_data['sections'][$section_name]['vars'] = array();
+ }
+ foreach ($vars as $var) {
+ $this->set_var($var, $this->compiler->config_data['sections'][$section_name]);
+ }
+ }
+
+ const TPC_OPENB = 1;
+ const TPC_SECTION = 2;
+ const TPC_CLOSEB = 3;
+ const TPC_DOT = 4;
+ const TPC_ID = 5;
+ const TPC_EQUAL = 6;
+ const TPC_FLOAT = 7;
+ const TPC_INT = 8;
+ const TPC_BOOL = 9;
+ const TPC_SINGLE_QUOTED_STRING = 10;
+ const TPC_DOUBLE_QUOTED_STRING = 11;
+ const TPC_TRIPPLE_QUOTES = 12;
+ const TPC_TRIPPLE_TEXT = 13;
+ const TPC_TRIPPLE_QUOTES_END = 14;
+ const TPC_NAKED_STRING = 15;
+ const TPC_OTHER = 16;
+ const TPC_NEWLINE = 17;
+ const TPC_COMMENTSTART = 18;
+ const YY_NO_ACTION = 60;
+ const YY_ACCEPT_ACTION = 59;
+ const YY_ERROR_ACTION = 58;
+
+ const YY_SZ_ACTTAB = 39;
+public static $yy_action = array(
+ 24, 25, 26, 27, 28, 12, 15, 23, 31, 32,
+ 59, 8, 9, 3, 21, 22, 33, 13, 33, 13,
+ 14, 10, 18, 16, 30, 11, 17, 20, 34, 7,
+ 5, 1, 2, 29, 4, 19, 52, 35, 6,
+ );
+ public static $yy_lookahead = array(
+ 7, 8, 9, 10, 11, 12, 5, 27, 15, 16,
+ 20, 21, 25, 23, 25, 26, 17, 18, 17, 18,
+ 2, 25, 4, 13, 14, 1, 15, 24, 17, 22,
+ 3, 23, 23, 14, 6, 2, 28, 17, 3,
+);
+ const YY_SHIFT_USE_DFLT = -8;
+ const YY_SHIFT_MAX = 19;
+ public static $yy_shift_ofst = array(
+ -8, 1, 1, 1, -7, -1, -1, 24, -8, -8,
+ -8, 18, 10, 11, 27, 28, 19, 20, 33, 35,
+);
+ const YY_REDUCE_USE_DFLT = -21;
+ const YY_REDUCE_MAX = 10;
+ public static $yy_reduce_ofst = array(
+ -10, -11, -11, -11, -20, -13, -4, 3, 7, 8,
+ 9,
+);
+ public static $yyExpectedTokens = array(
+ array(),
+ array(5, 17, 18, ),
+ array(5, 17, 18, ),
+ array(5, 17, 18, ),
+ array(7, 8, 9, 10, 11, 12, 15, 16, ),
+ array(17, 18, ),
+ array(17, 18, ),
+ array(1, ),
+ array(),
+ array(),
+ array(),
+ array(2, 4, ),
+ array(13, 14, ),
+ array(15, 17, ),
+ array(3, ),
+ array(6, ),
+ array(14, ),
+ array(17, ),
+ array(2, ),
+ array(3, ),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+);
+ public static $yy_default = array(
+ 44, 40, 41, 37, 58, 58, 58, 36, 39, 44,
+ 44, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+ 38, 42, 43, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57,
+);
+ const YYNOCODE = 29;
+ const YYSTACKDEPTH = 100;
+ const YYNSTATE = 36;
+ const YYNRULE = 22;
+ const YYERRORSYMBOL = 19;
+ const YYERRSYMDT = 'yy0';
+ const YYFALLBACK = 0;
+ public static $yyFallback = array(
+ );
+ public function Trace($TraceFILE, $zTracePrompt)
+ {
+ if (!$TraceFILE) {
+ $zTracePrompt = 0;
+ } elseif (!$zTracePrompt) {
+ $TraceFILE = 0;
+ }
+ $this->yyTraceFILE = $TraceFILE;
+ $this->yyTracePrompt = $zTracePrompt;
+ }
+
+ public function PrintTrace()
+ {
+ $this->yyTraceFILE = fopen('php://output', 'w');
+ $this->yyTracePrompt = '<br>';
+ }
+
+ public $yyTraceFILE;
+ public $yyTracePrompt;
+ public $yyidx; /* Index of top element in stack */
+ public $yyerrcnt; /* Shifts left before out of the error */
+ public $yystack = array(); /* The parser's stack */
+
+ public $yyTokenName = array(
+ '$', 'OPENB', 'SECTION', 'CLOSEB',
+ 'DOT', 'ID', 'EQUAL', 'FLOAT',
+ 'INT', 'BOOL', 'SINGLE_QUOTED_STRING', 'DOUBLE_QUOTED_STRING',
+ 'TRIPPLE_QUOTES', 'TRIPPLE_TEXT', 'TRIPPLE_QUOTES_END', 'NAKED_STRING',
+ 'OTHER', 'NEWLINE', 'COMMENTSTART', 'error',
+ 'start', 'global_vars', 'sections', 'var_list',
+ 'section', 'newline', 'var', 'value',
+ );
+
+ public static $yyRuleName = array(
+ 'start ::= global_vars sections',
+ 'global_vars ::= var_list',
+ 'sections ::= sections section',
+ 'sections ::=',
+ 'section ::= OPENB SECTION CLOSEB newline var_list',
+ 'section ::= OPENB DOT SECTION CLOSEB newline var_list',
+ 'var_list ::= var_list newline',
+ 'var_list ::= var_list var',
+ 'var_list ::=',
+ 'var ::= ID EQUAL value',
+ 'value ::= FLOAT',
+ 'value ::= INT',
+ 'value ::= BOOL',
+ 'value ::= SINGLE_QUOTED_STRING',
+ 'value ::= DOUBLE_QUOTED_STRING',
+ 'value ::= TRIPPLE_QUOTES TRIPPLE_TEXT TRIPPLE_QUOTES_END',
+ 'value ::= TRIPPLE_QUOTES TRIPPLE_QUOTES_END',
+ 'value ::= NAKED_STRING',
+ 'value ::= OTHER',
+ 'newline ::= NEWLINE',
+ 'newline ::= COMMENTSTART NEWLINE',
+ 'newline ::= COMMENTSTART NAKED_STRING NEWLINE',
+ );
+
+ public function tokenName($tokenType)
+ {
+ if ($tokenType === 0) {
+ return 'End of Input';
+ }
+ if ($tokenType > 0 && $tokenType < count($this->yyTokenName)) {
+ return $this->yyTokenName[$tokenType];
+ } else {
+ return 'Unknown';
+ }
+ }
+
+ public static function yy_destructor($yymajor, $yypminor)
+ {
+ switch ($yymajor) {
+ default: break; /* If no destructor action specified: do nothing */
+ }
+ }
+
+ public function yy_pop_parser_stack()
+ {
+ if (empty($this->yystack)) {
+ return;
+ }
+ $yytos = array_pop($this->yystack);
+ if ($this->yyTraceFILE && $this->yyidx >= 0) {
+ fwrite($this->yyTraceFILE,
+ $this->yyTracePrompt . 'Popping ' . $this->yyTokenName[$yytos->major] .
+ "\n");
+ }
+ $yymajor = $yytos->major;
+ self::yy_destructor($yymajor, $yytos->minor);
+ $this->yyidx--;
+
+ return $yymajor;
+ }
+
+ public function __destruct()
+ {
+ while ($this->yystack !== Array()) {
+ $this->yy_pop_parser_stack();
+ }
+ if (is_resource($this->yyTraceFILE)) {
+ fclose($this->yyTraceFILE);
+ }
+ }
+
+ public function yy_get_expected_tokens($token)
+ {
+ static $res3 = array();
+ static $res4 = array();
+ $state = $this->yystack[$this->yyidx]->stateno;
+ $expected = self::$yyExpectedTokens[$state];
+ if (isset($res3[$state][$token])) {
+ if ($res3[$state][$token]) {
+ return $expected;
+ }
+ } else {
+ if ($res3[$state][$token] = in_array($token, self::$yyExpectedTokens[$state], true)) {
+ return $expected;
+ }
+ }
+ $stack = $this->yystack;
+ $yyidx = $this->yyidx;
+ do {
+ $yyact = $this->yy_find_shift_action($token);
+ if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+ // reduce action
+ $done = 0;
+ do {
+ if ($done++ === 100) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // too much recursion prevents proper detection
+ // so give up
+ return array_unique($expected);
+ }
+ $yyruleno = $yyact - self::YYNSTATE;
+ $this->yyidx -= self::$yyRuleInfo[$yyruleno][1];
+ $nextstate = $this->yy_find_reduce_action(
+ $this->yystack[$this->yyidx]->stateno,
+ self::$yyRuleInfo[$yyruleno][0]);
+ if (isset(self::$yyExpectedTokens[$nextstate])) {
+ $expected = array_merge($expected, self::$yyExpectedTokens[$nextstate]);
+ if (isset($res4[$nextstate][$token])) {
+ if ($res4[$nextstate][$token]) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return array_unique($expected);
+ }
+ } else {
+ if ($res4[$nextstate][$token] = in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return array_unique($expected);
+ }
+ }
+ }
+ if ($nextstate < self::YYNSTATE) {
+ // we need to shift a non-terminal
+ $this->yyidx++;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = $nextstate;
+ $x->major = self::$yyRuleInfo[$yyruleno][0];
+ $this->yystack[$this->yyidx] = $x;
+ continue 2;
+ } elseif ($nextstate === self::YYNSTATE + self::YYNRULE + 1) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // the last token was just ignored, we can't accept
+ // by ignoring input, this is in essence ignoring a
+ // syntax error!
+ return array_unique($expected);
+ } elseif ($nextstate === self::YY_NO_ACTION) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // input accepted, but not shifted (I guess)
+ return $expected;
+ } else {
+ $yyact = $nextstate;
+ }
+ } while (true);
+ }
+ break;
+ } while (true);
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+
+ return array_unique($expected);
+ }
+
+ public function yy_is_expected_token($token)
+ {
+ static $res = array();
+ static $res2 = array();
+ if ($token === 0) {
+ return true; // 0 is not part of this
+ }
+ $state = $this->yystack[$this->yyidx]->stateno;
+ if (isset($res[$state][$token])) {
+ if ($res[$state][$token]) {
+ return true;
+ }
+ } else {
+ if ($res[$state][$token] = in_array($token, self::$yyExpectedTokens[$state], true)) {
+ return true;
+ }
+ }
+ $stack = $this->yystack;
+ $yyidx = $this->yyidx;
+ do {
+ $yyact = $this->yy_find_shift_action($token);
+ if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+ // reduce action
+ $done = 0;
+ do {
+ if ($done++ === 100) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // too much recursion prevents proper detection
+ // so give up
+ return true;
+ }
+ $yyruleno = $yyact - self::YYNSTATE;
+ $this->yyidx -= self::$yyRuleInfo[$yyruleno][1];
+ $nextstate = $this->yy_find_reduce_action(
+ $this->yystack[$this->yyidx]->stateno,
+ self::$yyRuleInfo[$yyruleno][0]);
+ if (isset($res2[$nextstate][$token])) {
+ if ($res2[$nextstate][$token]) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return true;
+ }
+ } else {
+ if ($res2[$nextstate][$token] = (isset(self::$yyExpectedTokens[$nextstate]) && in_array($token, self::$yyExpectedTokens[$nextstate], true))) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return true;
+ }
+ }
+ if ($nextstate < self::YYNSTATE) {
+ // we need to shift a non-terminal
+ $this->yyidx++;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = $nextstate;
+ $x->major = self::$yyRuleInfo[$yyruleno][0];
+ $this->yystack[$this->yyidx] = $x;
+ continue 2;
+ } elseif ($nextstate === self::YYNSTATE + self::YYNRULE + 1) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ if (!$token) {
+ // end of input: this is valid
+ return true;
+ }
+ // the last token was just ignored, we can't accept
+ // by ignoring input, this is in essence ignoring a
+ // syntax error!
+ return false;
+ } elseif ($nextstate === self::YY_NO_ACTION) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // input accepted, but not shifted (I guess)
+ return true;
+ } else {
+ $yyact = $nextstate;
+ }
+ } while (true);
+ }
+ break;
+ } while (true);
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+
+ return true;
+ }
+
+ public function yy_find_shift_action($iLookAhead)
+ {
+ $stateno = $this->yystack[$this->yyidx]->stateno;
+
+ /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
+ if (!isset(self::$yy_shift_ofst[$stateno])) {
+ // no shift actions
+ return self::$yy_default[$stateno];
+ }
+ $i = self::$yy_shift_ofst[$stateno];
+ if ($i === self::YY_SHIFT_USE_DFLT) {
+ return self::$yy_default[$stateno];
+ }
+ if ($iLookAhead === self::YYNOCODE) {
+ return self::YY_NO_ACTION;
+ }
+ $i += $iLookAhead;
+ if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+ self::$yy_lookahead[$i] != $iLookAhead) {
+ if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
+ && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
+ if ($this->yyTraceFILE) {
+ fwrite($this->yyTraceFILE, $this->yyTracePrompt . 'FALLBACK ' .
+ $this->yyTokenName[$iLookAhead] . ' => ' .
+ $this->yyTokenName[$iFallback] . "\n");
+ }
+
+ return $this->yy_find_shift_action($iFallback);
+ }
+
+ return self::$yy_default[$stateno];
+ } else {
+ return self::$yy_action[$i];
+ }
+ }
+
+ public function yy_find_reduce_action($stateno, $iLookAhead)
+ {
+ /* $stateno = $this->yystack[$this->yyidx]->stateno; */
+
+ if (!isset(self::$yy_reduce_ofst[$stateno])) {
+ return self::$yy_default[$stateno];
+ }
+ $i = self::$yy_reduce_ofst[$stateno];
+ if ($i === self::YY_REDUCE_USE_DFLT) {
+ return self::$yy_default[$stateno];
+ }
+ if ($iLookAhead === self::YYNOCODE) {
+ return self::YY_NO_ACTION;
+ }
+ $i += $iLookAhead;
+ if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+ self::$yy_lookahead[$i] != $iLookAhead) {
+ return self::$yy_default[$stateno];
+ } else {
+ return self::$yy_action[$i];
+ }
+ }
+
+ public function yy_shift($yyNewState, $yyMajor, $yypMinor)
+ {
+ $this->yyidx++;
+ if ($this->yyidx >= self::YYSTACKDEPTH) {
+ $this->yyidx--;
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sStack Overflow!\n", $this->yyTracePrompt);
+ }
+ while ($this->yyidx >= 0) {
+ $this->yy_pop_parser_stack();
+ }
+// line 245 "src/Parser/ConfigfileParser.y"
+
+ $this->internalError = true;
+ $this->compiler->trigger_config_file_error('Stack overflow in configfile parser');
+
+ return;
+ }
+ $yytos = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $yytos->stateno = $yyNewState;
+ $yytos->major = $yyMajor;
+ $yytos->minor = $yypMinor;
+ $this->yystack[] = $yytos;
+ if ($this->yyTraceFILE && $this->yyidx > 0) {
+ fprintf($this->yyTraceFILE, "%sShift %d\n", $this->yyTracePrompt,
+ $yyNewState);
+ fprintf($this->yyTraceFILE, "%sStack:", $this->yyTracePrompt);
+ for ($i = 1; $i <= $this->yyidx; $i++) {
+ fprintf($this->yyTraceFILE, " %s",
+ $this->yyTokenName[$this->yystack[$i]->major]);
+ }
+ fwrite($this->yyTraceFILE,"\n");
+ }
+ }
+
+ public static $yyRuleInfo = array(
+ array( 0 => 20, 1 => 2 ),
+ array( 0 => 21, 1 => 1 ),
+ array( 0 => 22, 1 => 2 ),
+ array( 0 => 22, 1 => 0 ),
+ array( 0 => 24, 1 => 5 ),
+ array( 0 => 24, 1 => 6 ),
+ array( 0 => 23, 1 => 2 ),
+ array( 0 => 23, 1 => 2 ),
+ array( 0 => 23, 1 => 0 ),
+ array( 0 => 26, 1 => 3 ),
+ array( 0 => 27, 1 => 1 ),
+ array( 0 => 27, 1 => 1 ),
+ array( 0 => 27, 1 => 1 ),
+ array( 0 => 27, 1 => 1 ),
+ array( 0 => 27, 1 => 1 ),
+ array( 0 => 27, 1 => 3 ),
+ array( 0 => 27, 1 => 2 ),
+ array( 0 => 27, 1 => 1 ),
+ array( 0 => 27, 1 => 1 ),
+ array( 0 => 25, 1 => 1 ),
+ array( 0 => 25, 1 => 2 ),
+ array( 0 => 25, 1 => 3 ),
+ );
+
+ public static $yyReduceMap = array(
+ 0 => 0,
+ 2 => 0,
+ 3 => 0,
+ 19 => 0,
+ 20 => 0,
+ 21 => 0,
+ 1 => 1,
+ 4 => 4,
+ 5 => 5,
+ 6 => 6,
+ 7 => 7,
+ 8 => 8,
+ 9 => 9,
+ 10 => 10,
+ 11 => 11,
+ 12 => 12,
+ 13 => 13,
+ 14 => 14,
+ 15 => 15,
+ 16 => 16,
+ 17 => 17,
+ 18 => 17,
+ );
+// line 251 "src/Parser/ConfigfileParser.y"
+ public function yy_r0(){
+ $this->_retvalue = null;
+ }
+// line 256 "src/Parser/ConfigfileParser.y"
+ public function yy_r1(){
+ $this->add_global_vars($this->yystack[$this->yyidx + 0]->minor);
+ $this->_retvalue = null;
+ }
+// line 270 "src/Parser/ConfigfileParser.y"
+ public function yy_r4(){
+ $this->add_section_vars($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + 0]->minor);
+ $this->_retvalue = null;
+ }
+// line 275 "src/Parser/ConfigfileParser.y"
+ public function yy_r5(){
+ if ($this->configReadHidden) {
+ $this->add_section_vars($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + 0]->minor);
+ }
+ $this->_retvalue = null;
+ }
+// line 283 "src/Parser/ConfigfileParser.y"
+ public function yy_r6(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;
+ }
+// line 287 "src/Parser/ConfigfileParser.y"
+ public function yy_r7(){
+ $this->_retvalue = array_merge($this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 291 "src/Parser/ConfigfileParser.y"
+ public function yy_r8(){
+ $this->_retvalue = array();
+ }
+// line 297 "src/Parser/ConfigfileParser.y"
+ public function yy_r9(){
+ $this->_retvalue = array('key' => $this->yystack[$this->yyidx + -2]->minor, 'value' => $this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 302 "src/Parser/ConfigfileParser.y"
+ public function yy_r10(){
+ $this->_retvalue = (float) $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 306 "src/Parser/ConfigfileParser.y"
+ public function yy_r11(){
+ $this->_retvalue = (int) $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 310 "src/Parser/ConfigfileParser.y"
+ public function yy_r12(){
+ $this->_retvalue = $this->parse_bool($this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 314 "src/Parser/ConfigfileParser.y"
+ public function yy_r13(){
+ $this->_retvalue = self::parse_single_quoted_string($this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 318 "src/Parser/ConfigfileParser.y"
+ public function yy_r14(){
+ $this->_retvalue = self::parse_double_quoted_string($this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 322 "src/Parser/ConfigfileParser.y"
+ public function yy_r15(){
+ $this->_retvalue = self::parse_tripple_double_quoted_string($this->yystack[$this->yyidx + -1]->minor);
+ }
+// line 326 "src/Parser/ConfigfileParser.y"
+ public function yy_r16(){
+ $this->_retvalue = '';
+ }
+// line 330 "src/Parser/ConfigfileParser.y"
+ public function yy_r17(){
+ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
+ }
+
+ private $_retvalue;
+
+ public function yy_reduce($yyruleno)
+ {
+ if ($this->yyTraceFILE && $yyruleno >= 0
+ && $yyruleno < count(self::$yyRuleName)) {
+ fprintf($this->yyTraceFILE, "%sReduce (%d) [%s].\n",
+ $this->yyTracePrompt, $yyruleno,
+ self::$yyRuleName[$yyruleno]);
+ }
+
+ $this->_retvalue = $yy_lefthand_side = null;
+ if (isset(self::$yyReduceMap[$yyruleno])) {
+ // call the action
+ $this->_retvalue = null;
+ $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
+ $yy_lefthand_side = $this->_retvalue;
+ }
+ $yygoto = self::$yyRuleInfo[$yyruleno][0];
+ $yysize = self::$yyRuleInfo[$yyruleno][1];
+ $this->yyidx -= $yysize;
+ for ($i = $yysize; $i; $i--) {
+ // pop all of the right-hand side parameters
+ array_pop($this->yystack);
+ }
+ $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
+ if ($yyact < self::YYNSTATE) {
+ if (!$this->yyTraceFILE && $yysize) {
+ $this->yyidx++;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = $yyact;
+ $x->major = $yygoto;
+ $x->minor = $yy_lefthand_side;
+ $this->yystack[$this->yyidx] = $x;
+ } else {
+ $this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
+ }
+ } elseif ($yyact === self::YYNSTATE + self::YYNRULE + 1) {
+ $this->yy_accept();
+ }
+ }
+
+ public function yy_parse_failed()
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sFail!\n", $this->yyTracePrompt);
+ } while ($this->yyidx >= 0) {
+ $this->yy_pop_parser_stack();
+ }
+ }
+
+ public function yy_syntax_error($yymajor, $TOKEN)
+ {
+// line 238 "src/Parser/ConfigfileParser.y"
+
+ $this->internalError = true;
+ $this->yymajor = $yymajor;
+ $this->compiler->trigger_config_file_error();
+ }
+
+ public function yy_accept()
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sAccept!\n", $this->yyTracePrompt);
+ } while ($this->yyidx >= 0) {
+ $this->yy_pop_parser_stack();
+ }
+// line 231 "src/Parser/ConfigfileParser.y"
+
+ $this->successful = !$this->internalError;
+ $this->internalError = false;
+ $this->retvalue = $this->_retvalue;
+ }
+
+ public function doParse($yymajor, $yytokenvalue)
+ {
+ $yyerrorhit = 0; /* True if yymajor has invoked an error */
+
+ if ($this->yyidx === null || $this->yyidx < 0) {
+ $this->yyidx = 0;
+ $this->yyerrcnt = -1;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = 0;
+ $x->major = 0;
+ $this->yystack = array();
+ $this->yystack[] = $x;
+ }
+ $yyendofinput = ($yymajor==0);
+
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sInput %s\n",
+ $this->yyTracePrompt, $this->yyTokenName[$yymajor]);
+ }
+
+ do {
+ $yyact = $this->yy_find_shift_action($yymajor);
+ if ($yymajor < self::YYERRORSYMBOL &&
+ !$this->yy_is_expected_token($yymajor)) {
+ // force a syntax error
+ $yyact = self::YY_ERROR_ACTION;
+ }
+ if ($yyact < self::YYNSTATE) {
+ $this->yy_shift($yyact, $yymajor, $yytokenvalue);
+ $this->yyerrcnt--;
+ if ($yyendofinput && $this->yyidx >= 0) {
+ $yymajor = 0;
+ } else {
+ $yymajor = self::YYNOCODE;
+ }
+ } elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
+ $this->yy_reduce($yyact - self::YYNSTATE);
+ } elseif ($yyact === self::YY_ERROR_ACTION) {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sSyntax Error!\n",
+ $this->yyTracePrompt);
+ }
+ if (self::YYERRORSYMBOL) {
+ if ($this->yyerrcnt < 0) {
+ $this->yy_syntax_error($yymajor, $yytokenvalue);
+ }
+ $yymx = $this->yystack[$this->yyidx]->major;
+ if ($yymx === self::YYERRORSYMBOL || $yyerrorhit) {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sDiscard input token %s\n",
+ $this->yyTracePrompt, $this->yyTokenName[$yymajor]);
+ }
+ $this->yy_destructor($yymajor, $yytokenvalue);
+ $yymajor = self::YYNOCODE;
+ } else {
+ while ($this->yyidx >= 0 &&
+ $yymx !== self::YYERRORSYMBOL &&
+ ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
+ ){
+ $this->yy_pop_parser_stack();
+ }
+ if ($this->yyidx < 0 || $yymajor==0) {
+ $this->yy_destructor($yymajor, $yytokenvalue);
+ $this->yy_parse_failed();
+ $yymajor = self::YYNOCODE;
+ } elseif ($yymx !== self::YYERRORSYMBOL) {
+ $u2 = 0;
+ $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
+ }
+ }
+ $this->yyerrcnt = 3;
+ $yyerrorhit = 1;
+ } else {
+ if ($this->yyerrcnt <= 0) {
+ $this->yy_syntax_error($yymajor, $yytokenvalue);
+ }
+ $this->yyerrcnt = 3;
+ $this->yy_destructor($yymajor, $yytokenvalue);
+ if ($yyendofinput) {
+ $this->yy_parse_failed();
+ }
+ $yymajor = self::YYNOCODE;
+ }
+ } else {
+ $this->yy_accept();
+ $yymajor = self::YYNOCODE;
+ }
+ } while ($yymajor !== self::YYNOCODE && $this->yyidx >= 0);
+ }
+}
+
diff --git a/src/Parser/ConfigfileParser.y b/src/Parser/ConfigfileParser.y
new file mode 100644
index 00000000..23afc2d8
--- /dev/null
+++ b/src/Parser/ConfigfileParser.y
@@ -0,0 +1,352 @@
+/**
+* ConfigfileParser
+*
+* This is the config file parser
+*
+*
+* @package Smarty
+* @subpackage Config
+* @author Uwe Tews
+*/
+%name TPC_
+%declare_class {
+
+namespace Smarty\Parser;
+
+use \Smarty\Lexer\ConfigfileLexer as Lexer;
+use \Smarty\Compiler\Configfile as Configfile;
+
+/**
+* Smarty Internal Plugin Configfileparse
+*
+* This is the config file parser.
+* It is generated from the ConfigfileParser.y file
+* @package Smarty
+* @subpackage Compiler
+* @author Uwe Tews
+*/
+class ConfigfileParser
+}
+%include_class
+{
+ /**
+ * result status
+ *
+ * @var bool
+ */
+ public $successful = true;
+ /**
+ * return value
+ *
+ * @var mixed
+ */
+ public $retvalue = 0;
+ /**
+ * @var
+ */
+ public $yymajor;
+ /**
+ * lexer object
+ *
+ * @var Lexer
+ */
+ private $lex;
+ /**
+ * internal error flag
+ *
+ * @var bool
+ */
+ private $internalError = false;
+ /**
+ * compiler object
+ *
+ * @var Configfile
+ */
+ public $compiler = null;
+ /**
+ * smarty object
+ *
+ * @var Smarty
+ */
+ public $smarty = null;
+ /**
+ * copy of config_overwrite property
+ *
+ * @var bool
+ */
+ private $configOverwrite = false;
+ /**
+ * copy of config_read_hidden property
+ *
+ * @var bool
+ */
+ private $configReadHidden = false;
+ /**
+ * helper map
+ *
+ * @var array
+ */
+ private static $escapes_single = array('\\' => '\\',
+ '\'' => '\'');
+
+ /**
+ * constructor
+ *
+ * @param Lexer $lex
+ * @param Configfile $compiler
+ */
+ public function __construct(Lexer $lex, Configfile $compiler)
+ {
+ $this->lex = $lex;
+ $this->smarty = $compiler->getSmarty();
+ $this->compiler = $compiler;
+ $this->configOverwrite = $this->smarty->config_overwrite;
+ $this->configReadHidden = $this->smarty->config_read_hidden;
+ }
+
+ /**
+ * parse optional boolean keywords
+ *
+ * @param string $str
+ *
+ * @return bool
+ */
+ private function parse_bool($str)
+ {
+ $str = strtolower($str);
+ if (in_array($str, array('on', 'yes', 'true'))) {
+ $res = true;
+ } else {
+ $res = false;
+ }
+ return $res;
+ }
+
+ /**
+ * parse single quoted string
+ * remove outer quotes
+ * unescape inner quotes
+ *
+ * @param string $qstr
+ *
+ * @return string
+ */
+ private static function parse_single_quoted_string($qstr)
+ {
+ $escaped_string = substr($qstr, 1, strlen($qstr) - 2); //remove outer quotes
+
+ $ss = preg_split('/(\\\\.)/', $escaped_string, - 1, PREG_SPLIT_DELIM_CAPTURE);
+
+ $str = '';
+ foreach ($ss as $s) {
+ if (strlen($s) === 2 && $s[0] === '\\') {
+ if (isset(self::$escapes_single[$s[1]])) {
+ $s = self::$escapes_single[$s[1]];
+ }
+ }
+ $str .= $s;
+ }
+ return $str;
+ }
+
+ /**
+ * parse double quoted string
+ *
+ * @param string $qstr
+ *
+ * @return string
+ */
+ private static function parse_double_quoted_string($qstr)
+ {
+ $inner_str = substr($qstr, 1, strlen($qstr) - 2);
+ return stripcslashes($inner_str);
+ }
+
+ /**
+ * parse triple quoted string
+ *
+ * @param string $qstr
+ *
+ * @return string
+ */
+ private static function parse_tripple_double_quoted_string($qstr)
+ {
+ return stripcslashes($qstr);
+ }
+
+ /**
+ * set a config variable in target array
+ *
+ * @param array $var
+ * @param array $target_array
+ */
+ private function set_var(array $var, array &$target_array)
+ {
+ $key = $var['key'];
+ $value = $var['value'];
+
+ if ($this->configOverwrite || !isset($target_array['vars'][$key])) {
+ $target_array['vars'][$key] = $value;
+ } else {
+ settype($target_array['vars'][$key], 'array');
+ $target_array['vars'][$key][] = $value;
+ }
+ }
+
+ /**
+ * add config variable to global vars
+ *
+ * @param array $vars
+ */
+ private function add_global_vars(array $vars)
+ {
+ if (!isset($this->compiler->config_data['vars'])) {
+ $this->compiler->config_data['vars'] = array();
+ }
+ foreach ($vars as $var) {
+ $this->set_var($var, $this->compiler->config_data);
+ }
+ }
+
+ /**
+ * add config variable to section
+ *
+ * @param string $section_name
+ * @param array $vars
+ */
+ private function add_section_vars($section_name, array $vars)
+ {
+ if (!isset($this->compiler->config_data['sections'][$section_name]['vars'])) {
+ $this->compiler->config_data['sections'][$section_name]['vars'] = array();
+ }
+ foreach ($vars as $var) {
+ $this->set_var($var, $this->compiler->config_data['sections'][$section_name]);
+ }
+ }
+}
+
+%token_prefix TPC_
+
+%parse_accept
+{
+ $this->successful = !$this->internalError;
+ $this->internalError = false;
+ $this->retvalue = $this->_retvalue;
+}
+
+%syntax_error
+{
+ $this->internalError = true;
+ $this->yymajor = $yymajor;
+ $this->compiler->trigger_config_file_error();
+}
+
+%stack_overflow
+{
+ $this->internalError = true;
+ $this->compiler->trigger_config_file_error('Stack overflow in configfile parser');
+}
+
+// Complete config file
+start(res) ::= global_vars sections. {
+ res = null;
+}
+
+// Global vars
+global_vars(res) ::= var_list(vl). {
+ $this->add_global_vars(vl);
+ res = null;
+}
+
+// Sections
+sections(res) ::= sections section. {
+ res = null;
+}
+
+sections(res) ::= . {
+ res = null;
+}
+
+section(res) ::= OPENB SECTION(i) CLOSEB newline var_list(vars). {
+ $this->add_section_vars(i, vars);
+ res = null;
+}
+
+section(res) ::= OPENB DOT SECTION(i) CLOSEB newline var_list(vars). {
+ if ($this->configReadHidden) {
+ $this->add_section_vars(i, vars);
+ }
+ res = null;
+}
+
+// Var list
+var_list(res) ::= var_list(vl) newline. {
+ res = vl;
+}
+
+var_list(res) ::= var_list(vl) var(v). {
+ res = array_merge(vl, array(v));
+}
+
+var_list(res) ::= . {
+ res = array();
+}
+
+
+// Var
+var(res) ::= ID(id) EQUAL value(v). {
+ res = array('key' => id, 'value' => v);
+}
+
+
+value(res) ::= FLOAT(i). {
+ res = (float) i;
+}
+
+value(res) ::= INT(i). {
+ res = (int) i;
+}
+
+value(res) ::= BOOL(i). {
+ res = $this->parse_bool(i);
+}
+
+value(res) ::= SINGLE_QUOTED_STRING(i). {
+ res = self::parse_single_quoted_string(i);
+}
+
+value(res) ::= DOUBLE_QUOTED_STRING(i). {
+ res = self::parse_double_quoted_string(i);
+}
+
+value(res) ::= TRIPPLE_QUOTES(i) TRIPPLE_TEXT(c) TRIPPLE_QUOTES_END(ii). {
+ res = self::parse_tripple_double_quoted_string(c);
+}
+
+value(res) ::= TRIPPLE_QUOTES(i) TRIPPLE_QUOTES_END(ii). {
+ res = '';
+}
+
+value(res) ::= NAKED_STRING(i). {
+ res = i;
+}
+
+// NOTE: this is not a valid rule
+// It is added hier to produce a usefull error message on a missing '=';
+value(res) ::= OTHER(i). {
+ res = i;
+}
+
+
+// Newline and comments
+newline(res) ::= NEWLINE. {
+ res = null;
+}
+
+newline(res) ::= COMMENTSTART NEWLINE. {
+ res = null;
+}
+
+newline(res) ::= COMMENTSTART NAKED_STRING NEWLINE. {
+ res = null;
+}
diff --git a/src/Parser/TemplateParser.php b/src/Parser/TemplateParser.php
new file mode 100644
index 00000000..6304581a
--- /dev/null
+++ b/src/Parser/TemplateParser.php
@@ -0,0 +1,3003 @@
+<?php
+
+// line 11 "src/Parser/TemplateParser.y"
+
+
+namespace Smarty\Parser;
+
+use \Smarty\Lexer\TemplateLexer as Lexer;
+use \Smarty\ParseTree\Template as TemplateParseTree;
+use \Smarty\Compiler\Template as TemplateCompiler;
+use \Smarty\ParseTree\Code;
+use \Smarty\ParseTree\Dq;
+use \Smarty\ParseTree\DqContent;
+use \Smarty\ParseTree\Tag;
+
+
+/**
+* Smarty Template Parser Class
+*
+* This is the template parser.
+* It is generated from the TemplateParser.y file
+*
+* @author Uwe Tews <uwe.tews@googlemail.com>
+*/
+class TemplateParser
+{
+// line 35 "src/Parser/TemplateParser.y"
+
+ const ERR1 = 'Security error: Call to private object member not allowed';
+ const ERR2 = 'Security error: Call to dynamic object member not allowed';
+
+ /**
+ * result status
+ *
+ * @var bool
+ */
+ public $successful = true;
+
+ /**
+ * return value
+ *
+ * @var mixed
+ */
+ public $retvalue = 0;
+
+ /**
+ * @var
+ */
+ public $yymajor;
+
+ /**
+ * last index of array variable
+ *
+ * @var mixed
+ */
+ public $last_index;
+
+ /**
+ * last variable name
+ *
+ * @var string
+ */
+ public $last_variable;
+
+ /**
+ * root parse tree buffer
+ *
+ * @var TemplateParseTree
+ */
+ public $root_buffer;
+
+ /**
+ * current parse tree object
+ *
+ * @var \Smarty\ParseTree\Base
+ */
+ public $current_buffer;
+
+ /**
+ * lexer object
+ *
+ * @var Lexer
+ */
+ public $lex;
+
+ /**
+ * internal error flag
+ *
+ * @var bool
+ */
+ private $internalError = false;
+
+ /**
+ * {strip} status
+ *
+ * @var bool
+ */
+ public $strip = false;
+ /**
+ * compiler object
+ *
+ * @var TemplateCompiler
+ */
+ public $compiler = null;
+
+ /**
+ * smarty object
+ *
+ * @var \Smarty\Smarty
+ */
+ public $smarty = null;
+
+ /**
+ * template object
+ *
+ * @var \Smarty\Template
+ */
+ public $template = null;
+
+ /**
+ * block nesting level
+ *
+ * @var int
+ */
+ public $block_nesting_level = 0;
+
+ /**
+ * security object
+ *
+ * @var \Smarty\Security
+ */
+ public $security = null;
+
+ /**
+ * template prefix array
+ *
+ * @var \Smarty\ParseTree\Base[]
+ */
+ public $template_prefix = array();
+
+ /**
+ * template prefix array
+ *
+ * @var \Smarty\ParseTree\Base[]
+ */
+ public $template_postfix = array();
+
+ /**
+ * constructor
+ *
+ * @param Lexer $lex
+ * @param TemplateCompiler $compiler
+ */
+ public function __construct(Lexer $lex, TemplateCompiler $compiler)
+ {
+ $this->lex = $lex;
+ $this->compiler = $compiler;
+ $this->template = $this->compiler->getTemplate();
+ $this->smarty = $this->template->getSmarty();
+ $this->security = $this->smarty->security_policy ?? false;
+ $this->current_buffer = $this->root_buffer = new TemplateParseTree();
+ }
+
+ /**
+ * insert PHP code in current buffer
+ *
+ * @param string $code
+ */
+ public function insertPhpCode($code)
+ {
+ $this->current_buffer->append_subtree($this, new Tag($this, $code));
+ }
+
+ /**
+ * error rundown
+ *
+ */
+ public function errorRunDown()
+ {
+ while ($this->yystack !== array()) {
+ $this->yy_pop_parser_stack();
+ }
+ if (is_resource($this->yyTraceFILE)) {
+ fclose($this->yyTraceFILE);
+ }
+ }
+
+ /**
+ * merge PHP code with prefix code and return parse tree tag object
+ *
+ * @param string $code
+ *
+ * @return Tag
+ */
+ private function mergePrefixCode($code)
+ {
+ $tmp = '';
+ foreach ($this->compiler->prefix_code as $preCode) {
+ $tmp .= $preCode;
+ }
+ $this->compiler->prefix_code = array();
+ $tmp .= $code;
+ return new Tag($this, $this->compiler->processNocacheCode($tmp));
+ }
+
+
+ const TP_VERT = 1;
+ const TP_COLON = 2;
+ const TP_TEXT = 3;
+ const TP_STRIPON = 4;
+ const TP_STRIPOFF = 5;
+ const TP_LITERALSTART = 6;
+ const TP_LITERALEND = 7;
+ const TP_LITERAL = 8;
+ const TP_SIMPELOUTPUT = 9;
+ const TP_SIMPLETAG = 10;
+ const TP_SMARTYBLOCKCHILDPARENT = 11;
+ const TP_LDEL = 12;
+ const TP_RDEL = 13;
+ const TP_DOLLARID = 14;
+ const TP_EQUAL = 15;
+ const TP_ID = 16;
+ const TP_PTR = 17;
+ const TP_LDELIF = 18;
+ const TP_LDELFOR = 19;
+ const TP_SEMICOLON = 20;
+ const TP_INCDEC = 21;
+ const TP_TO = 22;
+ const TP_STEP = 23;
+ const TP_LDELFOREACH = 24;
+ const TP_SPACE = 25;
+ const TP_AS = 26;
+ const TP_APTR = 27;
+ const TP_LDELSETFILTER = 28;
+ const TP_CLOSETAG = 29;
+ const TP_LDELSLASH = 30;
+ const TP_ATTR = 31;
+ const TP_INTEGER = 32;
+ const TP_COMMA = 33;
+ const TP_OPENP = 34;
+ const TP_CLOSEP = 35;
+ const TP_MATH = 36;
+ const TP_UNIMATH = 37;
+ const TP_ISIN = 38;
+ const TP_QMARK = 39;
+ const TP_NOT = 40;
+ const TP_TYPECAST = 41;
+ const TP_HEX = 42;
+ const TP_DOT = 43;
+ const TP_INSTANCEOF = 44;
+ const TP_SINGLEQUOTESTRING = 45;
+ const TP_DOUBLECOLON = 46;
+ const TP_NAMESPACE = 47;
+ const TP_AT = 48;
+ const TP_HATCH = 49;
+ const TP_OPENB = 50;
+ const TP_CLOSEB = 51;
+ const TP_DOLLAR = 52;
+ const TP_LOGOP = 53;
+ const TP_SLOGOP = 54;
+ const TP_TLOGOP = 55;
+ const TP_SINGLECOND = 56;
+ const TP_ARRAYOPEN = 57;
+ const TP_QUOTE = 58;
+ const TP_BACKTICK = 59;
+ const YY_NO_ACTION = 525;
+ const YY_ACCEPT_ACTION = 524;
+ const YY_ERROR_ACTION = 523;
+
+ const YY_SZ_ACTTAB = 2191;
+public static $yy_action = array(
+ 33, 126, 524, 96, 261, 279, 437, 242, 243, 244,
+ 1, 98, 135, 127, 199, 228, 6, 55, 437, 217,
+ 197, 260, 109, 317, 392, 292, 212, 256, 213, 103,
+ 219, 392, 21, 392, 99, 43, 392, 32, 44, 45,
+ 273, 221, 392, 277, 392, 200, 392, 54, 4, 313,
+ 294, 46, 22, 280, 220, 5, 52, 242, 243, 244,
+ 1, 20, 132, 189, 190, 266, 6, 55, 241, 217,
+ 211, 29, 109, 224, 9, 156, 212, 256, 213, 493,
+ 205, 267, 21, 252, 264, 43, 176, 297, 44, 45,
+ 273, 221, 312, 230, 306, 200, 211, 54, 4, 320,
+ 294, 294, 3, 248, 99, 5, 52, 242, 243, 244,
+ 1, 294, 97, 386, 53, 231, 6, 55, 36, 217,
+ 99, 150, 109, 252, 16, 386, 212, 256, 213, 149,
+ 219, 386, 21, 112, 437, 43, 302, 93, 44, 45,
+ 273, 221, 306, 277, 211, 200, 437, 54, 4, 112,
+ 294, 197, 39, 99, 140, 5, 52, 242, 243, 244,
+ 1, 136, 134, 262, 199, 28, 6, 55, 155, 217,
+ 252, 151, 109, 99, 94, 91, 212, 256, 213, 137,
+ 219, 251, 21, 57, 308, 43, 13, 7, 44, 45,
+ 273, 221, 294, 277, 263, 200, 54, 54, 4, 294,
+ 294, 181, 112, 298, 197, 5, 52, 242, 243, 244,
+ 1, 259, 134, 232, 191, 353, 6, 55, 26, 217,
+ 330, 353, 109, 104, 149, 437, 212, 256, 213, 174,
+ 219, 222, 21, 250, 51, 43, 141, 437, 44, 45,
+ 273, 221, 232, 277, 296, 200, 251, 54, 4, 233,
+ 294, 35, 104, 353, 285, 5, 52, 242, 243, 244,
+ 1, 259, 133, 138, 199, 353, 6, 55, 108, 217,
+ 173, 353, 109, 175, 297, 14, 212, 256, 213, 450,
+ 219, 15, 11, 264, 51, 43, 450, 139, 44, 45,
+ 273, 221, 144, 277, 152, 200, 181, 54, 4, 136,
+ 294, 226, 251, 154, 251, 5, 52, 242, 243, 244,
+ 1, 146, 134, 251, 186, 197, 6, 55, 170, 217,
+ 157, 251, 109, 17, 180, 294, 212, 256, 213, 329,
+ 208, 215, 21, 180, 54, 43, 252, 294, 44, 45,
+ 273, 221, 143, 277, 181, 200, 254, 54, 4, 19,
+ 294, 126, 104, 253, 227, 5, 52, 242, 243, 244,
+ 1, 98, 134, 259, 184, 161, 6, 55, 178, 217,
+ 240, 148, 109, 175, 297, 292, 212, 256, 213, 284,
+ 219, 251, 21, 197, 23, 43, 51, 127, 44, 45,
+ 273, 221, 164, 277, 168, 200, 252, 54, 4, 171,
+ 294, 304, 251, 197, 327, 5, 52, 242, 243, 244,
+ 1, 137, 134, 167, 199, 180, 6, 55, 13, 217,
+ 181, 255, 109, 251, 87, 181, 212, 256, 213, 25,
+ 185, 18, 21, 177, 297, 43, 88, 15, 44, 45,
+ 273, 221, 294, 277, 24, 200, 295, 54, 4, 258,
+ 294, 41, 42, 40, 12, 5, 52, 242, 243, 244,
+ 1, 163, 136, 437, 199, 39, 6, 55, 287, 288,
+ 289, 290, 109, 259, 303, 437, 212, 256, 213, 450,
+ 219, 214, 21, 209, 194, 47, 450, 438, 44, 45,
+ 273, 221, 9, 277, 309, 200, 51, 54, 4, 438,
+ 294, 41, 42, 40, 12, 5, 52, 242, 243, 244,
+ 1, 10, 136, 24, 199, 321, 6, 55, 287, 288,
+ 289, 290, 109, 113, 239, 240, 212, 256, 213, 220,
+ 219, 255, 21, 8, 24, 43, 322, 316, 44, 45,
+ 273, 221, 89, 277, 293, 200, 195, 54, 4, 95,
+ 294, 34, 169, 172, 238, 5, 52, 282, 210, 211,
+ 247, 232, 90, 106, 158, 188, 100, 86, 234, 196,
+ 254, 104, 98, 19, 153, 268, 269, 253, 92, 220,
+ 159, 276, 201, 278, 251, 283, 292, 245, 282, 210,
+ 211, 247, 466, 90, 106, 466, 187, 100, 63, 466,
+ 246, 179, 110, 98, 181, 165, 268, 269, 257, 223,
+ 118, 265, 276, 201, 278, 251, 283, 292, 46, 22,
+ 280, 282, 270, 211, 249, 272, 111, 106, 274, 188,
+ 100, 86, 220, 282, 275, 211, 98, 259, 111, 268,
+ 269, 198, 116, 75, 7, 276, 201, 278, 98, 283,
+ 292, 268, 269, 145, 291, 56, 160, 276, 201, 278,
+ 51, 283, 292, 41, 42, 40, 12, 315, 162, 282,
+ 229, 211, 204, 311, 111, 318, 197, 198, 116, 75,
+ 287, 288, 289, 290, 98, 319, 36, 268, 269, 197,
+ 328, 331, 301, 276, 201, 278, 301, 283, 292, 37,
+ 14, 357, 282, 301, 211, 225, 15, 105, 203, 311,
+ 198, 119, 49, 14, 117, 301, 301, 98, 301, 15,
+ 268, 269, 437, 301, 301, 301, 276, 201, 278, 301,
+ 283, 292, 301, 301, 437, 282, 301, 211, 301, 301,
+ 111, 301, 301, 198, 119, 70, 301, 301, 301, 301,
+ 98, 301, 142, 268, 269, 301, 301, 301, 301, 276,
+ 201, 278, 251, 283, 292, 46, 22, 280, 282, 301,
+ 211, 207, 301, 111, 301, 301, 198, 119, 70, 301,
+ 301, 301, 301, 98, 301, 166, 268, 269, 301, 301,
+ 301, 301, 276, 201, 278, 251, 283, 292, 46, 22,
+ 280, 282, 301, 211, 202, 301, 111, 301, 301, 198,
+ 116, 75, 301, 301, 301, 301, 98, 301, 301, 268,
+ 269, 197, 301, 301, 301, 276, 201, 278, 301, 283,
+ 292, 301, 301, 354, 282, 301, 211, 301, 301, 111,
+ 301, 310, 198, 119, 70, 354, 301, 301, 301, 98,
+ 301, 354, 268, 269, 197, 301, 301, 301, 276, 201,
+ 278, 301, 283, 292, 301, 301, 388, 282, 301, 211,
+ 206, 301, 105, 301, 301, 198, 119, 61, 388, 233,
+ 301, 301, 98, 301, 388, 268, 269, 197, 301, 301,
+ 301, 276, 201, 278, 301, 283, 292, 301, 301, 385,
+ 282, 301, 211, 301, 301, 111, 301, 301, 198, 115,
+ 67, 385, 301, 301, 301, 98, 301, 385, 268, 269,
+ 301, 301, 301, 301, 276, 201, 278, 254, 283, 292,
+ 19, 301, 301, 282, 253, 211, 301, 466, 111, 301,
+ 466, 193, 114, 62, 466, 282, 301, 211, 98, 301,
+ 111, 268, 269, 198, 101, 85, 301, 276, 201, 278,
+ 98, 283, 292, 268, 269, 254, 216, 301, 19, 276,
+ 201, 278, 253, 283, 292, 282, 466, 211, 301, 301,
+ 111, 14, 301, 198, 102, 84, 301, 15, 301, 301,
+ 98, 301, 301, 268, 269, 301, 301, 301, 301, 276,
+ 201, 278, 301, 283, 292, 301, 301, 282, 301, 211,
+ 301, 301, 111, 301, 301, 198, 119, 58, 301, 282,
+ 301, 211, 98, 301, 111, 268, 269, 198, 119, 69,
+ 301, 276, 201, 278, 98, 283, 292, 268, 269, 301,
+ 301, 301, 301, 276, 201, 278, 301, 283, 292, 282,
+ 301, 211, 301, 301, 111, 301, 301, 198, 101, 59,
+ 301, 301, 301, 301, 98, 301, 301, 268, 269, 301,
+ 301, 301, 301, 276, 201, 278, 301, 283, 292, 301,
+ 301, 282, 301, 211, 301, 301, 111, 301, 301, 198,
+ 119, 68, 301, 282, 301, 211, 98, 301, 111, 268,
+ 269, 198, 119, 60, 301, 276, 201, 278, 98, 283,
+ 292, 268, 269, 301, 301, 301, 301, 276, 201, 278,
+ 301, 283, 292, 282, 301, 211, 301, 301, 111, 301,
+ 301, 198, 119, 61, 301, 301, 301, 301, 98, 301,
+ 301, 268, 269, 301, 301, 301, 301, 276, 201, 278,
+ 301, 283, 292, 301, 301, 282, 301, 211, 301, 301,
+ 111, 301, 301, 198, 119, 71, 301, 282, 301, 211,
+ 98, 301, 111, 268, 269, 198, 119, 72, 301, 276,
+ 201, 278, 98, 283, 292, 268, 269, 301, 301, 301,
+ 301, 276, 201, 278, 301, 283, 292, 282, 301, 211,
+ 301, 301, 111, 301, 301, 198, 119, 73, 301, 301,
+ 301, 301, 98, 301, 301, 268, 269, 301, 301, 301,
+ 301, 276, 201, 278, 301, 283, 292, 301, 301, 282,
+ 301, 211, 301, 301, 111, 301, 301, 198, 119, 74,
+ 301, 282, 301, 211, 98, 301, 111, 268, 269, 198,
+ 119, 76, 301, 276, 201, 278, 98, 283, 292, 268,
+ 269, 301, 301, 301, 301, 276, 201, 278, 301, 283,
+ 292, 282, 301, 211, 301, 301, 111, 301, 301, 192,
+ 119, 64, 301, 301, 301, 301, 98, 301, 301, 268,
+ 269, 301, 301, 301, 301, 276, 201, 278, 301, 283,
+ 292, 301, 301, 282, 301, 211, 301, 301, 111, 301,
+ 301, 198, 119, 65, 301, 282, 301, 211, 98, 301,
+ 111, 268, 269, 198, 119, 66, 301, 276, 201, 278,
+ 98, 283, 292, 268, 269, 301, 301, 301, 301, 276,
+ 201, 278, 301, 283, 292, 282, 301, 211, 301, 301,
+ 111, 301, 301, 198, 119, 77, 301, 301, 301, 301,
+ 98, 301, 301, 268, 269, 301, 301, 301, 301, 276,
+ 201, 278, 301, 283, 292, 301, 301, 282, 301, 211,
+ 301, 301, 111, 301, 301, 198, 119, 78, 301, 282,
+ 301, 211, 98, 301, 111, 268, 269, 198, 119, 79,
+ 301, 276, 201, 278, 98, 283, 292, 268, 269, 301,
+ 301, 301, 301, 276, 201, 278, 301, 283, 292, 282,
+ 301, 211, 301, 301, 111, 301, 301, 198, 119, 80,
+ 301, 301, 301, 301, 98, 301, 301, 268, 269, 301,
+ 301, 301, 301, 276, 201, 278, 301, 283, 292, 301,
+ 301, 282, 301, 211, 301, 301, 111, 301, 301, 198,
+ 119, 81, 301, 282, 301, 211, 98, 301, 111, 268,
+ 269, 198, 119, 82, 301, 276, 201, 278, 98, 283,
+ 292, 268, 269, 301, 301, 301, 301, 276, 201, 278,
+ 301, 283, 292, 282, 301, 211, 301, 301, 111, 301,
+ 301, 198, 119, 83, 301, 301, 301, 301, 98, 301,
+ 301, 268, 269, 301, 301, 301, 301, 276, 201, 278,
+ 301, 283, 292, 301, 301, 282, 301, 211, 301, 301,
+ 111, 301, 301, 198, 119, 48, 301, 282, 301, 211,
+ 98, 301, 111, 268, 269, 198, 119, 50, 301, 276,
+ 201, 278, 98, 283, 292, 268, 269, 301, 301, 301,
+ 301, 276, 201, 278, 301, 283, 292, 307, 107, 301,
+ 301, 301, 301, 242, 243, 244, 2, 301, 305, 301,
+ 301, 301, 6, 55, 41, 42, 40, 12, 109, 301,
+ 301, 301, 212, 256, 213, 301, 301, 38, 301, 14,
+ 301, 287, 288, 289, 290, 15, 301, 301, 301, 301,
+ 41, 42, 40, 12, 301, 301, 301, 301, 301, 301,
+ 301, 301, 300, 27, 301, 301, 307, 287, 288, 289,
+ 290, 301, 242, 243, 244, 2, 301, 305, 301, 301,
+ 301, 6, 55, 301, 301, 301, 301, 109, 301, 14,
+ 301, 212, 256, 213, 301, 15, 301, 301, 301, 301,
+ 41, 42, 40, 12, 301, 301, 301, 301, 301, 301,
+ 301, 301, 301, 301, 301, 301, 301, 287, 288, 289,
+ 290, 301, 27, 301, 301, 235, 236, 237, 130, 301,
+ 301, 242, 243, 244, 1, 301, 301, 301, 301, 301,
+ 6, 55, 301, 301, 301, 301, 109, 301, 301, 301,
+ 212, 256, 213, 282, 301, 211, 301, 301, 111, 301,
+ 301, 198, 131, 301, 301, 301, 301, 301, 98, 301,
+ 301, 301, 301, 301, 301, 301, 325, 276, 201, 278,
+ 301, 283, 292, 301, 301, 301, 301, 301, 282, 301,
+ 211, 301, 301, 111, 301, 301, 198, 125, 301, 301,
+ 282, 301, 211, 98, 301, 111, 301, 301, 198, 129,
+ 301, 281, 276, 201, 278, 98, 283, 292, 301, 301,
+ 301, 301, 301, 301, 276, 201, 278, 301, 283, 292,
+ 301, 301, 301, 301, 282, 301, 211, 301, 301, 111,
+ 301, 301, 198, 120, 301, 301, 301, 301, 301, 98,
+ 301, 301, 301, 301, 301, 301, 301, 301, 276, 201,
+ 278, 301, 283, 292, 301, 301, 282, 301, 211, 301,
+ 301, 111, 301, 301, 198, 121, 301, 301, 282, 301,
+ 211, 98, 301, 111, 301, 301, 198, 122, 301, 301,
+ 276, 201, 278, 98, 283, 292, 301, 301, 301, 301,
+ 301, 301, 276, 201, 278, 301, 283, 292, 282, 301,
+ 211, 301, 301, 111, 301, 301, 198, 123, 301, 301,
+ 301, 301, 301, 98, 301, 301, 301, 301, 301, 301,
+ 301, 301, 276, 201, 278, 301, 283, 292, 301, 301,
+ 282, 301, 211, 301, 301, 111, 301, 301, 198, 124,
+ 301, 301, 282, 301, 211, 98, 301, 111, 301, 301,
+ 198, 128, 301, 301, 276, 201, 278, 98, 283, 292,
+ 301, 301, 218, 301, 301, 301, 276, 201, 278, 466,
+ 283, 292, 466, 218, 301, 3, 466, 450, 301, 301,
+ 466, 271, 301, 466, 301, 301, 218, 466, 450, 301,
+ 301, 254, 271, 466, 19, 301, 466, 301, 253, 35,
+ 466, 450, 301, 450, 401, 271, 450, 14, 466, 147,
+ 450, 301, 301, 15, 450, 301, 301, 450, 301, 466,
+ 301, 450, 286, 301, 301, 301, 301, 450, 301, 301,
+ 450, 301, 466, 301, 450, 218, 437, 301, 401, 401,
+ 401, 401, 466, 301, 301, 466, 301, 301, 437, 466,
+ 450, 301, 30, 301, 271, 401, 401, 401, 401, 466,
+ 301, 301, 466, 301, 324, 301, 466, 450, 301, 301,
+ 301, 271, 301, 301, 301, 301, 450, 301, 301, 450,
+ 301, 466, 301, 450, 301, 301, 301, 41, 42, 40,
+ 12, 301, 301, 450, 301, 299, 450, 301, 466, 301,
+ 450, 301, 301, 301, 287, 288, 289, 290, 41, 42,
+ 40, 12, 323, 41, 42, 40, 12, 301, 41, 42,
+ 40, 12, 182, 314, 31, 287, 288, 289, 290, 183,
+ 287, 288, 289, 290, 301, 287, 288, 289, 290, 301,
+ 301, 301, 301, 301, 301, 41, 42, 40, 12, 301,
+ 301, 301, 41, 42, 40, 12, 301, 301, 41, 42,
+ 40, 12, 287, 288, 289, 290, 326, 301, 301, 287,
+ 288, 289, 290, 301, 301, 287, 288, 289, 290, 466,
+ 301, 301, 466, 301, 301, 301, 466, 450, 301, 41,
+ 42, 40, 12, 301, 301, 301, 301, 301, 301, 301,
+ 301, 301, 301, 301, 301, 301, 287, 288, 289, 290,
+ 301, 301, 301, 450, 301, 301, 450, 301, 466, 301,
+ 450,
+ );
+ public static $yy_lookahead = array(
+ 2, 70, 61, 62, 73, 92, 34, 9, 10, 11,
+ 12, 80, 14, 100, 16, 43, 18, 19, 46, 21,
+ 1, 90, 24, 51, 13, 94, 28, 29, 30, 80,
+ 32, 20, 34, 22, 17, 37, 25, 39, 40, 41,
+ 42, 43, 31, 45, 33, 47, 35, 49, 50, 100,
+ 52, 85, 86, 87, 43, 57, 58, 9, 10, 11,
+ 12, 12, 14, 14, 16, 16, 18, 19, 65, 21,
+ 67, 12, 24, 14, 33, 16, 28, 29, 30, 1,
+ 32, 32, 34, 97, 96, 37, 98, 99, 40, 41,
+ 42, 43, 51, 45, 65, 47, 67, 49, 50, 51,
+ 52, 52, 15, 69, 17, 57, 58, 9, 10, 11,
+ 12, 52, 14, 13, 16, 15, 18, 19, 15, 21,
+ 17, 95, 24, 97, 20, 25, 28, 29, 30, 95,
+ 32, 31, 34, 46, 34, 37, 107, 33, 40, 41,
+ 42, 43, 65, 45, 67, 47, 46, 49, 50, 46,
+ 52, 1, 2, 17, 14, 57, 58, 9, 10, 11,
+ 12, 14, 14, 16, 16, 12, 18, 19, 95, 21,
+ 97, 72, 24, 17, 34, 76, 28, 29, 30, 43,
+ 32, 82, 34, 106, 107, 37, 50, 34, 40, 41,
+ 42, 43, 52, 45, 47, 47, 49, 49, 50, 52,
+ 52, 102, 46, 13, 1, 57, 58, 9, 10, 11,
+ 12, 21, 14, 70, 16, 25, 18, 19, 27, 21,
+ 77, 31, 24, 80, 95, 34, 28, 29, 30, 14,
+ 32, 16, 34, 82, 44, 37, 72, 46, 40, 41,
+ 42, 43, 70, 45, 69, 47, 82, 49, 50, 77,
+ 52, 15, 80, 13, 51, 57, 58, 9, 10, 11,
+ 12, 21, 14, 80, 16, 25, 18, 19, 79, 21,
+ 76, 31, 24, 98, 99, 25, 28, 29, 30, 43,
+ 32, 31, 34, 96, 44, 37, 50, 14, 40, 41,
+ 42, 43, 72, 45, 72, 47, 102, 49, 50, 14,
+ 52, 16, 82, 72, 82, 57, 58, 9, 10, 11,
+ 12, 72, 14, 82, 16, 1, 18, 19, 76, 21,
+ 95, 82, 24, 15, 102, 52, 28, 29, 30, 21,
+ 32, 17, 34, 102, 49, 37, 97, 52, 40, 41,
+ 42, 43, 70, 45, 102, 47, 9, 49, 50, 12,
+ 52, 70, 80, 16, 73, 57, 58, 9, 10, 11,
+ 12, 80, 14, 21, 16, 95, 18, 19, 6, 21,
+ 8, 72, 24, 98, 99, 94, 28, 29, 30, 92,
+ 32, 82, 34, 1, 2, 37, 44, 100, 40, 41,
+ 42, 43, 72, 45, 76, 47, 97, 49, 50, 76,
+ 52, 59, 82, 1, 14, 57, 58, 9, 10, 11,
+ 12, 43, 14, 72, 16, 102, 18, 19, 50, 21,
+ 102, 103, 24, 82, 80, 102, 28, 29, 30, 27,
+ 32, 25, 34, 98, 99, 37, 80, 31, 40, 41,
+ 42, 43, 52, 45, 33, 47, 35, 49, 50, 16,
+ 52, 36, 37, 38, 39, 57, 58, 9, 10, 11,
+ 12, 95, 14, 34, 16, 2, 18, 19, 53, 54,
+ 55, 56, 24, 21, 59, 46, 28, 29, 30, 43,
+ 32, 48, 34, 63, 64, 37, 50, 34, 40, 41,
+ 42, 43, 33, 45, 35, 47, 44, 49, 50, 46,
+ 52, 36, 37, 38, 39, 57, 58, 9, 10, 11,
+ 12, 34, 14, 33, 16, 35, 18, 19, 53, 54,
+ 55, 56, 24, 46, 7, 8, 28, 29, 30, 43,
+ 32, 103, 34, 34, 33, 37, 35, 51, 40, 41,
+ 42, 43, 95, 45, 99, 47, 64, 49, 50, 81,
+ 52, 15, 81, 81, 7, 57, 58, 65, 66, 67,
+ 68, 70, 70, 71, 95, 73, 74, 75, 77, 78,
+ 9, 80, 80, 12, 72, 83, 84, 16, 76, 43,
+ 95, 89, 90, 91, 82, 93, 94, 13, 65, 66,
+ 67, 68, 9, 70, 71, 12, 73, 74, 75, 16,
+ 13, 16, 16, 80, 102, 72, 83, 84, 16, 48,
+ 16, 16, 89, 90, 91, 82, 93, 94, 85, 86,
+ 87, 65, 14, 67, 68, 16, 70, 71, 32, 73,
+ 74, 75, 43, 65, 32, 67, 80, 21, 70, 83,
+ 84, 73, 74, 75, 34, 89, 90, 91, 80, 93,
+ 94, 83, 84, 26, 16, 16, 49, 89, 90, 91,
+ 44, 93, 94, 36, 37, 38, 39, 51, 49, 65,
+ 16, 67, 104, 105, 70, 51, 1, 73, 74, 75,
+ 53, 54, 55, 56, 80, 51, 15, 83, 84, 1,
+ 16, 35, 108, 89, 90, 91, 108, 93, 94, 22,
+ 25, 13, 65, 108, 67, 17, 31, 70, 104, 105,
+ 73, 74, 75, 25, 77, 108, 108, 80, 108, 31,
+ 83, 84, 34, 108, 108, 108, 89, 90, 91, 108,
+ 93, 94, 108, 108, 46, 65, 108, 67, 108, 108,
+ 70, 108, 108, 73, 74, 75, 108, 108, 108, 108,
+ 80, 108, 72, 83, 84, 108, 108, 108, 108, 89,
+ 90, 91, 82, 93, 94, 85, 86, 87, 65, 108,
+ 67, 101, 108, 70, 108, 108, 73, 74, 75, 108,
+ 108, 108, 108, 80, 108, 72, 83, 84, 108, 108,
+ 108, 108, 89, 90, 91, 82, 93, 94, 85, 86,
+ 87, 65, 108, 67, 101, 108, 70, 108, 108, 73,
+ 74, 75, 108, 108, 108, 108, 80, 108, 108, 83,
+ 84, 1, 108, 108, 108, 89, 90, 91, 108, 93,
+ 94, 108, 108, 13, 65, 108, 67, 108, 108, 70,
+ 108, 105, 73, 74, 75, 25, 108, 108, 108, 80,
+ 108, 31, 83, 84, 1, 108, 108, 108, 89, 90,
+ 91, 108, 93, 94, 108, 108, 13, 65, 108, 67,
+ 101, 108, 70, 108, 108, 73, 74, 75, 25, 77,
+ 108, 108, 80, 108, 31, 83, 84, 1, 108, 108,
+ 108, 89, 90, 91, 108, 93, 94, 108, 108, 13,
+ 65, 108, 67, 108, 108, 70, 108, 108, 73, 74,
+ 75, 25, 108, 108, 108, 80, 108, 31, 83, 84,
+ 108, 108, 108, 108, 89, 90, 91, 9, 93, 94,
+ 12, 108, 108, 65, 16, 67, 108, 9, 70, 108,
+ 12, 73, 74, 75, 16, 65, 108, 67, 80, 108,
+ 70, 83, 84, 73, 74, 75, 108, 89, 90, 91,
+ 80, 93, 94, 83, 84, 9, 48, 108, 12, 89,
+ 90, 91, 16, 93, 94, 65, 48, 67, 108, 108,
+ 70, 25, 108, 73, 74, 75, 108, 31, 108, 108,
+ 80, 108, 108, 83, 84, 108, 108, 108, 108, 89,
+ 90, 91, 108, 93, 94, 108, 108, 65, 108, 67,
+ 108, 108, 70, 108, 108, 73, 74, 75, 108, 65,
+ 108, 67, 80, 108, 70, 83, 84, 73, 74, 75,
+ 108, 89, 90, 91, 80, 93, 94, 83, 84, 108,
+ 108, 108, 108, 89, 90, 91, 108, 93, 94, 65,
+ 108, 67, 108, 108, 70, 108, 108, 73, 74, 75,
+ 108, 108, 108, 108, 80, 108, 108, 83, 84, 108,
+ 108, 108, 108, 89, 90, 91, 108, 93, 94, 108,
+ 108, 65, 108, 67, 108, 108, 70, 108, 108, 73,
+ 74, 75, 108, 65, 108, 67, 80, 108, 70, 83,
+ 84, 73, 74, 75, 108, 89, 90, 91, 80, 93,
+ 94, 83, 84, 108, 108, 108, 108, 89, 90, 91,
+ 108, 93, 94, 65, 108, 67, 108, 108, 70, 108,
+ 108, 73, 74, 75, 108, 108, 108, 108, 80, 108,
+ 108, 83, 84, 108, 108, 108, 108, 89, 90, 91,
+ 108, 93, 94, 108, 108, 65, 108, 67, 108, 108,
+ 70, 108, 108, 73, 74, 75, 108, 65, 108, 67,
+ 80, 108, 70, 83, 84, 73, 74, 75, 108, 89,
+ 90, 91, 80, 93, 94, 83, 84, 108, 108, 108,
+ 108, 89, 90, 91, 108, 93, 94, 65, 108, 67,
+ 108, 108, 70, 108, 108, 73, 74, 75, 108, 108,
+ 108, 108, 80, 108, 108, 83, 84, 108, 108, 108,
+ 108, 89, 90, 91, 108, 93, 94, 108, 108, 65,
+ 108, 67, 108, 108, 70, 108, 108, 73, 74, 75,
+ 108, 65, 108, 67, 80, 108, 70, 83, 84, 73,
+ 74, 75, 108, 89, 90, 91, 80, 93, 94, 83,
+ 84, 108, 108, 108, 108, 89, 90, 91, 108, 93,
+ 94, 65, 108, 67, 108, 108, 70, 108, 108, 73,
+ 74, 75, 108, 108, 108, 108, 80, 108, 108, 83,
+ 84, 108, 108, 108, 108, 89, 90, 91, 108, 93,
+ 94, 108, 108, 65, 108, 67, 108, 108, 70, 108,
+ 108, 73, 74, 75, 108, 65, 108, 67, 80, 108,
+ 70, 83, 84, 73, 74, 75, 108, 89, 90, 91,
+ 80, 93, 94, 83, 84, 108, 108, 108, 108, 89,
+ 90, 91, 108, 93, 94, 65, 108, 67, 108, 108,
+ 70, 108, 108, 73, 74, 75, 108, 108, 108, 108,
+ 80, 108, 108, 83, 84, 108, 108, 108, 108, 89,
+ 90, 91, 108, 93, 94, 108, 108, 65, 108, 67,
+ 108, 108, 70, 108, 108, 73, 74, 75, 108, 65,
+ 108, 67, 80, 108, 70, 83, 84, 73, 74, 75,
+ 108, 89, 90, 91, 80, 93, 94, 83, 84, 108,
+ 108, 108, 108, 89, 90, 91, 108, 93, 94, 65,
+ 108, 67, 108, 108, 70, 108, 108, 73, 74, 75,
+ 108, 108, 108, 108, 80, 108, 108, 83, 84, 108,
+ 108, 108, 108, 89, 90, 91, 108, 93, 94, 108,
+ 108, 65, 108, 67, 108, 108, 70, 108, 108, 73,
+ 74, 75, 108, 65, 108, 67, 80, 108, 70, 83,
+ 84, 73, 74, 75, 108, 89, 90, 91, 80, 93,
+ 94, 83, 84, 108, 108, 108, 108, 89, 90, 91,
+ 108, 93, 94, 65, 108, 67, 108, 108, 70, 108,
+ 108, 73, 74, 75, 108, 108, 108, 108, 80, 108,
+ 108, 83, 84, 108, 108, 108, 108, 89, 90, 91,
+ 108, 93, 94, 108, 108, 65, 108, 67, 108, 108,
+ 70, 108, 108, 73, 74, 75, 108, 65, 108, 67,
+ 80, 108, 70, 83, 84, 73, 74, 75, 108, 89,
+ 90, 91, 80, 93, 94, 83, 84, 108, 108, 108,
+ 108, 89, 90, 91, 108, 93, 94, 3, 20, 108,
+ 108, 108, 108, 9, 10, 11, 12, 108, 14, 108,
+ 108, 108, 18, 19, 36, 37, 38, 39, 24, 108,
+ 108, 108, 28, 29, 30, 108, 108, 23, 108, 25,
+ 108, 53, 54, 55, 56, 31, 108, 108, 108, 108,
+ 36, 37, 38, 39, 108, 108, 108, 108, 108, 108,
+ 108, 108, 58, 59, 108, 108, 3, 53, 54, 55,
+ 56, 108, 9, 10, 11, 12, 108, 14, 108, 108,
+ 108, 18, 19, 108, 108, 108, 108, 24, 108, 25,
+ 108, 28, 29, 30, 108, 31, 108, 108, 108, 108,
+ 36, 37, 38, 39, 108, 108, 108, 108, 108, 108,
+ 108, 108, 108, 108, 108, 108, 108, 53, 54, 55,
+ 56, 58, 59, 108, 108, 3, 4, 5, 6, 108,
+ 108, 9, 10, 11, 12, 108, 108, 108, 108, 108,
+ 18, 19, 108, 108, 108, 108, 24, 108, 108, 108,
+ 28, 29, 30, 65, 108, 67, 108, 108, 70, 108,
+ 108, 73, 74, 108, 108, 108, 108, 108, 80, 108,
+ 108, 108, 108, 108, 108, 108, 88, 89, 90, 91,
+ 108, 93, 94, 108, 108, 108, 108, 108, 65, 108,
+ 67, 108, 108, 70, 108, 108, 73, 74, 108, 108,
+ 65, 108, 67, 80, 108, 70, 108, 108, 73, 74,
+ 108, 88, 89, 90, 91, 80, 93, 94, 108, 108,
+ 108, 108, 108, 108, 89, 90, 91, 108, 93, 94,
+ 108, 108, 108, 108, 65, 108, 67, 108, 108, 70,
+ 108, 108, 73, 74, 108, 108, 108, 108, 108, 80,
+ 108, 108, 108, 108, 108, 108, 108, 108, 89, 90,
+ 91, 108, 93, 94, 108, 108, 65, 108, 67, 108,
+ 108, 70, 108, 108, 73, 74, 108, 108, 65, 108,
+ 67, 80, 108, 70, 108, 108, 73, 74, 108, 108,
+ 89, 90, 91, 80, 93, 94, 108, 108, 108, 108,
+ 108, 108, 89, 90, 91, 108, 93, 94, 65, 108,
+ 67, 108, 108, 70, 108, 108, 73, 74, 108, 108,
+ 108, 108, 108, 80, 108, 108, 108, 108, 108, 108,
+ 108, 108, 89, 90, 91, 108, 93, 94, 108, 108,
+ 65, 108, 67, 108, 108, 70, 108, 108, 73, 74,
+ 108, 108, 65, 108, 67, 80, 108, 70, 108, 108,
+ 73, 74, 108, 108, 89, 90, 91, 80, 93, 94,
+ 108, 108, 2, 108, 108, 108, 89, 90, 91, 9,
+ 93, 94, 12, 2, 108, 15, 16, 17, 108, 108,
+ 9, 21, 108, 12, 108, 108, 2, 16, 17, 108,
+ 108, 9, 21, 9, 12, 108, 12, 108, 16, 15,
+ 16, 17, 108, 43, 2, 21, 46, 25, 48, 27,
+ 50, 108, 108, 31, 43, 108, 108, 46, 108, 48,
+ 108, 50, 51, 108, 108, 108, 108, 43, 108, 108,
+ 46, 108, 48, 108, 50, 2, 34, 108, 36, 37,
+ 38, 39, 9, 108, 108, 12, 108, 108, 46, 16,
+ 17, 108, 2, 108, 21, 53, 54, 55, 56, 9,
+ 108, 108, 12, 108, 13, 108, 16, 17, 108, 108,
+ 108, 21, 108, 108, 108, 108, 43, 108, 108, 46,
+ 108, 48, 108, 50, 108, 108, 108, 36, 37, 38,
+ 39, 108, 108, 43, 108, 13, 46, 108, 48, 108,
+ 50, 108, 108, 108, 53, 54, 55, 56, 36, 37,
+ 38, 39, 35, 36, 37, 38, 39, 108, 36, 37,
+ 38, 39, 13, 51, 2, 53, 54, 55, 56, 13,
+ 53, 54, 55, 56, 108, 53, 54, 55, 56, 108,
+ 108, 108, 108, 108, 108, 36, 37, 38, 39, 108,
+ 108, 108, 36, 37, 38, 39, 108, 108, 36, 37,
+ 38, 39, 53, 54, 55, 56, 13, 108, 108, 53,
+ 54, 55, 56, 108, 108, 53, 54, 55, 56, 9,
+ 108, 108, 12, 108, 108, 108, 16, 17, 108, 36,
+ 37, 38, 39, 108, 108, 108, 108, 108, 108, 108,
+ 108, 108, 108, 108, 108, 108, 53, 54, 55, 56,
+ 108, 108, 108, 43, 108, 108, 46, 108, 48, 108,
+ 50,
+);
+ const YY_SHIFT_USE_DFLT = -29;
+ const YY_SHIFT_MAX = 234;
+ public static $yy_shift_ofst = array(
+ -29, 98, 98, 148, 198, 198, 248, 148, 148, 198,
+ 148, 248, -2, 48, 298, 148, 148, 148, 298, 148,
+ 148, 148, 148, 148, 148, 148, 148, 148, 148, 148,
+ 348, 148, 148, 148, 148, 398, 148, 148, 148, 448,
+ 498, 498, 498, 498, 498, 498, 498, 498, 1574, 1624,
+ 1624, 147, 1564, 688, 285, 140, 675, 1623, 1548, 627,
+ 2021, 2047, 2042, 2052, 415, 2079, 2086, 2092, 2123, 465,
+ 465, 465, 465, 465, 465, 465, 465, 465, 465, 465,
+ 465, 465, 465, 465, 465, 465, 465, 1952, 956, 136,
+ 87, 675, 675, 140, 140, 150, 1682, 1930, 561, 59,
+ 820, 853, 886, 337, 337, 103, 250, 273, 250, 406,
+ 314, 156, 215, 215, 203, 382, 402, 250, 19, 19,
+ 19, 19, 19, 19, 19, 19, 17, 17, 78, 19,
+ -29, -29, 1941, 1954, 2003, 2020, 2140, 49, 918, 583,
+ 236, 250, 250, 308, 250, 390, 250, 390, 250, 368,
+ 368, 250, 250, 250, 250, 368, 153, 368, 368, 368,
+ 436, 368, 436, 368, 250, 250, 250, 250, 19, 463,
+ 19, 19, 463, 19, 499, 17, 17, 17, -29, -29,
+ -29, -29, -29, -29, 1972, 11, 100, 190, 240, 928,
+ -28, 191, 342, 616, 362, 517, 104, 433, 452, 429,
+ 453, 477, 411, 459, 41, 486, 480, 501, 536, 547,
+ 574, 587, 585, 586, 592, 594, 595, 608, 609, 589,
+ 596, 602, 610, 638, 499, 639, 607, 619, 654, 624,
+ 634, 674, 671, 656, 677,
+);
+ const YY_REDUCE_USE_DFLT = -88;
+ const YY_REDUCE_MAX = 183;
+ public static $yy_reduce_ofst = array(
+ -59, 492, 523, 556, 568, 604, 637, 670, 703, 736,
+ 769, 802, 835, 868, 880, 910, 942, 954, 984, 1016,
+ 1028, 1058, 1090, 1102, 1132, 1164, 1176, 1206, 1238, 1250,
+ 1280, 1312, 1324, 1354, 1386, 1398, 1428, 1460, 1472, 1648,
+ 1683, 1695, 1729, 1761, 1773, 1803, 1835, 1847, 533, 680,
+ 713, -69, 77, 99, 281, 491, 502, 29, -34, -34,
+ -34, -34, -34, -34, -34, -34, -34, -34, -34, -34,
+ -34, -34, -34, -34, -34, -34, -34, -34, -34, -34,
+ -34, -34, -34, -34, -34, -34, -34, 239, 299, -12,
+ 175, 222, 231, 143, 172, 318, 3, 34, 26, -51,
+ 194, 194, 194, 73, 26, 275, 164, 272, 220, 320,
+ 242, 275, -87, 287, 194, 194, 194, 341, 323, 194,
+ 194, 194, 194, 194, 194, 194, 275, 335, 194, 194,
+ 420, 194, 129, 129, 129, 129, 129, 183, -14, 129,
+ 129, 151, 151, 189, 151, 344, 151, 356, 151, 187,
+ 187, 151, 151, 151, 151, 187, 225, 187, 187, 187,
+ 270, 187, 366, 187, 151, 151, 151, 151, 313, 428,
+ 313, 313, 428, 313, 447, 445, 445, 445, 482, 468,
+ 471, 472, 469, 485,
+);
+ public static $yyExpectedTokens = array(
+ array(),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(2, 9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 39, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 51, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 21, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(9, 10, 11, 12, 14, 16, 18, 19, 24, 28, 29, 30, 32, 34, 37, 40, 41, 42, 43, 45, 47, 49, 50, 52, 57, 58, ),
+ array(23, 25, 31, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(25, 31, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(25, 31, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(14, 16, 47, 49, 52, ),
+ array(3, 9, 10, 11, 12, 14, 18, 19, 24, 28, 29, 30, 58, 59, ),
+ array(1, 13, 17, 25, 31, 34, 46, ),
+ array(14, 16, 49, 52, ),
+ array(14, 34, 52, ),
+ array(1, 25, 31, ),
+ array(3, 9, 10, 11, 12, 14, 18, 19, 24, 28, 29, 30, 58, 59, ),
+ array(20, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(26, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(13, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(35, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 51, 53, 54, 55, 56, ),
+ array(13, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, 59, ),
+ array(13, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(13, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(2, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(13, 36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(36, 37, 38, 39, 53, 54, 55, 56, ),
+ array(9, 12, 16, 25, 27, 31, ),
+ array(9, 12, 16, 25, 31, ),
+ array(17, 43, 50, ),
+ array(15, 17, 46, ),
+ array(1, 25, 31, ),
+ array(1, 25, 31, ),
+ array(14, 34, 52, ),
+ array(14, 34, 52, ),
+ array(1, 2, ),
+ array(3, 4, 5, 6, 9, 10, 11, 12, 18, 19, 24, 28, 29, 30, ),
+ array(2, 9, 12, 15, 16, 17, 21, 43, 46, 48, 50, ),
+ array(9, 12, 16, 48, ),
+ array(12, 14, 16, 52, ),
+ array(1, 13, 25, 31, ),
+ array(1, 13, 25, 31, ),
+ array(1, 13, 25, 31, ),
+ array(9, 12, 16, ),
+ array(9, 12, 16, ),
+ array(15, 17, 46, ),
+ array(25, 31, ),
+ array(14, 52, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(1, 17, ),
+ array(17, 46, ),
+ array(14, 16, ),
+ array(14, 16, ),
+ array(1, 51, ),
+ array(1, 2, ),
+ array(1, 27, ),
+ array(25, 31, ),
+ array(1, ),
+ array(1, ),
+ array(1, ),
+ array(1, ),
+ array(1, ),
+ array(1, ),
+ array(1, ),
+ array(1, ),
+ array(17, ),
+ array(17, ),
+ array(1, ),
+ array(1, ),
+ array(),
+ array(),
+ array(2, 9, 12, 16, 17, 21, 43, 46, 48, 50, 51, ),
+ array(2, 9, 12, 15, 16, 17, 21, 43, 46, 48, 50, ),
+ array(2, 9, 12, 16, 17, 21, 43, 46, 48, 50, ),
+ array(2, 9, 12, 16, 17, 21, 43, 46, 48, 50, ),
+ array(9, 12, 16, 17, 43, 46, 48, 50, ),
+ array(12, 14, 16, 32, 52, ),
+ array(9, 12, 16, 48, ),
+ array(9, 12, 16, ),
+ array(15, 43, 50, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(15, 21, ),
+ array(25, 31, ),
+ array(14, 52, ),
+ array(25, 31, ),
+ array(14, 52, ),
+ array(25, 31, ),
+ array(43, 50, ),
+ array(43, 50, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(43, 50, ),
+ array(12, 34, ),
+ array(43, 50, ),
+ array(43, 50, ),
+ array(43, 50, ),
+ array(43, 50, ),
+ array(43, 50, ),
+ array(43, 50, ),
+ array(43, 50, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(25, 31, ),
+ array(1, ),
+ array(2, ),
+ array(1, ),
+ array(1, ),
+ array(2, ),
+ array(1, ),
+ array(34, ),
+ array(17, ),
+ array(17, ),
+ array(17, ),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(2, 34, 36, 37, 38, 39, 46, 53, 54, 55, 56, ),
+ array(13, 20, 22, 25, 31, 33, 35, 43, ),
+ array(13, 15, 25, 31, 34, 46, ),
+ array(13, 21, 25, 31, 44, ),
+ array(13, 21, 25, 31, 44, ),
+ array(9, 12, 16, 48, ),
+ array(34, 43, 46, 51, ),
+ array(27, 34, 46, ),
+ array(21, 44, 59, ),
+ array(21, 44, 51, ),
+ array(6, 8, ),
+ array(7, 8, ),
+ array(20, 33, ),
+ array(16, 48, ),
+ array(21, 44, ),
+ array(34, 46, ),
+ array(34, 46, ),
+ array(34, 46, ),
+ array(33, 35, ),
+ array(33, 35, ),
+ array(33, 51, ),
+ array(43, 51, ),
+ array(33, 35, ),
+ array(33, 35, ),
+ array(15, 43, ),
+ array(7, ),
+ array(13, ),
+ array(13, ),
+ array(16, ),
+ array(16, ),
+ array(16, ),
+ array(16, ),
+ array(16, ),
+ array(14, ),
+ array(16, ),
+ array(43, ),
+ array(32, ),
+ array(32, ),
+ array(34, ),
+ array(16, ),
+ array(34, ),
+ array(16, ),
+ array(49, ),
+ array(49, ),
+ array(16, ),
+ array(51, ),
+ array(51, ),
+ array(16, ),
+ array(15, ),
+ array(35, ),
+ array(22, ),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+ array(),
+);
+ public static $yy_default = array(
+ 342, 523, 523, 523, 508, 508, 523, 485, 485, 523,
+ 485, 523, 523, 523, 523, 523, 523, 523, 523, 523,
+ 523, 523, 523, 523, 523, 523, 523, 523, 523, 523,
+ 523, 523, 523, 523, 523, 523, 523, 523, 523, 523,
+ 523, 523, 523, 523, 523, 523, 523, 523, 382, 361,
+ 382, 523, 523, 424, 523, 523, 382, 523, 523, 387,
+ 523, 523, 523, 355, 523, 523, 523, 523, 523, 366,
+ 484, 405, 411, 483, 509, 511, 510, 410, 412, 409,
+ 413, 389, 393, 394, 384, 387, 355, 382, 382, 498,
+ 440, 382, 382, 523, 523, 373, 332, 439, 450, 523,
+ 396, 396, 396, 450, 450, 440, 382, 523, 382, 382,
+ 376, 440, 523, 523, 396, 396, 396, 363, 378, 396,
+ 403, 415, 416, 417, 404, 408, 440, 495, 415, 402,
+ 340, 492, 439, 439, 439, 439, 439, 523, 452, 450,
+ 466, 352, 362, 523, 365, 523, 370, 523, 371, 447,
+ 448, 356, 358, 359, 360, 476, 450, 475, 478, 477,
+ 443, 444, 445, 446, 372, 368, 369, 364, 374, 486,
+ 377, 379, 487, 433, 450, 472, 499, 496, 340, 491,
+ 491, 491, 450, 450, 424, 420, 424, 414, 414, 451,
+ 424, 424, 414, 414, 338, 523, 523, 523, 414, 424,
+ 434, 523, 523, 523, 523, 420, 523, 523, 420, 523,
+ 523, 523, 523, 523, 523, 523, 523, 523, 523, 420,
+ 422, 523, 497, 523, 466, 523, 523, 523, 523, 523,
+ 429, 523, 523, 523, 390, 333, 334, 335, 336, 337,
+ 339, 341, 343, 344, 345, 346, 347, 348, 349, 351,
+ 380, 381, 468, 469, 470, 490, 375, 488, 489, 418,
+ 427, 428, 437, 438, 449, 453, 454, 455, 397, 398,
+ 399, 400, 401, 419, 421, 423, 425, 429, 430, 431,
+ 406, 407, 432, 435, 436, 463, 461, 500, 501, 502,
+ 503, 441, 442, 474, 467, 482, 350, 473, 519, 520,
+ 512, 513, 514, 517, 516, 518, 521, 522, 515, 505,
+ 507, 506, 504, 479, 464, 462, 460, 457, 458, 459,
+ 465, 480, 481, 426, 456, 494, 471, 466, 383, 367,
+ 391, 395,
+);
+ const YYNOCODE = 109;
+ const YYSTACKDEPTH = 500;
+ const YYNSTATE = 332;
+ const YYNRULE = 191;
+ const YYERRORSYMBOL = 60;
+ const YYERRSYMDT = 'yy0';
+ const YYFALLBACK = 0;
+ public static $yyFallback = array(
+ );
+ public function Trace($TraceFILE, $zTracePrompt)
+ {
+ if (!$TraceFILE) {
+ $zTracePrompt = 0;
+ } elseif (!$zTracePrompt) {
+ $TraceFILE = 0;
+ }
+ $this->yyTraceFILE = $TraceFILE;
+ $this->yyTracePrompt = $zTracePrompt;
+ }
+
+ public function PrintTrace()
+ {
+ $this->yyTraceFILE = fopen('php://output', 'w');
+ $this->yyTracePrompt = '<br>';
+ }
+
+ public $yyTraceFILE;
+ public $yyTracePrompt;
+ public $yyidx; /* Index of top element in stack */
+ public $yyerrcnt; /* Shifts left before out of the error */
+ public $yystack = array(); /* The parser's stack */
+
+ public $yyTokenName = array(
+ '$', 'VERT', 'COLON', 'TEXT',
+ 'STRIPON', 'STRIPOFF', 'LITERALSTART', 'LITERALEND',
+ 'LITERAL', 'SIMPELOUTPUT', 'SIMPLETAG', 'SMARTYBLOCKCHILDPARENT',
+ 'LDEL', 'RDEL', 'DOLLARID', 'EQUAL',
+ 'ID', 'PTR', 'LDELIF', 'LDELFOR',
+ 'SEMICOLON', 'INCDEC', 'TO', 'STEP',
+ 'LDELFOREACH', 'SPACE', 'AS', 'APTR',
+ 'LDELSETFILTER', 'CLOSETAG', 'LDELSLASH', 'ATTR',
+ 'INTEGER', 'COMMA', 'OPENP', 'CLOSEP',
+ 'MATH', 'UNIMATH', 'ISIN', 'QMARK',
+ 'NOT', 'TYPECAST', 'HEX', 'DOT',
+ 'INSTANCEOF', 'SINGLEQUOTESTRING', 'DOUBLECOLON', 'NAMESPACE',
+ 'AT', 'HATCH', 'OPENB', 'CLOSEB',
+ 'DOLLAR', 'LOGOP', 'SLOGOP', 'TLOGOP',
+ 'SINGLECOND', 'ARRAYOPEN', 'QUOTE', 'BACKTICK',
+ 'error', 'start', 'template', 'literal_e2',
+ 'literal_e1', 'smartytag', 'tagbody', 'tag',
+ 'outattr', 'eqoutattr', 'varindexed', 'output',
+ 'attributes', 'variable', 'value', 'expr',
+ 'modifierlist', 'statement', 'statements', 'foraction',
+ 'varvar', 'modparameters', 'attribute', 'nullcoalescing',
+ 'ternary', 'tlop', 'lop', 'scond',
+ 'array', 'function', 'ns1', 'doublequoted_with_quotes',
+ 'static_class_access', 'arraydef', 'object', 'arrayindex',
+ 'indexdef', 'varvarele', 'objectchain', 'objectelement',
+ 'method', 'params', 'modifier', 'modparameter',
+ 'arrayelements', 'arrayelement', 'doublequoted', 'doublequotedcontent',
+ );
+
+ public static $yyRuleName = array(
+ 'start ::= template',
+ 'template ::= template TEXT',
+ 'template ::= template STRIPON',
+ 'template ::= template STRIPOFF',
+ 'template ::= template LITERALSTART literal_e2 LITERALEND',
+ 'literal_e2 ::= literal_e1 LITERALSTART literal_e1 LITERALEND',
+ 'literal_e2 ::= literal_e1',
+ 'literal_e1 ::= literal_e1 LITERAL',
+ 'literal_e1 ::=',
+ 'template ::= template smartytag',
+ 'template ::=',
+ 'smartytag ::= SIMPELOUTPUT',
+ 'smartytag ::= SIMPLETAG',
+ 'smartytag ::= SMARTYBLOCKCHILDPARENT',
+ 'smartytag ::= LDEL tagbody RDEL',
+ 'smartytag ::= tag RDEL',
+ 'tagbody ::= outattr',
+ 'tagbody ::= DOLLARID eqoutattr',
+ 'tagbody ::= varindexed eqoutattr',
+ 'eqoutattr ::= EQUAL outattr',
+ 'outattr ::= output attributes',
+ 'output ::= variable',
+ 'output ::= value',
+ 'output ::= expr',
+ 'tag ::= LDEL ID attributes',
+ 'tag ::= LDEL ID',
+ 'tag ::= LDEL ID modifierlist attributes',
+ 'tag ::= LDEL ID PTR ID attributes',
+ 'tag ::= LDEL ID PTR ID modifierlist attributes',
+ 'tag ::= LDELIF expr',
+ 'tag ::= LDELIF expr attributes',
+ 'tag ::= LDELIF statement',
+ 'tag ::= LDELIF statement attributes',
+ 'tag ::= LDELFOR statements SEMICOLON expr SEMICOLON varindexed foraction attributes',
+ 'foraction ::= EQUAL expr',
+ 'foraction ::= INCDEC',
+ 'tag ::= LDELFOR statement TO expr attributes',
+ 'tag ::= LDELFOR statement TO expr STEP expr attributes',
+ 'tag ::= LDELFOREACH SPACE expr AS varvar attributes',
+ 'tag ::= LDELFOREACH SPACE expr AS varvar APTR varvar attributes',
+ 'tag ::= LDELFOREACH attributes',
+ 'tag ::= LDELSETFILTER ID modparameters',
+ 'tag ::= LDELSETFILTER ID modparameters modifierlist',
+ 'smartytag ::= CLOSETAG',
+ 'tag ::= LDELSLASH ID',
+ 'tag ::= LDELSLASH ID modifierlist',
+ 'tag ::= LDELSLASH ID PTR ID',
+ 'tag ::= LDELSLASH ID PTR ID modifierlist',
+ 'attributes ::= attributes attribute',
+ 'attributes ::= attribute',
+ 'attributes ::=',
+ 'attribute ::= SPACE ID EQUAL ID',
+ 'attribute ::= ATTR expr',
+ 'attribute ::= ATTR value',
+ 'attribute ::= SPACE ID',
+ 'attribute ::= SPACE expr',
+ 'attribute ::= SPACE value',
+ 'attribute ::= SPACE INTEGER EQUAL expr',
+ 'statements ::= statement',
+ 'statements ::= statements COMMA statement',
+ 'statement ::= DOLLARID EQUAL INTEGER',
+ 'statement ::= DOLLARID EQUAL expr',
+ 'statement ::= varindexed EQUAL expr',
+ 'statement ::= OPENP statement CLOSEP',
+ 'expr ::= value',
+ 'expr ::= nullcoalescing',
+ 'expr ::= ternary',
+ 'expr ::= INCDEC DOLLARID',
+ 'expr ::= DOLLARID INCDEC',
+ 'expr ::= DOLLARID COLON ID',
+ 'expr ::= expr MATH value',
+ 'expr ::= expr UNIMATH value',
+ 'expr ::= expr tlop value',
+ 'expr ::= expr lop expr',
+ 'expr ::= expr scond',
+ 'expr ::= expr ISIN array',
+ 'expr ::= expr ISIN value',
+ 'nullcoalescing ::= expr QMARK QMARK expr',
+ 'ternary ::= expr QMARK DOLLARID COLON expr',
+ 'ternary ::= expr QMARK value COLON expr',
+ 'ternary ::= expr QMARK expr COLON expr',
+ 'ternary ::= expr QMARK COLON expr',
+ 'value ::= variable',
+ 'value ::= UNIMATH value',
+ 'value ::= NOT value',
+ 'value ::= TYPECAST value',
+ 'value ::= variable INCDEC',
+ 'value ::= HEX',
+ 'value ::= INTEGER',
+ 'value ::= INTEGER DOT INTEGER',
+ 'value ::= INTEGER DOT',
+ 'value ::= DOT INTEGER',
+ 'value ::= ID',
+ 'value ::= function',
+ 'value ::= OPENP expr CLOSEP',
+ 'value ::= variable INSTANCEOF ns1',
+ 'value ::= variable INSTANCEOF variable',
+ 'value ::= SINGLEQUOTESTRING',
+ 'value ::= doublequoted_with_quotes',
+ 'value ::= varindexed DOUBLECOLON static_class_access',
+ 'value ::= smartytag',
+ 'value ::= value modifierlist',
+ 'value ::= NAMESPACE',
+ 'value ::= arraydef',
+ 'value ::= ns1 DOUBLECOLON static_class_access',
+ 'ns1 ::= ID',
+ 'ns1 ::= NAMESPACE',
+ 'variable ::= DOLLARID',
+ 'variable ::= varindexed',
+ 'variable ::= varvar AT ID',
+ 'variable ::= object',
+ 'variable ::= HATCH ID HATCH',
+ 'variable ::= HATCH ID HATCH arrayindex',
+ 'variable ::= HATCH variable HATCH',
+ 'variable ::= HATCH variable HATCH arrayindex',
+ 'varindexed ::= DOLLARID arrayindex',
+ 'varindexed ::= varvar arrayindex',
+ 'arrayindex ::= arrayindex indexdef',
+ 'arrayindex ::=',
+ 'indexdef ::= DOT DOLLARID',
+ 'indexdef ::= DOT varvar',
+ 'indexdef ::= DOT varvar AT ID',
+ 'indexdef ::= DOT ID',
+ 'indexdef ::= DOT INTEGER',
+ 'indexdef ::= DOT LDEL expr RDEL',
+ 'indexdef ::= OPENB ID CLOSEB',
+ 'indexdef ::= OPENB ID DOT ID CLOSEB',
+ 'indexdef ::= OPENB SINGLEQUOTESTRING CLOSEB',
+ 'indexdef ::= OPENB INTEGER CLOSEB',
+ 'indexdef ::= OPENB DOLLARID CLOSEB',
+ 'indexdef ::= OPENB variable CLOSEB',
+ 'indexdef ::= OPENB value CLOSEB',
+ 'indexdef ::= OPENB expr CLOSEB',
+ 'indexdef ::= OPENB CLOSEB',
+ 'varvar ::= DOLLARID',
+ 'varvar ::= DOLLAR',
+ 'varvar ::= varvar varvarele',
+ 'varvarele ::= ID',
+ 'varvarele ::= SIMPELOUTPUT',
+ 'varvarele ::= LDEL expr RDEL',
+ 'object ::= varindexed objectchain',
+ 'objectchain ::= objectelement',
+ 'objectchain ::= objectchain objectelement',
+ 'objectelement ::= PTR ID arrayindex',
+ 'objectelement ::= PTR varvar arrayindex',
+ 'objectelement ::= PTR LDEL expr RDEL arrayindex',
+ 'objectelement ::= PTR ID LDEL expr RDEL arrayindex',
+ 'objectelement ::= PTR method',
+ 'function ::= ns1 OPENP params CLOSEP',
+ 'method ::= ID OPENP params CLOSEP',
+ 'method ::= DOLLARID OPENP params CLOSEP',
+ 'params ::= params COMMA expr',
+ 'params ::= expr',
+ 'params ::=',
+ 'modifierlist ::= modifierlist modifier modparameters',
+ 'modifierlist ::= modifier modparameters',
+ 'modifier ::= VERT AT ID',
+ 'modifier ::= VERT ID',
+ 'modparameters ::= modparameters modparameter',
+ 'modparameters ::=',
+ 'modparameter ::= COLON value',
+ 'modparameter ::= COLON UNIMATH value',
+ 'modparameter ::= COLON array',
+ 'static_class_access ::= method',
+ 'static_class_access ::= method objectchain',
+ 'static_class_access ::= ID',
+ 'static_class_access ::= DOLLARID arrayindex',
+ 'static_class_access ::= DOLLARID arrayindex objectchain',
+ 'lop ::= LOGOP',
+ 'lop ::= SLOGOP',
+ 'tlop ::= TLOGOP',
+ 'scond ::= SINGLECOND',
+ 'arraydef ::= OPENB arrayelements CLOSEB',
+ 'arraydef ::= ARRAYOPEN arrayelements CLOSEP',
+ 'arrayelements ::= arrayelement',
+ 'arrayelements ::= arrayelements COMMA arrayelement',
+ 'arrayelements ::=',
+ 'arrayelement ::= value APTR expr',
+ 'arrayelement ::= ID APTR expr',
+ 'arrayelement ::= expr',
+ 'doublequoted_with_quotes ::= QUOTE QUOTE',
+ 'doublequoted_with_quotes ::= QUOTE doublequoted QUOTE',
+ 'doublequoted ::= doublequoted doublequotedcontent',
+ 'doublequoted ::= doublequotedcontent',
+ 'doublequotedcontent ::= BACKTICK variable BACKTICK',
+ 'doublequotedcontent ::= BACKTICK expr BACKTICK',
+ 'doublequotedcontent ::= DOLLARID',
+ 'doublequotedcontent ::= LDEL variable RDEL',
+ 'doublequotedcontent ::= LDEL expr RDEL',
+ 'doublequotedcontent ::= smartytag',
+ 'doublequotedcontent ::= TEXT',
+ );
+
+ public function tokenName($tokenType)
+ {
+ if ($tokenType === 0) {
+ return 'End of Input';
+ }
+ if ($tokenType > 0 && $tokenType < count($this->yyTokenName)) {
+ return $this->yyTokenName[$tokenType];
+ } else {
+ return 'Unknown';
+ }
+ }
+
+ public static function yy_destructor($yymajor, $yypminor)
+ {
+ switch ($yymajor) {
+ default: break; /* If no destructor action specified: do nothing */
+ }
+ }
+
+ public function yy_pop_parser_stack()
+ {
+ if (empty($this->yystack)) {
+ return;
+ }
+ $yytos = array_pop($this->yystack);
+ if ($this->yyTraceFILE && $this->yyidx >= 0) {
+ fwrite($this->yyTraceFILE,
+ $this->yyTracePrompt . 'Popping ' . $this->yyTokenName[$yytos->major] .
+ "\n");
+ }
+ $yymajor = $yytos->major;
+ self::yy_destructor($yymajor, $yytos->minor);
+ $this->yyidx--;
+
+ return $yymajor;
+ }
+
+ public function __destruct()
+ {
+ while ($this->yystack !== Array()) {
+ $this->yy_pop_parser_stack();
+ }
+ if (is_resource($this->yyTraceFILE)) {
+ fclose($this->yyTraceFILE);
+ }
+ }
+
+ public function yy_get_expected_tokens($token)
+ {
+ static $res3 = array();
+ static $res4 = array();
+ $state = $this->yystack[$this->yyidx]->stateno;
+ $expected = self::$yyExpectedTokens[$state];
+ if (isset($res3[$state][$token])) {
+ if ($res3[$state][$token]) {
+ return $expected;
+ }
+ } else {
+ if ($res3[$state][$token] = in_array($token, self::$yyExpectedTokens[$state], true)) {
+ return $expected;
+ }
+ }
+ $stack = $this->yystack;
+ $yyidx = $this->yyidx;
+ do {
+ $yyact = $this->yy_find_shift_action($token);
+ if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+ // reduce action
+ $done = 0;
+ do {
+ if ($done++ === 100) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // too much recursion prevents proper detection
+ // so give up
+ return array_unique($expected);
+ }
+ $yyruleno = $yyact - self::YYNSTATE;
+ $this->yyidx -= self::$yyRuleInfo[$yyruleno][1];
+ $nextstate = $this->yy_find_reduce_action(
+ $this->yystack[$this->yyidx]->stateno,
+ self::$yyRuleInfo[$yyruleno][0]);
+ if (isset(self::$yyExpectedTokens[$nextstate])) {
+ $expected = array_merge($expected, self::$yyExpectedTokens[$nextstate]);
+ if (isset($res4[$nextstate][$token])) {
+ if ($res4[$nextstate][$token]) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return array_unique($expected);
+ }
+ } else {
+ if ($res4[$nextstate][$token] = in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return array_unique($expected);
+ }
+ }
+ }
+ if ($nextstate < self::YYNSTATE) {
+ // we need to shift a non-terminal
+ $this->yyidx++;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = $nextstate;
+ $x->major = self::$yyRuleInfo[$yyruleno][0];
+ $this->yystack[$this->yyidx] = $x;
+ continue 2;
+ } elseif ($nextstate === self::YYNSTATE + self::YYNRULE + 1) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // the last token was just ignored, we can't accept
+ // by ignoring input, this is in essence ignoring a
+ // syntax error!
+ return array_unique($expected);
+ } elseif ($nextstate === self::YY_NO_ACTION) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // input accepted, but not shifted (I guess)
+ return $expected;
+ } else {
+ $yyact = $nextstate;
+ }
+ } while (true);
+ }
+ break;
+ } while (true);
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+
+ return array_unique($expected);
+ }
+
+ public function yy_is_expected_token($token)
+ {
+ static $res = array();
+ static $res2 = array();
+ if ($token === 0) {
+ return true; // 0 is not part of this
+ }
+ $state = $this->yystack[$this->yyidx]->stateno;
+ if (isset($res[$state][$token])) {
+ if ($res[$state][$token]) {
+ return true;
+ }
+ } else {
+ if ($res[$state][$token] = in_array($token, self::$yyExpectedTokens[$state], true)) {
+ return true;
+ }
+ }
+ $stack = $this->yystack;
+ $yyidx = $this->yyidx;
+ do {
+ $yyact = $this->yy_find_shift_action($token);
+ if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+ // reduce action
+ $done = 0;
+ do {
+ if ($done++ === 100) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // too much recursion prevents proper detection
+ // so give up
+ return true;
+ }
+ $yyruleno = $yyact - self::YYNSTATE;
+ $this->yyidx -= self::$yyRuleInfo[$yyruleno][1];
+ $nextstate = $this->yy_find_reduce_action(
+ $this->yystack[$this->yyidx]->stateno,
+ self::$yyRuleInfo[$yyruleno][0]);
+ if (isset($res2[$nextstate][$token])) {
+ if ($res2[$nextstate][$token]) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return true;
+ }
+ } else {
+ if ($res2[$nextstate][$token] = (isset(self::$yyExpectedTokens[$nextstate]) && in_array($token, self::$yyExpectedTokens[$nextstate], true))) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ return true;
+ }
+ }
+ if ($nextstate < self::YYNSTATE) {
+ // we need to shift a non-terminal
+ $this->yyidx++;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = $nextstate;
+ $x->major = self::$yyRuleInfo[$yyruleno][0];
+ $this->yystack[$this->yyidx] = $x;
+ continue 2;
+ } elseif ($nextstate === self::YYNSTATE + self::YYNRULE + 1) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ if (!$token) {
+ // end of input: this is valid
+ return true;
+ }
+ // the last token was just ignored, we can't accept
+ // by ignoring input, this is in essence ignoring a
+ // syntax error!
+ return false;
+ } elseif ($nextstate === self::YY_NO_ACTION) {
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+ // input accepted, but not shifted (I guess)
+ return true;
+ } else {
+ $yyact = $nextstate;
+ }
+ } while (true);
+ }
+ break;
+ } while (true);
+ $this->yyidx = $yyidx;
+ $this->yystack = $stack;
+
+ return true;
+ }
+
+ public function yy_find_shift_action($iLookAhead)
+ {
+ $stateno = $this->yystack[$this->yyidx]->stateno;
+
+ /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
+ if (!isset(self::$yy_shift_ofst[$stateno])) {
+ // no shift actions
+ return self::$yy_default[$stateno];
+ }
+ $i = self::$yy_shift_ofst[$stateno];
+ if ($i === self::YY_SHIFT_USE_DFLT) {
+ return self::$yy_default[$stateno];
+ }
+ if ($iLookAhead === self::YYNOCODE) {
+ return self::YY_NO_ACTION;
+ }
+ $i += $iLookAhead;
+ if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+ self::$yy_lookahead[$i] != $iLookAhead) {
+ if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
+ && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
+ if ($this->yyTraceFILE) {
+ fwrite($this->yyTraceFILE, $this->yyTracePrompt . 'FALLBACK ' .
+ $this->yyTokenName[$iLookAhead] . ' => ' .
+ $this->yyTokenName[$iFallback] . "\n");
+ }
+
+ return $this->yy_find_shift_action($iFallback);
+ }
+
+ return self::$yy_default[$stateno];
+ } else {
+ return self::$yy_action[$i];
+ }
+ }
+
+ public function yy_find_reduce_action($stateno, $iLookAhead)
+ {
+ /* $stateno = $this->yystack[$this->yyidx]->stateno; */
+
+ if (!isset(self::$yy_reduce_ofst[$stateno])) {
+ return self::$yy_default[$stateno];
+ }
+ $i = self::$yy_reduce_ofst[$stateno];
+ if ($i === self::YY_REDUCE_USE_DFLT) {
+ return self::$yy_default[$stateno];
+ }
+ if ($iLookAhead === self::YYNOCODE) {
+ return self::YY_NO_ACTION;
+ }
+ $i += $iLookAhead;
+ if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+ self::$yy_lookahead[$i] != $iLookAhead) {
+ return self::$yy_default[$stateno];
+ } else {
+ return self::$yy_action[$i];
+ }
+ }
+
+ public function yy_shift($yyNewState, $yyMajor, $yypMinor)
+ {
+ $this->yyidx++;
+ if ($this->yyidx >= self::YYSTACKDEPTH) {
+ $this->yyidx--;
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sStack Overflow!\n", $this->yyTracePrompt);
+ }
+ while ($this->yyidx >= 0) {
+ $this->yy_pop_parser_stack();
+ }
+// line 232 "src/Parser/TemplateParser.y"
+
+ $this->internalError = true;
+ $this->compiler->trigger_template_error('Stack overflow in template parser');
+
+ return;
+ }
+ $yytos = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $yytos->stateno = $yyNewState;
+ $yytos->major = $yyMajor;
+ $yytos->minor = $yypMinor;
+ $this->yystack[] = $yytos;
+ if ($this->yyTraceFILE && $this->yyidx > 0) {
+ fprintf($this->yyTraceFILE, "%sShift %d\n", $this->yyTracePrompt,
+ $yyNewState);
+ fprintf($this->yyTraceFILE, "%sStack:", $this->yyTracePrompt);
+ for ($i = 1; $i <= $this->yyidx; $i++) {
+ fprintf($this->yyTraceFILE, " %s",
+ $this->yyTokenName[$this->yystack[$i]->major]);
+ }
+ fwrite($this->yyTraceFILE,"\n");
+ }
+ }
+
+ public static $yyRuleInfo = array(
+ array( 0 => 61, 1 => 1 ),
+ array( 0 => 62, 1 => 2 ),
+ array( 0 => 62, 1 => 2 ),
+ array( 0 => 62, 1 => 2 ),
+ array( 0 => 62, 1 => 4 ),
+ array( 0 => 63, 1 => 4 ),
+ array( 0 => 63, 1 => 1 ),
+ array( 0 => 64, 1 => 2 ),
+ array( 0 => 64, 1 => 0 ),
+ array( 0 => 62, 1 => 2 ),
+ array( 0 => 62, 1 => 0 ),
+ array( 0 => 65, 1 => 1 ),
+ array( 0 => 65, 1 => 1 ),
+ array( 0 => 65, 1 => 1 ),
+ array( 0 => 65, 1 => 3 ),
+ array( 0 => 65, 1 => 2 ),
+ array( 0 => 66, 1 => 1 ),
+ array( 0 => 66, 1 => 2 ),
+ array( 0 => 66, 1 => 2 ),
+ array( 0 => 69, 1 => 2 ),
+ array( 0 => 68, 1 => 2 ),
+ array( 0 => 71, 1 => 1 ),
+ array( 0 => 71, 1 => 1 ),
+ array( 0 => 71, 1 => 1 ),
+ array( 0 => 67, 1 => 3 ),
+ array( 0 => 67, 1 => 2 ),
+ array( 0 => 67, 1 => 4 ),
+ array( 0 => 67, 1 => 5 ),
+ array( 0 => 67, 1 => 6 ),
+ array( 0 => 67, 1 => 2 ),
+ array( 0 => 67, 1 => 3 ),
+ array( 0 => 67, 1 => 2 ),
+ array( 0 => 67, 1 => 3 ),
+ array( 0 => 67, 1 => 8 ),
+ array( 0 => 79, 1 => 2 ),
+ array( 0 => 79, 1 => 1 ),
+ array( 0 => 67, 1 => 5 ),
+ array( 0 => 67, 1 => 7 ),
+ array( 0 => 67, 1 => 6 ),
+ array( 0 => 67, 1 => 8 ),
+ array( 0 => 67, 1 => 2 ),
+ array( 0 => 67, 1 => 3 ),
+ array( 0 => 67, 1 => 4 ),
+ array( 0 => 65, 1 => 1 ),
+ array( 0 => 67, 1 => 2 ),
+ array( 0 => 67, 1 => 3 ),
+ array( 0 => 67, 1 => 4 ),
+ array( 0 => 67, 1 => 5 ),
+ array( 0 => 72, 1 => 2 ),
+ array( 0 => 72, 1 => 1 ),
+ array( 0 => 72, 1 => 0 ),
+ array( 0 => 82, 1 => 4 ),
+ array( 0 => 82, 1 => 2 ),
+ array( 0 => 82, 1 => 2 ),
+ array( 0 => 82, 1 => 2 ),
+ array( 0 => 82, 1 => 2 ),
+ array( 0 => 82, 1 => 2 ),
+ array( 0 => 82, 1 => 4 ),
+ array( 0 => 78, 1 => 1 ),
+ array( 0 => 78, 1 => 3 ),
+ array( 0 => 77, 1 => 3 ),
+ array( 0 => 77, 1 => 3 ),
+ array( 0 => 77, 1 => 3 ),
+ array( 0 => 77, 1 => 3 ),
+ array( 0 => 75, 1 => 1 ),
+ array( 0 => 75, 1 => 1 ),
+ array( 0 => 75, 1 => 1 ),
+ array( 0 => 75, 1 => 2 ),
+ array( 0 => 75, 1 => 2 ),
+ array( 0 => 75, 1 => 3 ),
+ array( 0 => 75, 1 => 3 ),
+ array( 0 => 75, 1 => 3 ),
+ array( 0 => 75, 1 => 3 ),
+ array( 0 => 75, 1 => 3 ),
+ array( 0 => 75, 1 => 2 ),
+ array( 0 => 75, 1 => 3 ),
+ array( 0 => 75, 1 => 3 ),
+ array( 0 => 83, 1 => 4 ),
+ array( 0 => 84, 1 => 5 ),
+ array( 0 => 84, 1 => 5 ),
+ array( 0 => 84, 1 => 5 ),
+ array( 0 => 84, 1 => 4 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 2 ),
+ array( 0 => 74, 1 => 2 ),
+ array( 0 => 74, 1 => 2 ),
+ array( 0 => 74, 1 => 2 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 3 ),
+ array( 0 => 74, 1 => 2 ),
+ array( 0 => 74, 1 => 2 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 3 ),
+ array( 0 => 74, 1 => 3 ),
+ array( 0 => 74, 1 => 3 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 3 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 2 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 1 ),
+ array( 0 => 74, 1 => 3 ),
+ array( 0 => 90, 1 => 1 ),
+ array( 0 => 90, 1 => 1 ),
+ array( 0 => 73, 1 => 1 ),
+ array( 0 => 73, 1 => 1 ),
+ array( 0 => 73, 1 => 3 ),
+ array( 0 => 73, 1 => 1 ),
+ array( 0 => 73, 1 => 3 ),
+ array( 0 => 73, 1 => 4 ),
+ array( 0 => 73, 1 => 3 ),
+ array( 0 => 73, 1 => 4 ),
+ array( 0 => 70, 1 => 2 ),
+ array( 0 => 70, 1 => 2 ),
+ array( 0 => 95, 1 => 2 ),
+ array( 0 => 95, 1 => 0 ),
+ array( 0 => 96, 1 => 2 ),
+ array( 0 => 96, 1 => 2 ),
+ array( 0 => 96, 1 => 4 ),
+ array( 0 => 96, 1 => 2 ),
+ array( 0 => 96, 1 => 2 ),
+ array( 0 => 96, 1 => 4 ),
+ array( 0 => 96, 1 => 3 ),
+ array( 0 => 96, 1 => 5 ),
+ array( 0 => 96, 1 => 3 ),
+ array( 0 => 96, 1 => 3 ),
+ array( 0 => 96, 1 => 3 ),
+ array( 0 => 96, 1 => 3 ),
+ array( 0 => 96, 1 => 3 ),
+ array( 0 => 96, 1 => 3 ),
+ array( 0 => 96, 1 => 2 ),
+ array( 0 => 80, 1 => 1 ),
+ array( 0 => 80, 1 => 1 ),
+ array( 0 => 80, 1 => 2 ),
+ array( 0 => 97, 1 => 1 ),
+ array( 0 => 97, 1 => 1 ),
+ array( 0 => 97, 1 => 3 ),
+ array( 0 => 94, 1 => 2 ),
+ array( 0 => 98, 1 => 1 ),
+ array( 0 => 98, 1 => 2 ),
+ array( 0 => 99, 1 => 3 ),
+ array( 0 => 99, 1 => 3 ),
+ array( 0 => 99, 1 => 5 ),
+ array( 0 => 99, 1 => 6 ),
+ array( 0 => 99, 1 => 2 ),
+ array( 0 => 89, 1 => 4 ),
+ array( 0 => 100, 1 => 4 ),
+ array( 0 => 100, 1 => 4 ),
+ array( 0 => 101, 1 => 3 ),
+ array( 0 => 101, 1 => 1 ),
+ array( 0 => 101, 1 => 0 ),
+ array( 0 => 76, 1 => 3 ),
+ array( 0 => 76, 1 => 2 ),
+ array( 0 => 102, 1 => 3 ),
+ array( 0 => 102, 1 => 2 ),
+ array( 0 => 81, 1 => 2 ),
+ array( 0 => 81, 1 => 0 ),
+ array( 0 => 103, 1 => 2 ),
+ array( 0 => 103, 1 => 3 ),
+ array( 0 => 103, 1 => 2 ),
+ array( 0 => 92, 1 => 1 ),
+ array( 0 => 92, 1 => 2 ),
+ array( 0 => 92, 1 => 1 ),
+ array( 0 => 92, 1 => 2 ),
+ array( 0 => 92, 1 => 3 ),
+ array( 0 => 86, 1 => 1 ),
+ array( 0 => 86, 1 => 1 ),
+ array( 0 => 85, 1 => 1 ),
+ array( 0 => 87, 1 => 1 ),
+ array( 0 => 93, 1 => 3 ),
+ array( 0 => 93, 1 => 3 ),
+ array( 0 => 104, 1 => 1 ),
+ array( 0 => 104, 1 => 3 ),
+ array( 0 => 104, 1 => 0 ),
+ array( 0 => 105, 1 => 3 ),
+ array( 0 => 105, 1 => 3 ),
+ array( 0 => 105, 1 => 1 ),
+ array( 0 => 91, 1 => 2 ),
+ array( 0 => 91, 1 => 3 ),
+ array( 0 => 106, 1 => 2 ),
+ array( 0 => 106, 1 => 1 ),
+ array( 0 => 107, 1 => 3 ),
+ array( 0 => 107, 1 => 3 ),
+ array( 0 => 107, 1 => 1 ),
+ array( 0 => 107, 1 => 3 ),
+ array( 0 => 107, 1 => 3 ),
+ array( 0 => 107, 1 => 1 ),
+ array( 0 => 107, 1 => 1 ),
+ );
+
+ public static $yyReduceMap = array(
+ 0 => 0,
+ 1 => 1,
+ 2 => 2,
+ 3 => 3,
+ 4 => 4,
+ 5 => 5,
+ 6 => 6,
+ 21 => 6,
+ 22 => 6,
+ 23 => 6,
+ 35 => 6,
+ 55 => 6,
+ 56 => 6,
+ 64 => 6,
+ 65 => 6,
+ 66 => 6,
+ 82 => 6,
+ 87 => 6,
+ 88 => 6,
+ 93 => 6,
+ 97 => 6,
+ 98 => 6,
+ 102 => 6,
+ 103 => 6,
+ 105 => 6,
+ 110 => 6,
+ 174 => 6,
+ 179 => 6,
+ 7 => 7,
+ 8 => 8,
+ 9 => 9,
+ 11 => 11,
+ 12 => 12,
+ 13 => 13,
+ 14 => 14,
+ 15 => 15,
+ 16 => 16,
+ 17 => 17,
+ 18 => 18,
+ 19 => 19,
+ 20 => 20,
+ 24 => 24,
+ 25 => 25,
+ 26 => 26,
+ 27 => 27,
+ 28 => 28,
+ 29 => 29,
+ 30 => 30,
+ 32 => 30,
+ 31 => 31,
+ 33 => 33,
+ 34 => 34,
+ 36 => 36,
+ 37 => 37,
+ 38 => 38,
+ 39 => 39,
+ 40 => 40,
+ 41 => 41,
+ 42 => 42,
+ 43 => 43,
+ 44 => 44,
+ 45 => 45,
+ 46 => 46,
+ 47 => 47,
+ 48 => 48,
+ 49 => 49,
+ 58 => 49,
+ 152 => 49,
+ 156 => 49,
+ 160 => 49,
+ 162 => 49,
+ 50 => 50,
+ 153 => 50,
+ 159 => 50,
+ 51 => 51,
+ 52 => 52,
+ 53 => 52,
+ 54 => 54,
+ 137 => 54,
+ 57 => 57,
+ 59 => 59,
+ 60 => 60,
+ 61 => 60,
+ 62 => 62,
+ 63 => 63,
+ 67 => 67,
+ 68 => 68,
+ 69 => 69,
+ 70 => 70,
+ 71 => 70,
+ 72 => 72,
+ 73 => 73,
+ 74 => 74,
+ 75 => 75,
+ 76 => 76,
+ 77 => 77,
+ 78 => 78,
+ 79 => 79,
+ 80 => 79,
+ 81 => 81,
+ 83 => 83,
+ 85 => 83,
+ 86 => 83,
+ 117 => 83,
+ 84 => 84,
+ 89 => 89,
+ 90 => 90,
+ 91 => 91,
+ 92 => 92,
+ 94 => 94,
+ 95 => 95,
+ 96 => 95,
+ 99 => 99,
+ 100 => 100,
+ 101 => 101,
+ 104 => 104,
+ 106 => 106,
+ 107 => 107,
+ 108 => 108,
+ 109 => 109,
+ 111 => 111,
+ 112 => 112,
+ 113 => 113,
+ 114 => 114,
+ 115 => 115,
+ 116 => 116,
+ 118 => 118,
+ 176 => 118,
+ 119 => 119,
+ 120 => 120,
+ 121 => 121,
+ 122 => 122,
+ 123 => 123,
+ 124 => 124,
+ 132 => 124,
+ 125 => 125,
+ 126 => 126,
+ 127 => 127,
+ 128 => 127,
+ 130 => 127,
+ 131 => 127,
+ 129 => 129,
+ 133 => 133,
+ 134 => 134,
+ 135 => 135,
+ 180 => 135,
+ 136 => 136,
+ 138 => 138,
+ 139 => 139,
+ 140 => 140,
+ 141 => 141,
+ 142 => 142,
+ 143 => 143,
+ 144 => 144,
+ 145 => 145,
+ 146 => 146,
+ 147 => 147,
+ 148 => 148,
+ 149 => 149,
+ 150 => 150,
+ 151 => 151,
+ 154 => 154,
+ 155 => 155,
+ 157 => 157,
+ 158 => 158,
+ 161 => 161,
+ 163 => 163,
+ 164 => 164,
+ 165 => 165,
+ 166 => 166,
+ 167 => 167,
+ 168 => 168,
+ 169 => 169,
+ 170 => 170,
+ 171 => 171,
+ 172 => 172,
+ 173 => 172,
+ 175 => 175,
+ 177 => 177,
+ 178 => 178,
+ 181 => 181,
+ 182 => 182,
+ 183 => 183,
+ 184 => 184,
+ 187 => 184,
+ 185 => 185,
+ 188 => 185,
+ 186 => 186,
+ 189 => 189,
+ 190 => 190,
+ );
+// line 245 "src/Parser/TemplateParser.y"
+ public function yy_r0(){
+ $this->root_buffer->prepend_array($this, $this->template_prefix);
+ $this->root_buffer->append_array($this, $this->template_postfix);
+ $this->_retvalue = $this->root_buffer->to_smarty_php($this);
+ }
+// line 252 "src/Parser/TemplateParser.y"
+ public function yy_r1(){
+ $text = $this->yystack[ $this->yyidx + 0 ]->minor;
+
+ if ((string)$text == '') {
+ $this->current_buffer->append_subtree($this, null);
+ }
+
+ $this->current_buffer->append_subtree($this, new \Smarty\ParseTree\Text($text, $this->strip));
+ }
+// line 262 "src/Parser/TemplateParser.y"
+ public function yy_r2(){
+ $this->strip = true;
+ }
+// line 266 "src/Parser/TemplateParser.y"
+ public function yy_r3(){
+ $this->strip = false;
+ }
+// line 271 "src/Parser/TemplateParser.y"
+ public function yy_r4(){
+ $this->current_buffer->append_subtree($this, new \Smarty\ParseTree\Text($this->yystack[$this->yyidx + -1]->minor));
+ }
+// line 276 "src/Parser/TemplateParser.y"
+ public function yy_r5(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor.$this->yystack[$this->yyidx + -1]->minor;
+ }
+// line 279 "src/Parser/TemplateParser.y"
+ public function yy_r6(){
+ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 283 "src/Parser/TemplateParser.y"
+ public function yy_r7(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
+
+ }
+// line 288 "src/Parser/TemplateParser.y"
+ public function yy_r8(){
+ $this->_retvalue = '';
+ }
+// line 292 "src/Parser/TemplateParser.y"
+ public function yy_r9(){
+ if ($this->compiler->has_code) {
+ $this->current_buffer->append_subtree($this, $this->mergePrefixCode($this->yystack[$this->yyidx + 0]->minor));
+ }
+ $this->compiler->has_variable_string = false;
+ $this->block_nesting_level = $this->compiler->getTagStackCount();
+ }
+// line 304 "src/Parser/TemplateParser.y"
+ public function yy_r11(){
+ $var = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $');
+ $attributes = [];
+ if (preg_match('/^(.*)(\s+nocache)$/', $var, $match)) {
+ $attributes[] = 'nocache';
+ $var = $match[1];
+ }
+ $this->_retvalue = $this->compiler->compilePrintExpression($this->compiler->compileVariable('\''.$var.'\''), $attributes);
+ }
+// line 315 "src/Parser/TemplateParser.y"
+ public function yy_r12(){
+ $tag = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()));
+ if ($tag == 'strip') {
+ $this->strip = true;
+ $this->_retvalue = null;
+ } else {
+ if (defined($tag)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant($tag, $this->compiler);
+ }
+ $this->_retvalue = $this->compiler->compilePrintExpression($tag);
+ } else {
+ if (preg_match('/^(.*)(\s+nocache)$/', $tag, $match)) {
+ $this->_retvalue = $this->compiler->compileTag($match[1],array('\'nocache\''));
+ } else {
+ $this->_retvalue = $this->compiler->compileTag($tag,array());
+ }
+ }
+ }
+ }
+// line 336 "src/Parser/TemplateParser.y"
+ public function yy_r13(){
+ $j = strrpos($this->yystack[$this->yyidx + 0]->minor,'.');
+ if ($this->yystack[$this->yyidx + 0]->minor[$j+1] == 'c') {
+ // {$smarty.block.child}
+ $this->_retvalue = $this->compiler->compileChildBlock();
+ } else {
+ // {$smarty.block.parent}
+ $this->_retvalue = $this->compiler->compileParentBlock();
+ }
+ }
+// line 347 "src/Parser/TemplateParser.y"
+ public function yy_r14(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;
+ }
+// line 351 "src/Parser/TemplateParser.y"
+ public function yy_r15(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;
+ }
+// line 355 "src/Parser/TemplateParser.y"
+ public function yy_r16(){
+ $this->_retvalue = $this->compiler->compilePrintExpression($this->yystack[$this->yyidx + 0]->minor[0], $this->yystack[$this->yyidx + 0]->minor[1]);
+ }
+// line 364 "src/Parser/TemplateParser.y"
+ public function yy_r17(){
+ $this->_retvalue = $this->compiler->compileTag('assign',array_merge(array(array('value'=>$this->yystack[$this->yyidx + 0]->minor[0]),array('var'=>'\''.substr($this->yystack[$this->yyidx + -1]->minor,1).'\'')),$this->yystack[$this->yyidx + 0]->minor[1]));
+ }
+// line 368 "src/Parser/TemplateParser.y"
+ public function yy_r18(){
+ $this->_retvalue = $this->compiler->compileTag('assign',array_merge(array(array('value'=>$this->yystack[$this->yyidx + 0]->minor[0]),array('var'=>$this->yystack[$this->yyidx + -1]->minor['var'])),$this->yystack[$this->yyidx + 0]->minor[1]),array('smarty_internal_index'=>$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index']));
+ }
+// line 372 "src/Parser/TemplateParser.y"
+ public function yy_r19(){
+ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 376 "src/Parser/TemplateParser.y"
+ public function yy_r20(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 391 "src/Parser/TemplateParser.y"
+ public function yy_r24(){
+ if (defined($this->yystack[$this->yyidx + -1]->minor)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant($this->yystack[$this->yyidx + -1]->minor, $this->compiler);
+ }
+ $this->_retvalue = $this->compiler->compilePrintExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor);
+ } else {
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor);
+ }
+ }
+// line 401 "src/Parser/TemplateParser.y"
+ public function yy_r25(){
+ if (defined($this->yystack[$this->yyidx + 0]->minor)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant($this->yystack[$this->yyidx + 0]->minor, $this->compiler);
+ }
+ $this->_retvalue = $this->compiler->compilePrintExpression($this->yystack[$this->yyidx + 0]->minor);
+ } else {
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + 0]->minor,array());
+ }
+ }
+// line 414 "src/Parser/TemplateParser.y"
+ public function yy_r26(){
+ if (defined($this->yystack[$this->yyidx + -2]->minor)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant($this->yystack[$this->yyidx + -2]->minor, $this->compiler);
+ }
+ $this->_retvalue = $this->compiler->compilePrintExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
+ } else {
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -2]->minor,$this->yystack[$this->yyidx + 0]->minor, array('modifierlist'=>$this->yystack[$this->yyidx + -1]->minor));
+ }
+ }
+// line 426 "src/Parser/TemplateParser.y"
+ public function yy_r27(){
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -3]->minor,$this->yystack[$this->yyidx + 0]->minor,array('object_method'=>$this->yystack[$this->yyidx + -1]->minor));
+ }
+// line 431 "src/Parser/TemplateParser.y"
+ public function yy_r28(){
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -4]->minor,$this->yystack[$this->yyidx + 0]->minor,array('modifierlist'=>$this->yystack[$this->yyidx + -1]->minor, 'object_method'=>$this->yystack[$this->yyidx + -2]->minor));
+ }
+// line 436 "src/Parser/TemplateParser.y"
+ public function yy_r29(){
+ $tag = trim(substr($this->yystack[$this->yyidx + -1]->minor,$this->compiler->getLdelLength()));
+ $this->_retvalue = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,array(),array('if condition'=>$this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 441 "src/Parser/TemplateParser.y"
+ public function yy_r30(){
+ $tag = trim(substr($this->yystack[$this->yyidx + -2]->minor,$this->compiler->getLdelLength()));
+ $this->_retvalue = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,$this->yystack[$this->yyidx + 0]->minor,array('if condition'=>$this->yystack[$this->yyidx + -1]->minor));
+ }
+// line 446 "src/Parser/TemplateParser.y"
+ public function yy_r31(){
+ $tag = trim(substr($this->yystack[$this->yyidx + -1]->minor,$this->compiler->getLdelLength()));
+ $this->_retvalue = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,array(),array('if condition'=>$this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 457 "src/Parser/TemplateParser.y"
+ public function yy_r33(){
+ $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('start'=>$this->yystack[$this->yyidx + -6]->minor),array('ifexp'=>$this->yystack[$this->yyidx + -4]->minor),array('var'=>$this->yystack[$this->yyidx + -2]->minor),array('step'=>$this->yystack[$this->yyidx + -1]->minor))),1);
+ }
+// line 461 "src/Parser/TemplateParser.y"
+ public function yy_r34(){
+ $this->_retvalue = '='.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 469 "src/Parser/TemplateParser.y"
+ public function yy_r36(){
+ $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('start'=>$this->yystack[$this->yyidx + -3]->minor),array('to'=>$this->yystack[$this->yyidx + -1]->minor))),0);
+ }
+// line 473 "src/Parser/TemplateParser.y"
+ public function yy_r37(){
+ $this->_retvalue = $this->compiler->compileTag('for',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('start'=>$this->yystack[$this->yyidx + -5]->minor),array('to'=>$this->yystack[$this->yyidx + -3]->minor),array('step'=>$this->yystack[$this->yyidx + -1]->minor))),0);
+ }
+// line 478 "src/Parser/TemplateParser.y"
+ public function yy_r38(){
+ $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('from'=>$this->yystack[$this->yyidx + -3]->minor),array('item'=>$this->yystack[$this->yyidx + -1]->minor))));
+ }
+// line 482 "src/Parser/TemplateParser.y"
+ public function yy_r39(){
+ $this->_retvalue = $this->compiler->compileTag('foreach',array_merge($this->yystack[$this->yyidx + 0]->minor,array(array('from'=>$this->yystack[$this->yyidx + -5]->minor),array('item'=>$this->yystack[$this->yyidx + -1]->minor),array('key'=>$this->yystack[$this->yyidx + -3]->minor))));
+ }
+// line 485 "src/Parser/TemplateParser.y"
+ public function yy_r40(){
+ $this->_retvalue = $this->compiler->compileTag('foreach',$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 490 "src/Parser/TemplateParser.y"
+ public function yy_r41(){
+ $this->_retvalue = $this->compiler->compileTag('setfilter',array(),array('modifier_list'=>array(array_merge(array($this->yystack[$this->yyidx + -1]->minor),$this->yystack[$this->yyidx + 0]->minor))));
+ }
+// line 494 "src/Parser/TemplateParser.y"
+ public function yy_r42(){
+ $this->_retvalue = $this->compiler->compileTag('setfilter',array(),array('modifier_list'=>array_merge(array(array_merge(array($this->yystack[$this->yyidx + -2]->minor),$this->yystack[$this->yyidx + -1]->minor)),$this->yystack[$this->yyidx + 0]->minor)));
+ }
+// line 500 "src/Parser/TemplateParser.y"
+ public function yy_r43(){
+ $tag = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' /');
+ if ($tag === 'strip') {
+ $this->strip = false;
+ $this->_retvalue = null;
+ } else {
+ $this->_retvalue = $this->compiler->compileTag($tag.'close',array());
+ }
+ }
+// line 509 "src/Parser/TemplateParser.y"
+ public function yy_r44(){
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + 0]->minor.'close',array());
+ }
+// line 513 "src/Parser/TemplateParser.y"
+ public function yy_r45(){
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -1]->minor.'close',array(),array('modifier_list'=>$this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 518 "src/Parser/TemplateParser.y"
+ public function yy_r46(){
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -2]->minor.'close',array(),array('object_method'=>$this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 522 "src/Parser/TemplateParser.y"
+ public function yy_r47(){
+ $this->_retvalue = $this->compiler->compileTag($this->yystack[$this->yyidx + -3]->minor.'close',array(),array('object_method'=>$this->yystack[$this->yyidx + -1]->minor, 'modifier_list'=>$this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 530 "src/Parser/TemplateParser.y"
+ public function yy_r48(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;
+ $this->_retvalue[] = $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 536 "src/Parser/TemplateParser.y"
+ public function yy_r49(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 541 "src/Parser/TemplateParser.y"
+ public function yy_r50(){
+ $this->_retvalue = array();
+ }
+// line 546 "src/Parser/TemplateParser.y"
+ public function yy_r51(){
+ if (defined($this->yystack[$this->yyidx + 0]->minor)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant($this->yystack[$this->yyidx + 0]->minor, $this->compiler);
+ }
+ $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>$this->yystack[$this->yyidx + 0]->minor);
+ } else {
+ $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>'\''.$this->yystack[$this->yyidx + 0]->minor.'\'');
+ }
+ }
+// line 557 "src/Parser/TemplateParser.y"
+ public function yy_r52(){
+ $this->_retvalue = array(trim($this->yystack[$this->yyidx + -1]->minor," =\n\r\t")=>$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 565 "src/Parser/TemplateParser.y"
+ public function yy_r54(){
+ $this->_retvalue = '\''.$this->yystack[$this->yyidx + 0]->minor.'\'';
+ }
+// line 577 "src/Parser/TemplateParser.y"
+ public function yy_r57(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor=>$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 590 "src/Parser/TemplateParser.y"
+ public function yy_r59(){
+ $this->yystack[$this->yyidx + -2]->minor[]=$this->yystack[$this->yyidx + 0]->minor;
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor;
+ }
+// line 595 "src/Parser/TemplateParser.y"
+ public function yy_r60(){
+ $this->_retvalue = array('var' => '\''.substr($this->yystack[$this->yyidx + -2]->minor,1).'\'', 'value'=>$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 602 "src/Parser/TemplateParser.y"
+ public function yy_r62(){
+ $this->_retvalue = array('var' => $this->yystack[$this->yyidx + -2]->minor, 'value'=>$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 606 "src/Parser/TemplateParser.y"
+ public function yy_r63(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;
+ }
+// line 631 "src/Parser/TemplateParser.y"
+ public function yy_r67(){
+ $this->_retvalue = '$_smarty_tpl->getVariable(\''. substr($this->yystack[$this->yyidx + 0]->minor,1) .'\')->preIncDec(\'' . $this->yystack[$this->yyidx + -1]->minor . '\')';
+ }
+// line 636 "src/Parser/TemplateParser.y"
+ public function yy_r68(){
+ $this->_retvalue = '$_smarty_tpl->getVariable(\''. substr($this->yystack[$this->yyidx + -1]->minor,1) .'\')->postIncDec(\'' . $this->yystack[$this->yyidx + 0]->minor . '\')';
+ }
+// line 641 "src/Parser/TemplateParser.y"
+ public function yy_r69(){
+ $this->_retvalue = '$_smarty_tpl->getStreamVariable(\''.substr($this->yystack[$this->yyidx + -2]->minor,1).'://' . $this->yystack[$this->yyidx + 0]->minor . '\')';
+ }
+// line 646 "src/Parser/TemplateParser.y"
+ public function yy_r70(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor . trim($this->yystack[$this->yyidx + -1]->minor) . $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 656 "src/Parser/TemplateParser.y"
+ public function yy_r72(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor['pre']. $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor['op'].$this->yystack[$this->yyidx + 0]->minor .')';
+ }
+// line 660 "src/Parser/TemplateParser.y"
+ public function yy_r73(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 664 "src/Parser/TemplateParser.y"
+ public function yy_r74(){
+ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor . $this->yystack[$this->yyidx + -1]->minor . ')';
+ }
+// line 668 "src/Parser/TemplateParser.y"
+ public function yy_r75(){
+ $this->_retvalue = 'in_array('.$this->yystack[$this->yyidx + -2]->minor.','.$this->yystack[$this->yyidx + 0]->minor.')';
+ }
+// line 672 "src/Parser/TemplateParser.y"
+ public function yy_r76(){
+ $this->_retvalue = 'in_array('.$this->yystack[$this->yyidx + -2]->minor.',(array)'.$this->yystack[$this->yyidx + 0]->minor.')';
+ }
+// line 677 "src/Parser/TemplateParser.y"
+ public function yy_r77(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor.' ?? '.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 684 "src/Parser/TemplateParser.y"
+ public function yy_r78(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -4]->minor.' ? '. $this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + -2]->minor,1).'\'') . ' : '.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 688 "src/Parser/TemplateParser.y"
+ public function yy_r79(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -4]->minor.' ? '.$this->yystack[$this->yyidx + -2]->minor.' : '.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 697 "src/Parser/TemplateParser.y"
+ public function yy_r81(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor.' ?: '.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 707 "src/Parser/TemplateParser.y"
+ public function yy_r83(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 712 "src/Parser/TemplateParser.y"
+ public function yy_r84(){
+ $this->_retvalue = '!'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 733 "src/Parser/TemplateParser.y"
+ public function yy_r89(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'.'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 737 "src/Parser/TemplateParser.y"
+ public function yy_r90(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.'.';
+ }
+// line 741 "src/Parser/TemplateParser.y"
+ public function yy_r91(){
+ $this->_retvalue = '.'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 746 "src/Parser/TemplateParser.y"
+ public function yy_r92(){
+ if (defined($this->yystack[$this->yyidx + 0]->minor)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant($this->yystack[$this->yyidx + 0]->minor, $this->compiler);
+ }
+ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
+ } else {
+ $this->_retvalue = '\''.$this->yystack[$this->yyidx + 0]->minor.'\'';
+ }
+ }
+// line 763 "src/Parser/TemplateParser.y"
+ public function yy_r94(){
+ $this->_retvalue = '('. $this->yystack[$this->yyidx + -1]->minor .')';
+ }
+// line 767 "src/Parser/TemplateParser.y"
+ public function yy_r95(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 785 "src/Parser/TemplateParser.y"
+ public function yy_r99(){
+ if ($this->security && $this->security->static_classes !== array()) {
+ $this->compiler->trigger_template_error('dynamic static class not allowed by security setting');
+ }
+ $prefixVar = $this->compiler->getNewPrefixVariable();
+ if ($this->yystack[$this->yyidx + -2]->minor['var'] === '\'smarty\'') {
+ $this->compiler->appendPrefixCode("<?php {$prefixVar} = ". (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index']).';?>');
+ } else {
+ $this->compiler->appendPrefixCode("<?php {$prefixVar} = ". $this->compiler->compileVariable($this->yystack[$this->yyidx + -2]->minor['var']).$this->yystack[$this->yyidx + -2]->minor['smarty_internal_index'].';?>');
+ }
+ $this->_retvalue = $prefixVar .'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1];
+ }
+// line 799 "src/Parser/TemplateParser.y"
+ public function yy_r100(){
+ $prefixVar = $this->compiler->getNewPrefixVariable();
+ $tmp = $this->compiler->appendCode('<?php ob_start();?>', $this->yystack[$this->yyidx + 0]->minor);
+ $this->compiler->appendPrefixCode($this->compiler->appendCode($tmp, "<?php {$prefixVar} = ob_get_clean();?>"));
+ $this->_retvalue = $prefixVar;
+ }
+// line 806 "src/Parser/TemplateParser.y"
+ public function yy_r101(){
+ $this->_retvalue = $this->compiler->compileModifier($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
+ }
+// line 819 "src/Parser/TemplateParser.y"
+ public function yy_r104(){
+ if (!in_array(strtolower($this->yystack[$this->yyidx + -2]->minor), array('self', 'parent')) && (!$this->security || $this->security->isTrustedStaticClassAccess($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->compiler))) {
+ if (isset($this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor])) {
+ $this->_retvalue = $this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor].'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1];
+ } else {
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1];
+ }
+ } else {
+ $this->compiler->trigger_template_error ('static class \''.$this->yystack[$this->yyidx + -2]->minor.'\' is undefined or not allowed by security setting');
+ }
+ }
+// line 838 "src/Parser/TemplateParser.y"
+ public function yy_r106(){
+ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 849 "src/Parser/TemplateParser.y"
+ public function yy_r107(){
+ $this->_retvalue = $this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + 0]->minor,1).'\'');
+ }
+// line 852 "src/Parser/TemplateParser.y"
+ public function yy_r108(){
+ if ($this->yystack[$this->yyidx + 0]->minor['var'] === '\'smarty\'') {
+ $smarty_var = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,$this->yystack[$this->yyidx + 0]->minor['smarty_internal_index']);
+ $this->_retvalue = $smarty_var;
+ } else {
+ // used for array reset,next,prev,end,current
+ $this->last_variable = $this->yystack[$this->yyidx + 0]->minor['var'];
+ $this->last_index = $this->yystack[$this->yyidx + 0]->minor['smarty_internal_index'];
+ $this->_retvalue = $this->compiler->compileVariable($this->yystack[$this->yyidx + 0]->minor['var']).$this->yystack[$this->yyidx + 0]->minor['smarty_internal_index'];
+ }
+ }
+// line 865 "src/Parser/TemplateParser.y"
+ public function yy_r109(){
+ $this->_retvalue = '$_smarty_tpl->getVariable('. $this->yystack[$this->yyidx + -2]->minor .')->'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 875 "src/Parser/TemplateParser.y"
+ public function yy_r111(){
+ $this->_retvalue = $this->compiler->compileConfigVariable('\'' . $this->yystack[$this->yyidx + -1]->minor . '\'');
+ }
+// line 879 "src/Parser/TemplateParser.y"
+ public function yy_r112(){
+ $this->_retvalue = '(is_array($tmp = ' . $this->compiler->compileConfigVariable('\'' . $this->yystack[$this->yyidx + -2]->minor . '\'') . ') ? $tmp'.$this->yystack[$this->yyidx + 0]->minor.' :null)';
+ }
+// line 883 "src/Parser/TemplateParser.y"
+ public function yy_r113(){
+ $this->_retvalue = $this->compiler->compileConfigVariable($this->yystack[$this->yyidx + -1]->minor);
+ }
+// line 887 "src/Parser/TemplateParser.y"
+ public function yy_r114(){
+ $this->_retvalue = '(is_array($tmp = ' . $this->compiler->compileConfigVariable($this->yystack[$this->yyidx + -2]->minor) . ') ? $tmp'.$this->yystack[$this->yyidx + 0]->minor.' : null)';
+ }
+// line 891 "src/Parser/TemplateParser.y"
+ public function yy_r115(){
+ $this->_retvalue = array('var'=>'\''.substr($this->yystack[$this->yyidx + -1]->minor,1).'\'', 'smarty_internal_index'=>$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 894 "src/Parser/TemplateParser.y"
+ public function yy_r116(){
+ $this->_retvalue = array('var'=>$this->yystack[$this->yyidx + -1]->minor, 'smarty_internal_index'=>$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 907 "src/Parser/TemplateParser.y"
+ public function yy_r118(){
+ return;
+ }
+// line 913 "src/Parser/TemplateParser.y"
+ public function yy_r119(){
+ $this->_retvalue = '['.$this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + 0]->minor,1).'\'').']';
+ }
+// line 916 "src/Parser/TemplateParser.y"
+ public function yy_r120(){
+ $this->_retvalue = '['.$this->compiler->compileVariable($this->yystack[$this->yyidx + 0]->minor).']';
+ }
+// line 920 "src/Parser/TemplateParser.y"
+ public function yy_r121(){
+ $this->_retvalue = '['.$this->compiler->compileVariable($this->yystack[$this->yyidx + -2]->minor).'->'.$this->yystack[$this->yyidx + 0]->minor.']';
+ }
+// line 924 "src/Parser/TemplateParser.y"
+ public function yy_r122(){
+ $this->_retvalue = '[\''. $this->yystack[$this->yyidx + 0]->minor .'\']';
+ }
+// line 928 "src/Parser/TemplateParser.y"
+ public function yy_r123(){
+ $this->_retvalue = '['. $this->yystack[$this->yyidx + 0]->minor .']';
+ }
+// line 933 "src/Parser/TemplateParser.y"
+ public function yy_r124(){
+ $this->_retvalue = '['. $this->yystack[$this->yyidx + -1]->minor .']';
+ }
+// line 938 "src/Parser/TemplateParser.y"
+ public function yy_r125(){
+ $this->_retvalue = '['.(new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,'[\'section\'][\''.$this->yystack[$this->yyidx + -1]->minor.'\'][\'index\']').']';
+ }
+// line 942 "src/Parser/TemplateParser.y"
+ public function yy_r126(){
+ $this->_retvalue = '['.(new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,'[\'section\'][\''.$this->yystack[$this->yyidx + -3]->minor.'\'][\''.$this->yystack[$this->yyidx + -1]->minor.'\']').']';
+ }
+// line 945 "src/Parser/TemplateParser.y"
+ public function yy_r127(){
+ $this->_retvalue = '['.$this->yystack[$this->yyidx + -1]->minor.']';
+ }
+// line 951 "src/Parser/TemplateParser.y"
+ public function yy_r129(){
+ $this->_retvalue = '['.$this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + -1]->minor,1).'\'').']';
+ }
+// line 967 "src/Parser/TemplateParser.y"
+ public function yy_r133(){
+ $this->_retvalue = '[]';
+ }
+// line 977 "src/Parser/TemplateParser.y"
+ public function yy_r134(){
+ $this->_retvalue = '\''.substr($this->yystack[$this->yyidx + 0]->minor,1).'\'';
+ }
+// line 981 "src/Parser/TemplateParser.y"
+ public function yy_r135(){
+ $this->_retvalue = '\'\'';
+ }
+// line 986 "src/Parser/TemplateParser.y"
+ public function yy_r136(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.'.'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 994 "src/Parser/TemplateParser.y"
+ public function yy_r138(){
+ $var = trim(substr($this->yystack[$this->yyidx + 0]->minor, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $');
+ $this->_retvalue = $this->compiler->compileVariable('\''.$var.'\'');
+ }
+// line 1000 "src/Parser/TemplateParser.y"
+ public function yy_r139(){
+ $this->_retvalue = '('.$this->yystack[$this->yyidx + -1]->minor.')';
+ }
+// line 1007 "src/Parser/TemplateParser.y"
+ public function yy_r140(){
+ if ($this->yystack[$this->yyidx + -1]->minor['var'] === '\'smarty\'') {
+ $this->_retvalue = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index']).$this->yystack[$this->yyidx + 0]->minor;
+ } else {
+ $this->_retvalue = $this->compiler->compileVariable($this->yystack[$this->yyidx + -1]->minor['var']).$this->yystack[$this->yyidx + -1]->minor['smarty_internal_index'].$this->yystack[$this->yyidx + 0]->minor;
+ }
+ }
+// line 1016 "src/Parser/TemplateParser.y"
+ public function yy_r141(){
+ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 1021 "src/Parser/TemplateParser.y"
+ public function yy_r142(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 1026 "src/Parser/TemplateParser.y"
+ public function yy_r143(){
+ if ($this->security && substr($this->yystack[$this->yyidx + -1]->minor,0,1) === '_') {
+ $this->compiler->trigger_template_error (self::ERR1);
+ }
+ $this->_retvalue = '->'.$this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 1033 "src/Parser/TemplateParser.y"
+ public function yy_r144(){
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ $this->_retvalue = '->{'.$this->compiler->compileVariable($this->yystack[$this->yyidx + -1]->minor).$this->yystack[$this->yyidx + 0]->minor.'}';
+ }
+// line 1040 "src/Parser/TemplateParser.y"
+ public function yy_r145(){
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ $this->_retvalue = '->{'.$this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + 0]->minor.'}';
+ }
+// line 1047 "src/Parser/TemplateParser.y"
+ public function yy_r146(){
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ $this->_retvalue = '->{\''.$this->yystack[$this->yyidx + -4]->minor.'\'.'.$this->yystack[$this->yyidx + -2]->minor.$this->yystack[$this->yyidx + 0]->minor.'}';
+ }
+// line 1055 "src/Parser/TemplateParser.y"
+ public function yy_r147(){
+ $this->_retvalue = '->'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 1063 "src/Parser/TemplateParser.y"
+ public function yy_r148(){
+ $this->_retvalue = $this->compiler->compileFunctionCall($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor);
+ }
+// line 1071 "src/Parser/TemplateParser.y"
+ public function yy_r149(){
+ if ($this->security && substr($this->yystack[$this->yyidx + -3]->minor,0,1) === '_') {
+ $this->compiler->trigger_template_error (self::ERR1);
+ }
+ $this->_retvalue = $this->yystack[$this->yyidx + -3]->minor . '('. implode(',',$this->yystack[$this->yyidx + -1]->minor) .')';
+ }
+// line 1078 "src/Parser/TemplateParser.y"
+ public function yy_r150(){
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ $prefixVar = $this->compiler->getNewPrefixVariable();
+ $this->compiler->appendPrefixCode("<?php {$prefixVar} = ".$this->compiler->compileVariable('\''.substr($this->yystack[$this->yyidx + -3]->minor,1).'\'').';?>');
+ $this->_retvalue = $prefixVar .'('. implode(',',$this->yystack[$this->yyidx + -1]->minor) .')';
+ }
+// line 1089 "src/Parser/TemplateParser.y"
+ public function yy_r151(){
+ $this->_retvalue = array_merge($this->yystack[$this->yyidx + -2]->minor,array($this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 1106 "src/Parser/TemplateParser.y"
+ public function yy_r154(){
+ $this->_retvalue = array_merge($this->yystack[$this->yyidx + -2]->minor,array(array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor)));
+ }
+// line 1110 "src/Parser/TemplateParser.y"
+ public function yy_r155(){
+ $this->_retvalue = array(array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor));
+ }
+// line 1118 "src/Parser/TemplateParser.y"
+ public function yy_r157(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 1126 "src/Parser/TemplateParser.y"
+ public function yy_r158(){
+ $this->_retvalue = array_merge($this->yystack[$this->yyidx + -1]->minor,$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 1139 "src/Parser/TemplateParser.y"
+ public function yy_r161(){
+ $this->_retvalue = array(trim($this->yystack[$this->yyidx + -1]->minor).$this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 1148 "src/Parser/TemplateParser.y"
+ public function yy_r163(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor, '', 'method');
+ }
+// line 1153 "src/Parser/TemplateParser.y"
+ public function yy_r164(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor, 'method');
+ }
+// line 1158 "src/Parser/TemplateParser.y"
+ public function yy_r165(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor, '');
+ }
+// line 1163 "src/Parser/TemplateParser.y"
+ public function yy_r166(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor, 'property');
+ }
+// line 1168 "src/Parser/TemplateParser.y"
+ public function yy_r167(){
+ $this->_retvalue = array($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor.$this->yystack[$this->yyidx + 0]->minor, 'property');
+ }
+// line 1174 "src/Parser/TemplateParser.y"
+ public function yy_r168(){
+ $this->_retvalue = ' '. trim($this->yystack[$this->yyidx + 0]->minor) . ' ';
+ }
+// line 1178 "src/Parser/TemplateParser.y"
+ public function yy_r169(){
+ static $lops = array(
+ 'eq' => ' == ',
+ 'ne' => ' != ',
+ 'neq' => ' != ',
+ 'gt' => ' > ',
+ 'ge' => ' >= ',
+ 'gte' => ' >= ',
+ 'lt' => ' < ',
+ 'le' => ' <= ',
+ 'lte' => ' <= ',
+ 'mod' => ' % ',
+ 'and' => ' && ',
+ 'or' => ' || ',
+ 'xor' => ' xor ',
+ );
+ $op = strtolower(preg_replace('/\s*/', '', $this->yystack[$this->yyidx + 0]->minor));
+ $this->_retvalue = $lops[$op];
+ }
+// line 1197 "src/Parser/TemplateParser.y"
+ public function yy_r170(){
+ static $tlops = array(
+ 'isdivby' => array('op' => ' % ', 'pre' => '!('),
+ 'isnotdivby' => array('op' => ' % ', 'pre' => '('),
+ 'isevenby' => array('op' => ' / ', 'pre' => '!(1 & '),
+ 'isnotevenby' => array('op' => ' / ', 'pre' => '(1 & '),
+ 'isoddby' => array('op' => ' / ', 'pre' => '(1 & '),
+ 'isnotoddby' => array('op' => ' / ', 'pre' => '!(1 & '),
+ );
+ $op = strtolower(preg_replace('/\s*/', '', $this->yystack[$this->yyidx + 0]->minor));
+ $this->_retvalue = $tlops[$op];
+ }
+// line 1210 "src/Parser/TemplateParser.y"
+ public function yy_r171(){
+ static $scond = array (
+ 'iseven' => '!(1 & ',
+ 'isnoteven' => '(1 & ',
+ 'isodd' => '(1 & ',
+ 'isnotodd' => '!(1 & ',
+ );
+ $op = strtolower(str_replace(' ', '', $this->yystack[$this->yyidx + 0]->minor));
+ $this->_retvalue = $scond[$op];
+ }
+// line 1224 "src/Parser/TemplateParser.y"
+ public function yy_r172(){
+ $this->_retvalue = 'array('.$this->yystack[$this->yyidx + -1]->minor.')';
+ }
+// line 1235 "src/Parser/TemplateParser.y"
+ public function yy_r175(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.','.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 1243 "src/Parser/TemplateParser.y"
+ public function yy_r177(){
+ $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'=>'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 1247 "src/Parser/TemplateParser.y"
+ public function yy_r178(){
+ $this->_retvalue = '\''.$this->yystack[$this->yyidx + -2]->minor.'\'=>'.$this->yystack[$this->yyidx + 0]->minor;
+ }
+// line 1263 "src/Parser/TemplateParser.y"
+ public function yy_r181(){
+ $this->compiler->leaveDoubleQuote();
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor->to_smarty_php($this);
+ }
+// line 1269 "src/Parser/TemplateParser.y"
+ public function yy_r182(){
+ $this->yystack[$this->yyidx + -1]->minor->append_subtree($this, $this->yystack[$this->yyidx + 0]->minor);
+ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;
+ }
+// line 1274 "src/Parser/TemplateParser.y"
+ public function yy_r183(){
+ $this->_retvalue = new Dq($this, $this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 1278 "src/Parser/TemplateParser.y"
+ public function yy_r184(){
+ $this->_retvalue = new Code('(string)'.$this->yystack[$this->yyidx + -1]->minor);
+ }
+// line 1282 "src/Parser/TemplateParser.y"
+ public function yy_r185(){
+ $this->_retvalue = new Code('(string)('.$this->yystack[$this->yyidx + -1]->minor.')');
+ }
+// line 1286 "src/Parser/TemplateParser.y"
+ public function yy_r186(){
+ $this->_retvalue = new Code('(string)$_smarty_tpl->getValue(\''. substr($this->yystack[$this->yyidx + 0]->minor,1) .'\')');
+ }
+// line 1298 "src/Parser/TemplateParser.y"
+ public function yy_r189(){
+ $this->_retvalue = new Tag($this, $this->yystack[$this->yyidx + 0]->minor);
+ }
+// line 1302 "src/Parser/TemplateParser.y"
+ public function yy_r190(){
+ $this->_retvalue = new DqContent($this->yystack[$this->yyidx + 0]->minor);
+ }
+
+ private $_retvalue;
+
+ public function yy_reduce($yyruleno)
+ {
+ if ($this->yyTraceFILE && $yyruleno >= 0
+ && $yyruleno < count(self::$yyRuleName)) {
+ fprintf($this->yyTraceFILE, "%sReduce (%d) [%s].\n",
+ $this->yyTracePrompt, $yyruleno,
+ self::$yyRuleName[$yyruleno]);
+ }
+
+ $this->_retvalue = $yy_lefthand_side = null;
+ if (isset(self::$yyReduceMap[$yyruleno])) {
+ // call the action
+ $this->_retvalue = null;
+ $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
+ $yy_lefthand_side = $this->_retvalue;
+ }
+ $yygoto = self::$yyRuleInfo[$yyruleno][0];
+ $yysize = self::$yyRuleInfo[$yyruleno][1];
+ $this->yyidx -= $yysize;
+ for ($i = $yysize; $i; $i--) {
+ // pop all of the right-hand side parameters
+ array_pop($this->yystack);
+ }
+ $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
+ if ($yyact < self::YYNSTATE) {
+ if (!$this->yyTraceFILE && $yysize) {
+ $this->yyidx++;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = $yyact;
+ $x->major = $yygoto;
+ $x->minor = $yy_lefthand_side;
+ $this->yystack[$this->yyidx] = $x;
+ } else {
+ $this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
+ }
+ } elseif ($yyact === self::YYNSTATE + self::YYNRULE + 1) {
+ $this->yy_accept();
+ }
+ }
+
+ public function yy_parse_failed()
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sFail!\n", $this->yyTracePrompt);
+ } while ($this->yyidx >= 0) {
+ $this->yy_pop_parser_stack();
+ }
+ }
+
+ public function yy_syntax_error($yymajor, $TOKEN)
+ {
+// line 225 "src/Parser/TemplateParser.y"
+
+ $this->internalError = true;
+ $this->yymajor = $yymajor;
+ $this->compiler->trigger_template_error();
+ }
+
+ public function yy_accept()
+ {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sAccept!\n", $this->yyTracePrompt);
+ } while ($this->yyidx >= 0) {
+ $this->yy_pop_parser_stack();
+ }
+// line 218 "src/Parser/TemplateParser.y"
+
+ $this->successful = !$this->internalError;
+ $this->internalError = false;
+ $this->retvalue = $this->_retvalue;
+ }
+
+ public function doParse($yymajor, $yytokenvalue)
+ {
+ $yyerrorhit = 0; /* True if yymajor has invoked an error */
+
+ if ($this->yyidx === null || $this->yyidx < 0) {
+ $this->yyidx = 0;
+ $this->yyerrcnt = -1;
+ $x = (object) ['stateno' => null, 'major' => null, 'minor' => null];
+ $x->stateno = 0;
+ $x->major = 0;
+ $this->yystack = array();
+ $this->yystack[] = $x;
+ }
+ $yyendofinput = ($yymajor==0);
+
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sInput %s\n",
+ $this->yyTracePrompt, $this->yyTokenName[$yymajor]);
+ }
+
+ do {
+ $yyact = $this->yy_find_shift_action($yymajor);
+ if ($yymajor < self::YYERRORSYMBOL &&
+ !$this->yy_is_expected_token($yymajor)) {
+ // force a syntax error
+ $yyact = self::YY_ERROR_ACTION;
+ }
+ if ($yyact < self::YYNSTATE) {
+ $this->yy_shift($yyact, $yymajor, $yytokenvalue);
+ $this->yyerrcnt--;
+ if ($yyendofinput && $this->yyidx >= 0) {
+ $yymajor = 0;
+ } else {
+ $yymajor = self::YYNOCODE;
+ }
+ } elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
+ $this->yy_reduce($yyact - self::YYNSTATE);
+ } elseif ($yyact === self::YY_ERROR_ACTION) {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sSyntax Error!\n",
+ $this->yyTracePrompt);
+ }
+ if (self::YYERRORSYMBOL) {
+ if ($this->yyerrcnt < 0) {
+ $this->yy_syntax_error($yymajor, $yytokenvalue);
+ }
+ $yymx = $this->yystack[$this->yyidx]->major;
+ if ($yymx === self::YYERRORSYMBOL || $yyerrorhit) {
+ if ($this->yyTraceFILE) {
+ fprintf($this->yyTraceFILE, "%sDiscard input token %s\n",
+ $this->yyTracePrompt, $this->yyTokenName[$yymajor]);
+ }
+ $this->yy_destructor($yymajor, $yytokenvalue);
+ $yymajor = self::YYNOCODE;
+ } else {
+ while ($this->yyidx >= 0 &&
+ $yymx !== self::YYERRORSYMBOL &&
+ ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
+ ){
+ $this->yy_pop_parser_stack();
+ }
+ if ($this->yyidx < 0 || $yymajor==0) {
+ $this->yy_destructor($yymajor, $yytokenvalue);
+ $this->yy_parse_failed();
+ $yymajor = self::YYNOCODE;
+ } elseif ($yymx !== self::YYERRORSYMBOL) {
+ $u2 = 0;
+ $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
+ }
+ }
+ $this->yyerrcnt = 3;
+ $yyerrorhit = 1;
+ } else {
+ if ($this->yyerrcnt <= 0) {
+ $this->yy_syntax_error($yymajor, $yytokenvalue);
+ }
+ $this->yyerrcnt = 3;
+ $this->yy_destructor($yymajor, $yytokenvalue);
+ if ($yyendofinput) {
+ $this->yy_parse_failed();
+ }
+ $yymajor = self::YYNOCODE;
+ }
+ } else {
+ $this->yy_accept();
+ $yymajor = self::YYNOCODE;
+ }
+ } while ($yymajor !== self::YYNOCODE && $this->yyidx >= 0);
+ }
+}
+
diff --git a/src/Parser/TemplateParser.y b/src/Parser/TemplateParser.y
new file mode 100644
index 00000000..b081f4b1
--- /dev/null
+++ b/src/Parser/TemplateParser.y
@@ -0,0 +1,1305 @@
+/*
+ * This file is part of Smarty.
+ *
+ * (c) 2015 Uwe Tews
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+%stack_size 500
+%name TP_
+%declare_class {
+
+namespace Smarty\Parser;
+
+use \Smarty\Lexer\TemplateLexer as Lexer;
+use \Smarty\ParseTree\Template as TemplateParseTree;
+use \Smarty\Compiler\Template as TemplateCompiler;
+use \Smarty\ParseTree\Code;
+use \Smarty\ParseTree\Dq;
+use \Smarty\ParseTree\DqContent;
+use \Smarty\ParseTree\Tag;
+
+
+/**
+* Smarty Template Parser Class
+*
+* This is the template parser.
+* It is generated from the TemplateParser.y file
+*
+* @author Uwe Tews <uwe.tews@googlemail.com>
+*/
+class TemplateParser
+}
+%include_class
+{
+ const ERR1 = 'Security error: Call to private object member not allowed';
+ const ERR2 = 'Security error: Call to dynamic object member not allowed';
+
+ /**
+ * result status
+ *
+ * @var bool
+ */
+ public $successful = true;
+
+ /**
+ * return value
+ *
+ * @var mixed
+ */
+ public $retvalue = 0;
+
+ /**
+ * @var
+ */
+ public $yymajor;
+
+ /**
+ * last index of array variable
+ *
+ * @var mixed
+ */
+ public $last_index;
+
+ /**
+ * last variable name
+ *
+ * @var string
+ */
+ public $last_variable;
+
+ /**
+ * root parse tree buffer
+ *
+ * @var TemplateParseTree
+ */
+ public $root_buffer;
+
+ /**
+ * current parse tree object
+ *
+ * @var \Smarty\ParseTree\Base
+ */
+ public $current_buffer;
+
+ /**
+ * lexer object
+ *
+ * @var Lexer
+ */
+ public $lex;
+
+ /**
+ * internal error flag
+ *
+ * @var bool
+ */
+ private $internalError = false;
+
+ /**
+ * {strip} status
+ *
+ * @var bool
+ */
+ public $strip = false;
+ /**
+ * compiler object
+ *
+ * @var TemplateCompiler
+ */
+ public $compiler = null;
+
+ /**
+ * smarty object
+ *
+ * @var \Smarty\Smarty
+ */
+ public $smarty = null;
+
+ /**
+ * template object
+ *
+ * @var \Smarty\Template
+ */
+ public $template = null;
+
+ /**
+ * block nesting level
+ *
+ * @var int
+ */
+ public $block_nesting_level = 0;
+
+ /**
+ * security object
+ *
+ * @var \Smarty\Security
+ */
+ public $security = null;
+
+ /**
+ * template prefix array
+ *
+ * @var \Smarty\ParseTree\Base[]
+ */
+ public $template_prefix = array();
+
+ /**
+ * template prefix array
+ *
+ * @var \Smarty\ParseTree\Base[]
+ */
+ public $template_postfix = array();
+
+ /**
+ * constructor
+ *
+ * @param Lexer $lex
+ * @param TemplateCompiler $compiler
+ */
+ public function __construct(Lexer $lex, TemplateCompiler $compiler)
+ {
+ $this->lex = $lex;
+ $this->compiler = $compiler;
+ $this->template = $this->compiler->getTemplate();
+ $this->smarty = $this->template->getSmarty();
+ $this->security = $this->smarty->security_policy ?? false;
+ $this->current_buffer = $this->root_buffer = new TemplateParseTree();
+ }
+
+ /**
+ * insert PHP code in current buffer
+ *
+ * @param string $code
+ */
+ public function insertPhpCode($code)
+ {
+ $this->current_buffer->append_subtree($this, new Tag($this, $code));
+ }
+
+ /**
+ * error rundown
+ *
+ */
+ public function errorRunDown()
+ {
+ while ($this->yystack !== array()) {
+ $this->yy_pop_parser_stack();
+ }
+ if (is_resource($this->yyTraceFILE)) {
+ fclose($this->yyTraceFILE);
+ }
+ }
+
+ /**
+ * merge PHP code with prefix code and return parse tree tag object
+ *
+ * @param string $code
+ *
+ * @return Tag
+ */
+ private function mergePrefixCode($code)
+ {
+ $tmp = '';
+ foreach ($this->compiler->prefix_code as $preCode) {
+ $tmp .= $preCode;
+ }
+ $this->compiler->prefix_code = array();
+ $tmp .= $code;
+ return new Tag($this, $this->compiler->processNocacheCode($tmp));
+ }
+
+}
+
+%token_prefix TP_
+
+%parse_accept
+{
+ $this->successful = !$this->internalError;
+ $this->internalError = false;
+ $this->retvalue = $this->_retvalue;
+}
+
+%syntax_error
+{
+ $this->internalError = true;
+ $this->yymajor = $yymajor;
+ $this->compiler->trigger_template_error();
+}
+
+%stack_overflow
+{
+ $this->internalError = true;
+ $this->compiler->trigger_template_error('Stack overflow in template parser');
+}
+
+
+%right VERT.
+%left COLON.
+
+
+ //
+ // complete template
+ //
+start(res) ::= template. {
+ $this->root_buffer->prepend_array($this, $this->template_prefix);
+ $this->root_buffer->append_array($this, $this->template_postfix);
+ res = $this->root_buffer->to_smarty_php($this);
+}
+
+ // template text
+template ::= template TEXT(B). {
+ $text = $this->yystack[ $this->yyidx + 0 ]->minor;
+
+ if ((string)$text == '') {
+ $this->current_buffer->append_subtree($this, null);
+ }
+
+ $this->current_buffer->append_subtree($this, new \Smarty\ParseTree\Text($text, $this->strip));
+}
+ // strip on
+template ::= template STRIPON. {
+ $this->strip = true;
+}
+ // strip off
+template ::= template STRIPOFF. {
+ $this->strip = false;
+}
+
+ // Literal
+template ::= template LITERALSTART literal_e2(B) LITERALEND. {
+ $this->current_buffer->append_subtree($this, new \Smarty\ParseTree\Text(B));
+}
+
+
+literal_e2(A) ::= literal_e1(B) LITERALSTART literal_e1(C) LITERALEND. {
+ A = B.C;
+}
+literal_e2(A) ::= literal_e1(B). {
+ A = B;
+}
+
+literal_e1(A) ::= literal_e1(B) LITERAL(C). {
+ A = B.C;
+
+}
+
+literal_e1(A) ::= . {
+ A = '';
+}
+ // Smarty tag
+template ::= template smartytag(B). {
+ if ($this->compiler->has_code) {
+ $this->current_buffer->append_subtree($this, $this->mergePrefixCode(B));
+ }
+ $this->compiler->has_variable_string = false;
+ $this->block_nesting_level = $this->compiler->getTagStackCount();
+}
+
+
+ // empty template
+template ::= .
+
+smartytag(A) ::= SIMPELOUTPUT(B). {
+ $var = trim(substr(B, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $');
+ $attributes = [];
+ if (preg_match('/^(.*)(\s+nocache)$/', $var, $match)) {
+ $attributes[] = 'nocache';
+ $var = $match[1];
+ }
+ A = $this->compiler->compilePrintExpression($this->compiler->compileVariable('\''.$var.'\''), $attributes);
+}
+
+// simple tag like {name}
+smartytag(A)::= SIMPLETAG(B). {
+ $tag = trim(substr(B, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()));
+ if ($tag == 'strip') {
+ $this->strip = true;
+ A = null;
+ } else {
+ if (defined($tag)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant($tag, $this->compiler);
+ }
+ A = $this->compiler->compilePrintExpression($tag);
+ } else {
+ if (preg_match('/^(.*)(\s+nocache)$/', $tag, $match)) {
+ A = $this->compiler->compileTag($match[1],array('\'nocache\''));
+ } else {
+ A = $this->compiler->compileTag($tag,array());
+ }
+ }
+ }
+}
+ // {$smarty.block.child} or {$smarty.block.parent}
+smartytag(A) ::= SMARTYBLOCKCHILDPARENT(i). {
+ $j = strrpos(i,'.');
+ if (i[$j+1] == 'c') {
+ // {$smarty.block.child}
+ A = $this->compiler->compileChildBlock();
+ } else {
+ // {$smarty.block.parent}
+ A = $this->compiler->compileParentBlock();
+ }
+}
+
+smartytag(A) ::= LDEL tagbody(B) RDEL. {
+ A = B;
+}
+
+ smartytag(A) ::= tag(B) RDEL. {
+ A = B;
+ }
+ // output with optional attributes
+tagbody(A) ::= outattr(B). {
+ A = $this->compiler->compilePrintExpression(B[0], B[1]);
+}
+
+//
+// Smarty tags start here
+//
+
+ // assign new style
+tagbody(A) ::= DOLLARID(B) eqoutattr(C). {
+ A = $this->compiler->compileTag('assign',array_merge(array(array('value'=>C[0]),array('var'=>'\''.substr(B,1).'\'')),C[1]));
+}
+
+tagbody(A) ::= varindexed(B) eqoutattr(C). {
+ A = $this->compiler->compileTag('assign',array_merge(array(array('value'=>C[0]),array('var'=>B['var'])),C[1]),array('smarty_internal_index'=>B['smarty_internal_index']));
+}
+
+eqoutattr(A) ::= EQUAL outattr(B). {
+ A = B;
+}
+
+outattr(A) ::= output(B) attributes(C). {
+ A = array(B,C);
+}
+
+output(A) ::= variable(B). {
+ A = B;
+}
+output(A) ::= value(B). {
+ A = B;
+}
+output(A) ::= expr(B). {
+ A = B;
+}
+
+ // tag with optional Smarty2 style attributes
+tag(res) ::= LDEL ID(i) attributes(a). {
+ if (defined(i)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant(i, $this->compiler);
+ }
+ res = $this->compiler->compilePrintExpression(i, a);
+ } else {
+ res = $this->compiler->compileTag(i,a);
+ }
+}
+tag(res) ::= LDEL ID(i). {
+ if (defined(i)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant(i, $this->compiler);
+ }
+ res = $this->compiler->compilePrintExpression(i);
+ } else {
+ res = $this->compiler->compileTag(i,array());
+ }
+}
+
+
+ // tag with modifier and optional Smarty2 style attributes
+tag(res) ::= LDEL ID(i) modifierlist(l)attributes(a). {
+ if (defined(i)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant(i, $this->compiler);
+ }
+ res = $this->compiler->compilePrintExpression(i, a, l);
+ } else {
+ res = $this->compiler->compileTag(i,a, array('modifierlist'=>l));
+ }
+}
+
+ // registered object tag
+tag(res) ::= LDEL ID(i) PTR ID(m) attributes(a). {
+ res = $this->compiler->compileTag(i,a,array('object_method'=>m));
+}
+
+ // registered object tag with modifiers
+tag(res) ::= LDEL ID(i) PTR ID(me) modifierlist(l) attributes(a). {
+ res = $this->compiler->compileTag(i,a,array('modifierlist'=>l, 'object_method'=>me));
+}
+
+ // {if}, {elseif} and {while} tag
+tag(res) ::= LDELIF(i) expr(ie). {
+ $tag = trim(substr(i,$this->compiler->getLdelLength()));
+ res = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,array(),array('if condition'=>ie));
+}
+
+tag(res) ::= LDELIF(i) expr(ie) attributes(a). {
+ $tag = trim(substr(i,$this->compiler->getLdelLength()));
+ res = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,a,array('if condition'=>ie));
+}
+
+tag(res) ::= LDELIF(i) statement(ie). {
+ $tag = trim(substr(i,$this->compiler->getLdelLength()));
+ res = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,array(),array('if condition'=>ie));
+}
+
+tag(res) ::= LDELIF(i) statement(ie) attributes(a). {
+ $tag = trim(substr(i,$this->compiler->getLdelLength()));
+ res = $this->compiler->compileTag(($tag === 'else if')? 'elseif' : $tag,a,array('if condition'=>ie));
+}
+
+ // {for} tag
+tag(res) ::= LDELFOR statements(st) SEMICOLON expr(ie) SEMICOLON varindexed(v2) foraction(e2) attributes(a). {
+ res = $this->compiler->compileTag('for',array_merge(a,array(array('start'=>st),array('ifexp'=>ie),array('var'=>v2),array('step'=>e2))),1);
+}
+
+ foraction(res) ::= EQUAL expr(e). {
+ res = '='.e;
+}
+
+ foraction(res) ::= INCDEC(e). {
+ res = e;
+}
+
+tag(res) ::= LDELFOR statement(st) TO expr(v) attributes(a). {
+ res = $this->compiler->compileTag('for',array_merge(a,array(array('start'=>st),array('to'=>v))),0);
+}
+
+tag(res) ::= LDELFOR statement(st) TO expr(v) STEP expr(v2) attributes(a). {
+ res = $this->compiler->compileTag('for',array_merge(a,array(array('start'=>st),array('to'=>v),array('step'=>v2))),0);
+}
+
+ // {foreach} tag
+tag(res) ::= LDELFOREACH SPACE expr(e) AS varvar(v0) attributes(a). {
+ res = $this->compiler->compileTag('foreach',array_merge(a,array(array('from'=>e),array('item'=>v0))));
+}
+
+tag(res) ::= LDELFOREACH SPACE expr(e) AS varvar(v1) APTR varvar(v0) attributes(a). {
+ res = $this->compiler->compileTag('foreach',array_merge(a,array(array('from'=>e),array('item'=>v0),array('key'=>v1))));
+}
+tag(res) ::= LDELFOREACH attributes(a). {
+ res = $this->compiler->compileTag('foreach',a);
+}
+
+ // {setfilter}
+tag(res) ::= LDELSETFILTER ID(m) modparameters(p). {
+ res = $this->compiler->compileTag('setfilter',array(),array('modifier_list'=>array(array_merge(array(m),p))));
+}
+
+tag(res) ::= LDELSETFILTER ID(m) modparameters(p) modifierlist(l). {
+ res = $this->compiler->compileTag('setfilter',array(),array('modifier_list'=>array_merge(array(array_merge(array(m),p)),l)));
+}
+
+
+ // end of block tag {/....}
+smartytag(res)::= CLOSETAG(t). {
+ $tag = trim(substr(t, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' /');
+ if ($tag === 'strip') {
+ $this->strip = false;
+ res = null;
+ } else {
+ res = $this->compiler->compileTag($tag.'close',array());
+ }
+ }
+tag(res) ::= LDELSLASH ID(i). {
+ res = $this->compiler->compileTag(i.'close',array());
+}
+
+tag(res) ::= LDELSLASH ID(i) modifierlist(l). {
+ res = $this->compiler->compileTag(i.'close',array(),array('modifier_list'=>l));
+}
+
+ // end of block object tag {/....}
+tag(res) ::= LDELSLASH ID(i) PTR ID(m). {
+ res = $this->compiler->compileTag(i.'close',array(),array('object_method'=>m));
+}
+
+tag(res) ::= LDELSLASH ID(i) PTR ID(m) modifierlist(l). {
+ res = $this->compiler->compileTag(i.'close',array(),array('object_method'=>m, 'modifier_list'=>l));
+}
+
+//
+//Attributes of Smarty tags
+//
+ // list of attributes
+attributes(res) ::= attributes(a1) attribute(a2). {
+ res = a1;
+ res[] = a2;
+}
+
+ // single attribute
+attributes(res) ::= attribute(a). {
+ res = array(a);
+}
+
+ // no attributes
+attributes(res) ::= . {
+ res = array();
+}
+
+ // attribute
+attribute(res) ::= SPACE ID(v) EQUAL ID(id). {
+ if (defined(id)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant(id, $this->compiler);
+ }
+ res = array(v=>id);
+ } else {
+ res = array(v=>'\''.id.'\'');
+ }
+}
+
+attribute(res) ::= ATTR(v) expr(e). {
+ res = array(trim(v," =\n\r\t")=>e);
+}
+
+attribute(res) ::= ATTR(v) value(e). {
+ res = array(trim(v," =\n\r\t")=>e);
+}
+
+attribute(res) ::= SPACE ID(v). {
+ res = '\''.v.'\'';
+}
+
+attribute(res) ::= SPACE expr(e). {
+ res = e;
+}
+
+attribute(res) ::= SPACE value(v). {
+ res = v;
+}
+
+attribute(res) ::= SPACE INTEGER(i) EQUAL expr(e). {
+ res = array(i=>e);
+}
+
+
+
+//
+// statement
+//
+statements(res) ::= statement(s). {
+ res = array(s);
+}
+
+statements(res) ::= statements(s1) COMMA statement(s). {
+ s1[]=s;
+ res = s1;
+}
+
+statement(res) ::= DOLLARID(i) EQUAL INTEGER(e). {
+ res = array('var' => '\''.substr(i,1).'\'', 'value'=>e);
+}
+statement(res) ::= DOLLARID(i) EQUAL expr(e). {
+ res = array('var' => '\''.substr(i,1).'\'', 'value'=>e);
+}
+
+statement(res) ::= varindexed(vi) EQUAL expr(e). {
+ res = array('var' => vi, 'value'=>e);
+}
+
+statement(res) ::= OPENP statement(st) CLOSEP. {
+ res = st;
+}
+
+
+//
+// expressions
+//
+
+ // single value
+expr(res) ::= value(v). {
+ res = v;
+}
+
+ // nullcoalescing
+expr(res) ::= nullcoalescing(v). {
+ res = v;
+}
+
+ // ternary
+expr(res) ::= ternary(v). {
+ res = v;
+}
+
+ // ++$a / --$a
+expr(res) ::= INCDEC(i2) DOLLARID(i). {
+ res = '$_smarty_tpl->getVariable(\''. substr(i,1) .'\')->preIncDec(\'' . i2 . '\')';
+}
+
+ // $a++ / $a--
+expr(res) ::= DOLLARID(i) INCDEC(i2). {
+ res = '$_smarty_tpl->getVariable(\''. substr(i,1) .'\')->postIncDec(\'' . i2 . '\')';
+}
+
+ // resources/streams
+expr(res) ::= DOLLARID(i) COLON ID(i2). {
+ res = '$_smarty_tpl->getStreamVariable(\''.substr(i,1).'://' . i2 . '\')';
+}
+
+ // arithmetic expression
+expr(res) ::= expr(e) MATH(m) value(v). {
+ res = e . trim(m) . v;
+}
+
+expr(res) ::= expr(e) UNIMATH(m) value(v). {
+ res = e . trim(m) . v;
+}
+
+// if expression
+ // special conditions
+expr(res) ::= expr(e1) tlop(c) value(e2). {
+ res = c['pre']. e1.c['op'].e2 .')';
+}
+ // simple expression
+expr(res) ::= expr(e1) lop(c) expr(e2). {
+ res = e1.c.e2;
+}
+
+expr(res) ::= expr(e1) scond(c). {
+ res = c . e1 . ')';
+}
+
+expr(res) ::= expr(e1) ISIN array(a). {
+ res = 'in_array('.e1.','.a.')';
+}
+
+expr(res) ::= expr(e1) ISIN value(v). {
+ res = 'in_array('.e1.',(array)'.v.')';
+}
+
+// null coalescing
+nullcoalescing(res) ::= expr(v) QMARK QMARK expr(e2). {
+ res = v.' ?? '.e2;
+}
+
+//
+// ternary
+//
+ternary(res) ::= expr(v) QMARK DOLLARID(e1) COLON expr(e2). {
+ res = v.' ? '. $this->compiler->compileVariable('\''.substr(e1,1).'\'') . ' : '.e2;
+}
+
+ternary(res) ::= expr(v) QMARK value(e1) COLON expr(e2). {
+ res = v.' ? '.e1.' : '.e2;
+}
+
+ternary(res) ::= expr(v) QMARK expr(e1) COLON expr(e2). {
+ res = v.' ? '.e1.' : '.e2;
+}
+
+// shorthand ternary
+ternary(res) ::= expr(v) QMARK COLON expr(e2). {
+ res = v.' ?: '.e2;
+}
+
+ // value
+value(res) ::= variable(v). {
+ res = v;
+}
+
+ // +/- value
+value(res) ::= UNIMATH(m) value(v). {
+ res = m.v;
+}
+
+ // logical negation
+value(res) ::= NOT value(v). {
+ res = '!'.v;
+}
+
+value(res) ::= TYPECAST(t) value(v). {
+ res = t.v;
+}
+
+value(res) ::= variable(v) INCDEC(o). {
+ res = v.o;
+}
+
+ // numeric
+value(res) ::= HEX(n). {
+ res = n;
+}
+
+value(res) ::= INTEGER(n). {
+ res = n;
+}
+
+value(res) ::= INTEGER(n1) DOT INTEGER(n2). {
+ res = n1.'.'.n2;
+}
+
+value(res) ::= INTEGER(n1) DOT. {
+ res = n1.'.';
+}
+
+value(res) ::= DOT INTEGER(n1). {
+ res = '.'.n1;
+}
+
+ // ID, true, false, null
+value(res) ::= ID(id). {
+ if (defined(id)) {
+ if ($this->security) {
+ $this->security->isTrustedConstant(id, $this->compiler);
+ }
+ res = id;
+ } else {
+ res = '\''.id.'\'';
+ }
+}
+
+ // function call
+value(res) ::= function(f). {
+ res = f;
+}
+
+ // expression
+value(res) ::= OPENP expr(e) CLOSEP. {
+ res = '('. e .')';
+}
+
+value(res) ::= variable(v1) INSTANCEOF(i) ns1(v2). {
+ res = v1.i.v2;
+}
+value(res) ::= variable(v1) INSTANCEOF(i) variable(v2). {
+ res = v1.i.v2;
+}
+
+ // singele quoted string
+value(res) ::= SINGLEQUOTESTRING(t). {
+ res = t;
+}
+
+ // double quoted string
+value(res) ::= doublequoted_with_quotes(s). {
+ res = s;
+}
+
+
+value(res) ::= varindexed(vi) DOUBLECOLON static_class_access(r). {
+ if ($this->security && $this->security->static_classes !== array()) {
+ $this->compiler->trigger_template_error('dynamic static class not allowed by security setting');
+ }
+ $prefixVar = $this->compiler->getNewPrefixVariable();
+ if (vi['var'] === '\'smarty\'') {
+ $this->compiler->appendPrefixCode("<?php {$prefixVar} = ". (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).';?>');
+ } else {
+ $this->compiler->appendPrefixCode("<?php {$prefixVar} = ". $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'].';?>');
+ }
+ res = $prefixVar .'::'.r[0].r[1];
+}
+
+ // Smarty tag
+value(res) ::= smartytag(st). {
+ $prefixVar = $this->compiler->getNewPrefixVariable();
+ $tmp = $this->compiler->appendCode('<?php ob_start();?>', st);
+ $this->compiler->appendPrefixCode($this->compiler->appendCode($tmp, "<?php {$prefixVar} = ob_get_clean();?>"));
+ res = $prefixVar;
+}
+
+value(res) ::= value(v) modifierlist(l). {
+ res = $this->compiler->compileModifier(l, v);
+}
+ // name space constant
+value(res) ::= NAMESPACE(c). {
+ res = c;
+}
+
+ // array
+value(res) ::= arraydef(a). {
+ res = a;
+}
+ // static class access
+value(res) ::= ns1(c)DOUBLECOLON static_class_access(s). {
+ if (!in_array(strtolower(c), array('self', 'parent')) && (!$this->security || $this->security->isTrustedStaticClassAccess(c, s, $this->compiler))) {
+ if (isset($this->smarty->registered_classes[c])) {
+ res = $this->smarty->registered_classes[c].'::'.s[0].s[1];
+ } else {
+ res = c.'::'.s[0].s[1];
+ }
+ } else {
+ $this->compiler->trigger_template_error ('static class \''.c.'\' is undefined or not allowed by security setting');
+ }
+}
+//
+// namespace stuff
+//
+
+ns1(res) ::= ID(i). {
+ res = i;
+}
+
+ns1(res) ::= NAMESPACE(i). {
+ res = i;
+ }
+
+
+
+
+//
+// variables
+//
+ // Smarty variable (optional array)
+variable(res) ::= DOLLARID(i). {
+ res = $this->compiler->compileVariable('\''.substr(i,1).'\'');
+}
+variable(res) ::= varindexed(vi). {
+ if (vi['var'] === '\'smarty\'') {
+ $smarty_var = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']);
+ res = $smarty_var;
+ } else {
+ // used for array reset,next,prev,end,current
+ $this->last_variable = vi['var'];
+ $this->last_index = vi['smarty_internal_index'];
+ res = $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'];
+ }
+}
+
+ // variable with property
+variable(res) ::= varvar(v) AT ID(p). {
+ res = '$_smarty_tpl->getVariable('. v .')->'.p;
+}
+
+ // object
+variable(res) ::= object(o). {
+ res = o;
+}
+
+ // config variable
+variable(res) ::= HATCH ID(i) HATCH. {
+ res = $this->compiler->compileConfigVariable('\'' . i . '\'');
+}
+
+variable(res) ::= HATCH ID(i) HATCH arrayindex(a). {
+ res = '(is_array($tmp = ' . $this->compiler->compileConfigVariable('\'' . i . '\'') . ') ? $tmp'.a.' :null)';
+}
+
+variable(res) ::= HATCH variable(v) HATCH. {
+ res = $this->compiler->compileConfigVariable(v);
+}
+
+variable(res) ::= HATCH variable(v) HATCH arrayindex(a). {
+ res = '(is_array($tmp = ' . $this->compiler->compileConfigVariable(v) . ') ? $tmp'.a.' : null)';
+}
+
+varindexed(res) ::= DOLLARID(i) arrayindex(a). {
+ res = array('var'=>'\''.substr(i,1).'\'', 'smarty_internal_index'=>a);
+}
+varindexed(res) ::= varvar(v) arrayindex(a). {
+ res = array('var'=>v, 'smarty_internal_index'=>a);
+}
+
+//
+// array index
+//
+ // multiple array index
+arrayindex(res) ::= arrayindex(a1) indexdef(a2). {
+ res = a1.a2;
+}
+
+ // no array index
+arrayindex ::= . {
+ return;
+}
+
+// single index definition
+ // Smarty2 style index
+indexdef(res) ::= DOT DOLLARID(i). {
+ res = '['.$this->compiler->compileVariable('\''.substr(i,1).'\'').']';
+}
+indexdef(res) ::= DOT varvar(v). {
+ res = '['.$this->compiler->compileVariable(v).']';
+}
+
+indexdef(res) ::= DOT varvar(v) AT ID(p). {
+ res = '['.$this->compiler->compileVariable(v).'->'.p.']';
+}
+
+indexdef(res) ::= DOT ID(i). {
+ res = '[\''. i .'\']';
+}
+
+indexdef(res) ::= DOT INTEGER(n). {
+ res = '['. n .']';
+}
+
+
+indexdef(res) ::= DOT LDEL expr(e) RDEL. {
+ res = '['. e .']';
+}
+
+ // section tag index
+indexdef(res) ::= OPENB ID(i)CLOSEB. {
+ res = '['.(new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,'[\'section\'][\''.i.'\'][\'index\']').']';
+}
+
+indexdef(res) ::= OPENB ID(i) DOT ID(i2) CLOSEB. {
+ res = '['.(new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,'[\'section\'][\''.i.'\'][\''.i2.'\']').']';
+}
+indexdef(res) ::= OPENB SINGLEQUOTESTRING(s) CLOSEB. {
+ res = '['.s.']';
+}
+indexdef(res) ::= OPENB INTEGER(n) CLOSEB. {
+ res = '['.n.']';
+}
+indexdef(res) ::= OPENB DOLLARID(i) CLOSEB. {
+ res = '['.$this->compiler->compileVariable('\''.substr(i,1).'\'').']';
+}
+indexdef(res) ::= OPENB variable(v) CLOSEB. {
+ res = '['.v.']';
+}
+indexdef(res) ::= OPENB value(v) CLOSEB. {
+ res = '['.v.']';
+}
+
+ // PHP style index
+indexdef(res) ::= OPENB expr(e) CLOSEB. {
+ res = '['. e .']';
+}
+
+ // for assign append array
+indexdef(res) ::= OPENB CLOSEB. {
+ res = '[]';
+}
+
+
+//
+// variable variable names
+//
+
+ // singel identifier element
+varvar(res) ::= DOLLARID(i). {
+ res = '\''.substr(i,1).'\'';
+}
+ // single $
+varvar(res) ::= DOLLAR. {
+ res = '\'\'';
+}
+
+ // sequence of identifier elements
+varvar(res) ::= varvar(v1) varvarele(v2). {
+ res = v1.'.'.v2;
+}
+
+ // fix sections of element
+varvarele(res) ::= ID(s). {
+ res = '\''.s.'\'';
+}
+varvarele(res) ::= SIMPELOUTPUT(i). {
+ $var = trim(substr(i, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $');
+ res = $this->compiler->compileVariable('\''.$var.'\'');
+}
+
+ // variable sections of element
+varvarele(res) ::= LDEL expr(e) RDEL. {
+ res = '('.e.')';
+}
+
+//
+// objects
+//
+object(res) ::= varindexed(vi) objectchain(oc). {
+ if (vi['var'] === '\'smarty\'') {
+ res = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).oc;
+ } else {
+ res = $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'].oc;
+ }
+}
+
+ // single element
+objectchain(res) ::= objectelement(oe). {
+ res = oe;
+}
+
+ // chain of elements
+objectchain(res) ::= objectchain(oc) objectelement(oe). {
+ res = oc.oe;
+}
+
+ // variable
+objectelement(res)::= PTR ID(i) arrayindex(a). {
+ if ($this->security && substr(i,0,1) === '_') {
+ $this->compiler->trigger_template_error (self::ERR1);
+ }
+ res = '->'.i.a;
+}
+
+objectelement(res)::= PTR varvar(v) arrayindex(a). {
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ res = '->{'.$this->compiler->compileVariable(v).a.'}';
+}
+
+objectelement(res)::= PTR LDEL expr(e) RDEL arrayindex(a). {
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ res = '->{'.e.a.'}';
+}
+
+objectelement(res)::= PTR ID(ii) LDEL expr(e) RDEL arrayindex(a). {
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ res = '->{\''.ii.'\'.'.e.a.'}';
+}
+
+ // method
+objectelement(res)::= PTR method(f). {
+ res = '->'.f;
+}
+
+
+//
+// function
+//
+function(res) ::= ns1(f) OPENP params(p) CLOSEP. {
+ res = $this->compiler->compileFunctionCall(f, p);
+}
+
+
+//
+// method
+//
+method(res) ::= ID(f) OPENP params(p) CLOSEP. {
+ if ($this->security && substr(f,0,1) === '_') {
+ $this->compiler->trigger_template_error (self::ERR1);
+ }
+ res = f . '('. implode(',',p) .')';
+}
+
+method(res) ::= DOLLARID(f) OPENP params(p) CLOSEP. {
+ if ($this->security) {
+ $this->compiler->trigger_template_error (self::ERR2);
+ }
+ $prefixVar = $this->compiler->getNewPrefixVariable();
+ $this->compiler->appendPrefixCode("<?php {$prefixVar} = ".$this->compiler->compileVariable('\''.substr(f,1).'\'').';?>');
+ res = $prefixVar .'('. implode(',',p) .')';
+}
+
+// function/method parameter
+ // multiple parameters
+params(res) ::= params(p) COMMA expr(e). {
+ res = array_merge(p,array(e));
+}
+
+ // single parameter
+params(res) ::= expr(e). {
+ res = array(e);
+}
+
+ // no parameter
+params(res) ::= . {
+ res = array();
+}
+
+//
+// modifier
+//
+modifierlist(res) ::= modifierlist(l) modifier(m) modparameters(p). {
+ res = array_merge(l,array(array_merge(m,p)));
+}
+
+modifierlist(res) ::= modifier(m) modparameters(p). {
+ res = array(array_merge(m,p));
+}
+
+modifier(res) ::= VERT AT ID(m). {
+ res = array(m);
+}
+
+modifier(res) ::= VERT ID(m). {
+ res = array(m);
+}
+
+//
+// modifier parameter
+//
+ // multiple parameter
+modparameters(res) ::= modparameters(mps) modparameter(mp). {
+ res = array_merge(mps,mp);
+}
+
+ // no parameter
+modparameters(res) ::= . {
+ res = array();
+}
+
+ // parameter expression
+modparameter(res) ::= COLON value(mp). {
+ res = array(mp);
+}
+modparameter(res) ::= COLON UNIMATH(m) value(mp). {
+ res = array(trim(m).mp);
+}
+
+modparameter(res) ::= COLON array(mp). {
+ res = array(mp);
+}
+
+ // static class methode call
+static_class_access(res) ::= method(m). {
+ res = array(m, '', 'method');
+}
+
+ // static class methode call with object chainig
+static_class_access(res) ::= method(m) objectchain(oc). {
+ res = array(m, oc, 'method');
+}
+
+ // static class constant
+static_class_access(res) ::= ID(v). {
+ res = array(v, '');
+}
+
+ // static class variables
+static_class_access(res) ::= DOLLARID(v) arrayindex(a). {
+ res = array(v, a, 'property');
+}
+
+ // static class variables with object chain
+static_class_access(res) ::= DOLLARID(v) arrayindex(a) objectchain(oc). {
+ res = array(v, a.oc, 'property');
+}
+
+
+// if conditions and operators
+lop(res) ::= LOGOP(o). {
+ res = ' '. trim(o) . ' ';
+}
+
+lop(res) ::= SLOGOP(o). {
+ static $lops = array(
+ 'eq' => ' == ',
+ 'ne' => ' != ',
+ 'neq' => ' != ',
+ 'gt' => ' > ',
+ 'ge' => ' >= ',
+ 'gte' => ' >= ',
+ 'lt' => ' < ',
+ 'le' => ' <= ',
+ 'lte' => ' <= ',
+ 'mod' => ' % ',
+ 'and' => ' && ',
+ 'or' => ' || ',
+ 'xor' => ' xor ',
+ );
+ $op = strtolower(preg_replace('/\s*/', '', o));
+ res = $lops[$op];
+}
+tlop(res) ::= TLOGOP(o). {
+ static $tlops = array(
+ 'isdivby' => array('op' => ' % ', 'pre' => '!('),
+ 'isnotdivby' => array('op' => ' % ', 'pre' => '('),
+ 'isevenby' => array('op' => ' / ', 'pre' => '!(1 & '),
+ 'isnotevenby' => array('op' => ' / ', 'pre' => '(1 & '),
+ 'isoddby' => array('op' => ' / ', 'pre' => '(1 & '),
+ 'isnotoddby' => array('op' => ' / ', 'pre' => '!(1 & '),
+ );
+ $op = strtolower(preg_replace('/\s*/', '', o));
+ res = $tlops[$op];
+ }
+
+scond(res) ::= SINGLECOND(o). {
+ static $scond = array (
+ 'iseven' => '!(1 & ',
+ 'isnoteven' => '(1 & ',
+ 'isodd' => '(1 & ',
+ 'isnotodd' => '!(1 & ',
+ );
+ $op = strtolower(str_replace(' ', '', o));
+ res = $scond[$op];
+}
+
+//
+// ARRAY element assignment
+//
+arraydef(res) ::= OPENB arrayelements(a) CLOSEB. {
+ res = 'array('.a.')';
+}
+arraydef(res) ::= ARRAYOPEN arrayelements(a) CLOSEP. {
+ res = 'array('.a.')';
+}
+
+arrayelements(res) ::= arrayelement(a). {
+ res = a;
+}
+
+arrayelements(res) ::= arrayelements(a1) COMMA arrayelement(a). {
+ res = a1.','.a;
+}
+
+arrayelements ::= . {
+ return;
+}
+
+arrayelement(res) ::= value(e1) APTR expr(e2). {
+ res = e1.'=>'.e2;
+}
+
+arrayelement(res) ::= ID(i) APTR expr(e2). {
+ res = '\''.i.'\'=>'.e2;
+}
+
+arrayelement(res) ::= expr(e). {
+ res = e;
+}
+
+
+//
+// double quoted strings
+//
+doublequoted_with_quotes(res) ::= QUOTE QUOTE. {
+ res = '\'\'';
+}
+
+doublequoted_with_quotes(res) ::= QUOTE doublequoted(s) QUOTE. {
+ $this->compiler->leaveDoubleQuote();
+ res = s->to_smarty_php($this);
+}
+
+
+doublequoted(res) ::= doublequoted(o1) doublequotedcontent(o2). {
+ o1->append_subtree($this, o2);
+ res = o1;
+}
+
+doublequoted(res) ::= doublequotedcontent(o). {
+ res = new Dq($this, o);
+}
+
+doublequotedcontent(res) ::= BACKTICK variable(v) BACKTICK. {
+ res = new Code('(string)'.v);
+}
+
+doublequotedcontent(res) ::= BACKTICK expr(e) BACKTICK. {
+ res = new Code('(string)('.e.')');
+}
+
+doublequotedcontent(res) ::= DOLLARID(i). {
+ res = new Code('(string)$_smarty_tpl->getValue(\''. substr(i,1) .'\')');
+}
+
+doublequotedcontent(res) ::= LDEL variable(v) RDEL. {
+ res = new Code('(string)'.v);
+}
+
+doublequotedcontent(res) ::= LDEL expr(e) RDEL. {
+ res = new Code('(string)('.e.')');
+}
+
+doublequotedcontent(res) ::= smartytag(st). {
+ res = new Tag($this, st);
+}
+
+doublequotedcontent(res) ::= TEXT(o). {
+ res = new DqContent(o);
+}
+
diff --git a/src/Resource/BasePlugin.php b/src/Resource/BasePlugin.php
new file mode 100644
index 00000000..56f7dfa0
--- /dev/null
+++ b/src/Resource/BasePlugin.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace Smarty\Resource;
+
+use Smarty\Exception;
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Source;
+
+/**
+ * Smarty Resource Plugin
+ * Base implementation for resource plugins
+ * @author Rodney Rehm
+ */
+abstract class BasePlugin
+{
+ /**
+ * resource types provided by the core
+ *
+ * @var array
+ */
+ public static $sysplugins = [
+ 'file' => FilePlugin::class,
+ 'string' => StringPlugin::class,
+ 'extends' => ExtendsPlugin::class,
+ 'stream' => StreamPlugin::class,
+ 'eval' => StringEval::class,
+ ];
+
+ /**
+ * Source must be recompiled on every occasion
+ *
+ * @var boolean
+ */
+ public $recompiled = false;
+
+ /**
+ * Flag if resource does allow compilation
+ *
+ * @return bool
+ */
+ public function supportsCompiledTemplates(): bool {
+ return true;
+ }
+
+ /**
+ * Check if resource must check time stamps when loading compiled or cached templates.
+ * Resources like 'extends' which use source components my disable timestamp checks on own resource.
+ * @return bool
+ */
+ public function checkTimestamps()
+ {
+ return true;
+ }
+
+ /**
+ * Load Resource Handler
+ *
+ * @param Smarty $smarty smarty object
+ * @param string $type name of the resource
+ *
+ * @return BasePlugin Resource Handler
+ * @throws Exception
+ */
+ public static function load(Smarty $smarty, $type)
+ {
+ // try smarty's cache
+ if (isset($smarty->_resource_handlers[ $type ])) {
+ return $smarty->_resource_handlers[ $type ];
+ }
+ // try registered resource
+ if (isset($smarty->registered_resources[ $type ])) {
+ return $smarty->_resource_handlers[ $type ] = $smarty->registered_resources[ $type ];
+ }
+ // try sysplugins dir
+ if (isset(self::$sysplugins[ $type ])) {
+ $_resource_class = self::$sysplugins[ $type ];
+ return $smarty->_resource_handlers[ $type ] = new $_resource_class();
+ }
+ // try plugins dir
+ $_resource_class = 'Smarty_Resource_' . \smarty_ucfirst_ascii($type);
+ if (class_exists($_resource_class, false)) {
+ return $smarty->_resource_handlers[ $type ] = new $_resource_class();
+ }
+ // try streams
+ $_known_stream = stream_get_wrappers();
+ if (in_array($type, $_known_stream)) {
+ // is known stream
+ if (is_object($smarty->security_policy)) {
+ $smarty->security_policy->isTrustedStream($type);
+ }
+ return $smarty->_resource_handlers[ $type ] = new StreamPlugin();
+ }
+ // TODO: try default_(template|config)_handler
+ // give up
+ throw new \Smarty\Exception("Unknown resource type '{$type}'");
+ }
+
+ /**
+ * Load template's source into current template object
+ *
+ * @param Source $source source object
+ *
+ * @return string template source
+ * @throws \Smarty\Exception if source cannot be loaded
+ */
+ abstract public function getContent(Source $source);
+
+ /**
+ * populate Source Object with metadata from Resource
+ *
+ * @param Source $source source object
+ * @param Template|null $_template template object
+ */
+ abstract public function populate(Source $source, \Smarty\Template $_template = null);
+
+ /**
+ * populate Source Object with timestamp and exists from Resource
+ *
+ * @param Source $source source object
+ */
+ public function populateTimestamp(Source $source)
+ {
+ // intentionally left blank
+ }
+
+ /*
+ * Check if resource must check time stamps when when loading complied or cached templates.
+ * Resources like 'extends' which use source components my disable timestamp checks on own resource.
+ *
+ * @return bool
+ */
+ /**
+ * Determine basename for compiled filename
+ *
+ * @param \Smarty\Template\Source $source source object
+ *
+ * @return string resource's basename
+ */
+ public function getBasename(\Smarty\Template\Source $source)
+ {
+ return basename(preg_replace('![^\w]+!', '_', $source->name));
+ }
+
+}
diff --git a/src/Resource/CustomPlugin.php b/src/Resource/CustomPlugin.php
new file mode 100644
index 00000000..b50ef7aa
--- /dev/null
+++ b/src/Resource/CustomPlugin.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Smarty Resource Plugin
+ *
+
+
+ * @author Rodney Rehm
+ */
+
+namespace Smarty\Resource;
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Source;
+use Smarty\Exception;
+
+/**
+ * Smarty Resource Plugin
+ * Wrapper Implementation for custom resource plugins
+ *
+
+
+ */
+abstract class CustomPlugin extends BasePlugin {
+
+ /**
+ * fetch template and its modification time from data source
+ *
+ * @param string $name template name
+ * @param string &$source template source
+ * @param integer &$mtime template modification timestamp (epoch)
+ */
+ abstract protected function fetch($name, &$source, &$mtime);
+
+ /**
+ * Fetch template's modification timestamp from data source
+ * {@internal implementing this method is optional.
+ * Only implement it if modification times can be accessed faster than loading the complete template source.}}
+ *
+ * @param string $name template name
+ *
+ * @return integer|boolean timestamp (epoch) the template was modified, or false if not found
+ */
+ protected function fetchTimestamp($name) {
+ return null;
+ }
+
+ /**
+ * populate Source Object with metadata from Resource
+ *
+ * @param Source $source source object
+ * @param Template|null $_template template object
+ */
+ public function populate(Source $source, Template $_template = null) {
+ $source->uid = sha1($source->type . ':' . $source->name);
+ $mtime = $this->fetchTimestamp($source->name);
+ if ($mtime !== null) {
+ $source->timestamp = $mtime;
+ } else {
+ $this->fetch($source->name, $content, $timestamp);
+ $source->timestamp = $timestamp ?? false;
+ if (isset($content)) {
+ $source->content = $content;
+ }
+ }
+ $source->exists = !!$source->timestamp;
+ }
+
+ /**
+ * Load template's source into current template object
+ *
+ * @param Source $source source object
+ *
+ * @return string template source
+ * @throws Exception if source cannot be loaded
+ */
+ public function getContent(Source $source) {
+ $this->fetch($source->name, $content, $timestamp);
+ if (isset($content)) {
+ return $content;
+ }
+ throw new Exception("Unable to read template {$source->type} '{$source->name}'");
+ }
+
+ /**
+ * Determine basename for compiled filename
+ *
+ * @param Source $source source object
+ *
+ * @return string resource's basename
+ */
+ public function getBasename(Source $source) {
+ return basename($this->generateSafeName($source->name));
+ }
+
+ /**
+ * Removes special characters from $name and limits its length to 127 characters.
+ *
+ * @param $name
+ *
+ * @return string
+ */
+ private function generateSafeName($name): string {
+ return substr(preg_replace('/[^A-Za-z0-9._]/', '', (string)$name), 0, 127);
+ }
+}
diff --git a/src/Resource/ExtendsPlugin.php b/src/Resource/ExtendsPlugin.php
new file mode 100644
index 00000000..acce54e2
--- /dev/null
+++ b/src/Resource/ExtendsPlugin.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Smarty\Resource;
+
+use Smarty\Exception;
+use Smarty\Template;
+use Smarty\Template\Source;
+
+/**
+ * Smarty Internal Plugin Resource Extends
+ * Implements the file system as resource for Smarty which {extend}s a chain of template files templates
+ * @author Uwe Tews
+ * @author Rodney Rehm
+ */
+class ExtendsPlugin extends BasePlugin
+{
+
+ /**
+ * populate Source Object with metadata from Resource
+ *
+ * @param Source $source source object
+ * @param Template|null $_template template object
+ *
+ * @throws Exception
+ */
+ public function populate(Source $source, Template $_template = null)
+ {
+ $uid = '';
+ $sources = array();
+ $components = explode('|', $source->name);
+ $smarty = $source->getSmarty();
+ $exists = true;
+ foreach ($components as $component) {
+ $_s = Source::load(null, $smarty, $component);
+ $sources[ $_s->uid ] = $_s;
+ $uid .= $_s->uid;
+ if ($_template) {
+ $exists = $exists && $_s->exists;
+ }
+ }
+ $source->components = $sources;
+ $source->uid = sha1($uid . $source->getSmarty()->_joined_template_dir);
+ $source->exists = $exists;
+ if ($_template) {
+ $source->timestamp = $_s->timestamp;
+ }
+ }
+
+ /**
+ * populate Source Object with timestamp and exists from Resource
+ *
+ * @param Source $source source object
+ */
+ public function populateTimestamp(Source $source)
+ {
+ $source->exists = true;
+ /* @var Source $_s */
+ foreach ($source->components as $_s) {
+ $source->exists = $source->exists && $_s->exists;
+ }
+ $source->timestamp = $source->exists ? $_s->getTimeStamp() : false;
+ }
+
+ /**
+ * Load template's source from files into current template object
+ *
+ * @param Source $source source object
+ *
+ * @return string template source
+ * @throws \Smarty\Exception if source cannot be loaded
+ */
+ public function getContent(Source $source)
+ {
+ if (!$source->exists) {
+ throw new \Smarty\Exception("Unable to load '{$source->type}:{$source->name}'");
+ }
+ $_components = array_reverse($source->components);
+ $_content = '';
+ /* @var Source $_s */
+ foreach ($_components as $_s) {
+ // read content
+ $_content .= $_s->getContent();
+ }
+ return $_content;
+ }
+
+ /**
+ * Determine basename for compiled filename
+ *
+ * @param Source $source source object
+ *
+ * @return string resource's basename
+ */
+ public function getBasename(Source $source)
+ {
+ return str_replace(':', '.', basename($source->getResourceName()));
+ }
+
+ /*
+ * Disable timestamp checks for extends resource.
+ * The individual source components will be checked.
+ *
+ * @return bool
+ */
+ /**
+ * @return bool
+ */
+ public function checkTimestamps()
+ {
+ return false;
+ }
+}
diff --git a/src/Resource/FilePlugin.php b/src/Resource/FilePlugin.php
new file mode 100644
index 00000000..7fd8667d
--- /dev/null
+++ b/src/Resource/FilePlugin.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Smarty Internal Plugin Resource File
+ *
+
+
+ * @author Uwe Tews
+ * @author Rodney Rehm
+ */
+
+namespace Smarty\Resource;
+
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Source;
+use Smarty\Exception;
+
+/**
+ * Smarty Internal Plugin Resource File
+ * Implements the file system as resource for Smarty templates
+ *
+
+
+ */
+class FilePlugin extends BasePlugin {
+
+ /**
+ * populate Source Object with metadata from Resource
+ *
+ * @param Source $source source object
+ * @param Template|null $_template template object
+ *
+ * @throws Exception
+ */
+ public function populate(Source $source, Template $_template = null) {
+
+ $source->uid = sha1(
+ $source->name . ($source->isConfig ? $source->getSmarty()->_joined_config_dir :
+ $source->getSmarty()->_joined_template_dir)
+ );
+
+ if ($path = $this->getFilePath($source->name, $source->getSmarty(), $source->isConfig)) {
+ if (isset($source->getSmarty()->security_policy) && is_object($source->getSmarty()->security_policy)) {
+ $source->getSmarty()->security_policy->isTrustedResourceDir($path, $source->isConfig);
+ }
+ $source->exists = true;
+ $source->timestamp = filemtime($path);
+ } else {
+ $source->timestamp = $source->exists = false;
+ }
+ }
+
+ /**
+ * populate Source Object with timestamp and exists from Resource
+ *
+ * @param Source $source source object
+ */
+ public function populateTimestamp(Source $source) {
+ if (!$source->exists && $path = $this->getFilePath($source->name, $source->getSmarty(), $source->isConfig)) {
+ $source->timestamp = $source->exists = is_file($path);
+ }
+ if ($source->exists && $path) {
+ $source->timestamp = filemtime($path);
+ }
+ }
+
+ /**
+ * Load template's source from file into current template object
+ *
+ * @param Source $source source object
+ *
+ * @return string template source
+ * @throws Exception if source cannot be loaded
+ */
+ public function getContent(Source $source) {
+ if ($source->exists) {
+ return file_get_contents($this->getFilePath($source->getResourceName(), $source->getSmarty(), $source->isConfig()));
+ }
+ throw new Exception(
+ 'Unable to read ' . ($source->isConfig ? 'config' : 'template') .
+ " {$source->type} '{$source->name}'"
+ );
+ }
+
+ /**
+ * Determine basename for compiled filename
+ *
+ * @param Source $source source object
+ *
+ * @return string resource's basename
+ */
+ public function getBasename(Source $source) {
+ return basename($source->getResourceName());
+ }
+
+ /**
+ * build template filepath by traversing the template_dir array
+ *
+ * @param $file
+ * @param Smarty $smarty
+ * @param bool $isConfig
+ *
+ * @return string fully qualified filepath
+ */
+ public function getFilePath($file, \Smarty\Smarty $smarty, bool $isConfig = false) {
+ // absolute file ?
+ if ($file[0] === '/' || $file[1] === ':') {
+ $file = $smarty->_realpath($file, true);
+ return is_file($file) ? $file : false;
+ }
+
+ // normalize DIRECTORY_SEPARATOR
+ if (strpos($file, DIRECTORY_SEPARATOR === '/' ? '\\' : '/') !== false) {
+ $file = str_replace(DIRECTORY_SEPARATOR === '/' ? '\\' : '/', DIRECTORY_SEPARATOR, $file);
+ }
+ $_directories = $smarty->getTemplateDir(null, $isConfig);
+ // template_dir index?
+ if ($file[0] === '[' && preg_match('#^\[([^\]]+)\](.+)$#', $file, $fileMatch)) {
+ $file = $fileMatch[2];
+ $_indices = explode(',', $fileMatch[1]);
+ $_index_dirs = [];
+ foreach ($_indices as $index) {
+ $index = trim($index);
+ // try string indexes
+ if (isset($_directories[$index])) {
+ $_index_dirs[] = $_directories[$index];
+ } elseif (is_numeric($index)) {
+ // try numeric index
+ $index = (int)$index;
+ if (isset($_directories[$index])) {
+ $_index_dirs[] = $_directories[$index];
+ } else {
+ // try at location index
+ $keys = array_keys($_directories);
+ if (isset($_directories[$keys[$index]])) {
+ $_index_dirs[] = $_directories[$keys[$index]];
+ }
+ }
+ }
+ }
+ if (empty($_index_dirs)) {
+ // index not found
+ return false;
+ } else {
+ $_directories = $_index_dirs;
+ }
+ }
+ // relative file name?
+ foreach ($_directories as $_directory) {
+ $path = $_directory . $file;
+ if (is_file($path)) {
+ return (strpos($path, '.' . DIRECTORY_SEPARATOR) !== false) ? $smarty->_realpath($path) : $path;
+ }
+ }
+ if (!isset($_index_dirs)) {
+ // Could be relative to cwd
+ $path = $smarty->_realpath($file, true);
+ if (is_file($path)) {
+ return $path;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the timestamp of the resource indicated by $resourceName, or false if it doesn't exist.
+ *
+ * @param string $resourceName
+ * @param Smarty $smarty
+ * @param bool $isConfig
+ *
+ * @return false|int
+ */
+ public function getResourceNameTimestamp(string $resourceName, \Smarty\Smarty $smarty, bool $isConfig = false) {
+ if ($path = $this->getFilePath($resourceName, $smarty, $isConfig)) {
+ return filemtime($path);
+ }
+ return false;
+ }
+}
diff --git a/src/Resource/RecompiledPlugin.php b/src/Resource/RecompiledPlugin.php
new file mode 100644
index 00000000..f1c64bc7
--- /dev/null
+++ b/src/Resource/RecompiledPlugin.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Smarty Resource Plugin
+ *
+
+
+ * @author Rodney Rehm
+ */
+
+namespace Smarty\Resource;
+
+use Smarty\Template;
+
+/**
+ * Smarty Resource Plugin
+ * Base implementation for resource plugins that don't compile cache
+ *
+
+
+ */
+abstract class RecompiledPlugin extends BasePlugin {
+
+ /**
+ * Flag that it's an recompiled resource
+ *
+ * @var bool
+ */
+ public $recompiled = true;
+
+ /**
+ * Flag if resource does allow compilation
+ *
+ * @return bool
+ */
+ public function supportsCompiledTemplates(): bool {
+ return false;
+ }
+
+ /*
+ * Disable timestamp checks for recompiled resource.
+ *
+ * @return bool
+ */
+ /**
+ * @return bool
+ */
+ public function checkTimestamps() {
+ return false;
+ }
+}
diff --git a/src/Resource/StreamPlugin.php b/src/Resource/StreamPlugin.php
new file mode 100644
index 00000000..91afffdc
--- /dev/null
+++ b/src/Resource/StreamPlugin.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Smarty Internal Plugin Resource Stream
+ * Implements the streams as resource for Smarty template
+ *
+
+
+ * @author Uwe Tews
+ * @author Rodney Rehm
+ */
+
+namespace Smarty\Resource;
+
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Source;
+
+/**
+ * Smarty Internal Plugin Resource Stream
+ * Implements the streams as resource for Smarty template
+ *
+ * @link https://php.net/streams
+
+
+ */
+class StreamPlugin extends RecompiledPlugin {
+
+ /**
+ * populate Source Object with meta data from Resource
+ *
+ * @param Source $source source object
+ * @param Template $_template template object
+ *
+ * @return void
+ */
+ public function populate(Source $source, Template $_template = null) {
+ $source->uid = false;
+ $source->content = $this->getContent($source);
+ $source->timestamp = $source->exists = !!$source->content;
+ }
+
+ /**
+ * Load template's source from stream into current template object
+ *
+ * @param Source $source source object
+ *
+ * @return string template source
+ */
+ public function getContent(Source $source) {
+
+ if (strpos($source->getResourceName(), '://') !== false) {
+ $filepath = $source->getResourceName();
+ } else {
+ $filepath = str_replace(':', '://', $source->getFullResourceName());
+ }
+
+ $t = '';
+ // the availability of the stream has already been checked in Smarty\Resource\Base::fetch()
+ $fp = fopen($filepath, 'r+');
+ if ($fp) {
+ while (!feof($fp) && ($current_line = fgets($fp)) !== false) {
+ $t .= $current_line;
+ }
+ fclose($fp);
+ return $t;
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/src/Resource/StringEval.php b/src/Resource/StringEval.php
new file mode 100644
index 00000000..5c35e743
--- /dev/null
+++ b/src/Resource/StringEval.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Smarty\Resource;
+
+use Smarty\Smarty;
+
+/**
+ * Smarty Internal Plugin Resource Eval
+ *
+
+
+ * @author Uwe Tews
+ * @author Rodney Rehm
+ */
+
+/**
+ * Smarty Internal Plugin Resource Eval
+ * Implements the strings as resource for Smarty template
+ * {@internal unlike string-resources the compiled state of eval-resources is NOT saved for subsequent access}}
+ *
+
+
+ */
+class StringEval extends RecompiledPlugin
+{
+ /**
+ * populate Source Object with meta data from Resource
+ *
+ * @param \Smarty\Template\Source $source source object
+ * @param \Smarty\Template $_template template object
+ *
+ * @return void
+ */
+ public function populate(\Smarty\Template\Source $source, \Smarty\Template $_template = null)
+ {
+ $source->uid = sha1($source->name);
+ $source->timestamp = $source->exists = true;
+ }
+
+ /**
+ * Load template's source from $resource_name into current template object
+ *
+ * @param \Smarty\Template\Source $source source object
+ *
+ * @return string template source
+ *@uses decode() to decode base64 and urlencoded template_resources
+ *
+ */
+ public function getContent(\Smarty\Template\Source $source)
+ {
+ return $this->decode($source->name);
+ }
+
+ /**
+ * decode base64 and urlencode
+ *
+ * @param string $string template_resource to decode
+ *
+ * @return string decoded template_resource
+ */
+ protected function decode($string)
+ {
+ // decode if specified
+ if (($pos = strpos($string, ':')) !== false) {
+ if (!strncmp($string, 'base64', 6)) {
+ return base64_decode(substr($string, 7));
+ } elseif (!strncmp($string, 'urlencode', 9)) {
+ return urldecode(substr($string, 10));
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * Determine basename for compiled filename
+ *
+ * @param \Smarty\Template\Source $source source object
+ *
+ * @return string resource's basename
+ */
+ public function getBasename(\Smarty\Template\Source $source)
+ {
+ return '';
+ }
+}
diff --git a/src/Resource/StringPlugin.php b/src/Resource/StringPlugin.php
new file mode 100644
index 00000000..6b5bee4d
--- /dev/null
+++ b/src/Resource/StringPlugin.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Smarty Internal Plugin Resource String
+ *
+
+
+ * @author Uwe Tews
+ * @author Rodney Rehm
+ */
+
+namespace Smarty\Resource;
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Template\Source;
+
+/**
+ * Smarty Internal Plugin Resource String
+ * Implements the strings as resource for Smarty template
+ * {@internal unlike eval-resources the compiled state of string-resources is saved for subsequent access}}
+ *
+
+
+ */
+class StringPlugin extends BasePlugin {
+
+ /**
+ * populate Source Object with metadata from Resource
+ *
+ * @param Source $source source object
+ * @param Template $_template template object
+ *
+ * @return void
+ */
+ public function populate(Source $source, Template $_template = null) {
+ $source->uid = sha1($source->name);
+ $source->timestamp = $source->exists = true;
+ }
+
+ /**
+ * Load template's source from $resource_name into current template object
+ *
+ * @param Source $source source object
+ *
+ * @return string template source
+ * @uses decode() to decode base64 and urlencoded template_resources
+ *
+ */
+ public function getContent(Source $source) {
+ return $this->decode($source->name);
+ }
+
+ /**
+ * decode base64 and urlencode
+ *
+ * @param string $string template_resource to decode
+ *
+ * @return string decoded template_resource
+ */
+ protected function decode($string) {
+ // decode if specified
+ if (($pos = strpos($string, ':')) !== false) {
+ if (!strncmp($string, 'base64', 6)) {
+ return base64_decode(substr($string, 7));
+ } elseif (!strncmp($string, 'urlencode', 9)) {
+ return urldecode(substr($string, 10));
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * Determine basename for compiled filename
+ * Always returns an empty string.
+ *
+ * @param Source $source source object
+ *
+ * @return string resource's basename
+ */
+ public function getBasename(Source $source) {
+ return '';
+ }
+
+ /*
+ * Disable timestamp checks for string resource.
+ *
+ * @return bool
+ */
+ /**
+ * @return bool
+ */
+ public function checkTimestamps() {
+ return false;
+ }
+}
diff --git a/src/Runtime/Block.php b/src/Runtime/Block.php
new file mode 100644
index 00000000..90eab9cb
--- /dev/null
+++ b/src/Runtime/Block.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Smarty\Runtime;
+
+/**
+ * Smarty {block} tag class
+ *
+
+
+ * @author Uwe Tews
+ */
+class Block
+{
+ /**
+ * Block name
+ *
+ * @var string
+ */
+ public $name = '';
+
+ /**
+ * Hide attribute
+ *
+ * @var bool
+ */
+ public $hide = false;
+
+ /**
+ * Append attribute
+ *
+ * @var bool
+ */
+ public $append = false;
+
+ /**
+ * prepend attribute
+ *
+ * @var bool
+ */
+ public $prepend = false;
+
+ /**
+ * Block calls $smarty.block.child
+ *
+ * @var bool
+ */
+ public $callsChild = false;
+
+ /**
+ * Inheritance child block
+ *
+ * @var Block|null
+ */
+ public $child = null;
+
+ /**
+ * Inheritance calling parent block
+ *
+ * @var Block|null
+ */
+ public $parent = null;
+
+ /**
+ * Inheritance Template index
+ *
+ * @var int
+ */
+ public $tplIndex = 0;
+
+ /**
+ * Block constructor.
+ * - if outer level {block} of child template ($state === 1) save it as child root block
+ * - otherwise process inheritance and render
+ *
+ * @param string $name block name
+ * @param int|null $tplIndex index of outer level {block} if nested
+ */
+ public function __construct($name, $tplIndex)
+ {
+ $this->name = $name;
+ $this->tplIndex = $tplIndex;
+ }
+
+ /**
+ * Compiled block code overloaded by {block} class
+ *
+ * @param \Smarty\Template $tpl
+ */
+ public function callBlock(\Smarty\Template $tpl)
+ {
+ }
+}
diff --git a/src/Runtime/CaptureRuntime.php b/src/Runtime/CaptureRuntime.php
new file mode 100644
index 00000000..3f37c59f
--- /dev/null
+++ b/src/Runtime/CaptureRuntime.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Smarty\Runtime;
+use Smarty\Template;
+
+/**
+ * Runtime Extension Capture
+ *
+
+
+ * @author Uwe Tews
+ */
+class CaptureRuntime {
+
+ /**
+ * Stack of capture parameter
+ *
+ * @var array
+ */
+ private $captureStack = [];
+
+ /**
+ * Current open capture sections
+ *
+ * @var int
+ */
+ private $captureCount = 0;
+
+ /**
+ * Count stack
+ *
+ * @var int[]
+ */
+ private $countStack = [];
+
+ /**
+ * Named buffer
+ *
+ * @var string[]
+ */
+ private $namedBuffer = [];
+
+ /**
+ * Open capture section
+ *
+ * @param \Smarty\Template $_template
+ * @param string $buffer capture name
+ * @param string $assign variable name
+ * @param string $append variable name
+ */
+ public function open(Template $_template, $buffer, $assign, $append) {
+
+ $this->registerCallbacks($_template);
+
+ $this->captureStack[] = [
+ $buffer,
+ $assign,
+ $append,
+ ];
+ $this->captureCount++;
+ ob_start();
+ }
+
+ /**
+ * Register callbacks in template class
+ *
+ * @param \Smarty\Template $_template
+ */
+ private function registerCallbacks(Template $_template) {
+
+ foreach ($_template->startRenderCallbacks as $callback) {
+ if (is_array($callback) && get_class($callback[0]) == self::class) {
+ // already registered
+ return;
+ }
+ }
+
+ $_template->startRenderCallbacks[] = [
+ $this,
+ 'startRender',
+ ];
+ $_template->endRenderCallbacks[] = [
+ $this,
+ 'endRender',
+ ];
+ $this->startRender($_template);
+ }
+
+ /**
+ * Start render callback
+ *
+ * @param \Smarty\Template $_template
+ */
+ public function startRender(Template $_template) {
+ $this->countStack[] = $this->captureCount;
+ $this->captureCount = 0;
+ }
+
+ /**
+ * Close capture section
+ *
+ * @param \Smarty\Template $_template
+ *
+ * @throws \Smarty\Exception
+ */
+ public function close(Template $_template) {
+ if ($this->captureCount) {
+ [$buffer, $assign, $append] = array_pop($this->captureStack);
+ $this->captureCount--;
+ if (isset($assign)) {
+ $_template->assign($assign, ob_get_contents());
+ }
+ if (isset($append)) {
+ $_template->append($append, ob_get_contents());
+ }
+ $this->namedBuffer[$buffer] = ob_get_clean();
+ } else {
+ $this->error($_template);
+ }
+ }
+
+ /**
+ * Error exception on not matching {capture}{/capture}
+ *
+ * @param \Smarty\Template $_template
+ *
+ * @throws \Smarty\Exception
+ */
+ public function error(Template $_template) {
+ throw new \Smarty\Exception("Not matching {capture}{/capture} in '{$_template->template_resource}'");
+ }
+
+ /**
+ * Return content of named capture buffer by key or as array
+ *
+ * @param \Smarty\Template $_template
+ * @param string|null $name
+ *
+ * @return string|string[]|null
+ */
+ public function getBuffer(Template $_template, $name = null) {
+ if (isset($name)) {
+ return $this->namedBuffer[$name] ?? null;
+ } else {
+ return $this->namedBuffer;
+ }
+ }
+
+ /**
+ * End render callback
+ *
+ * @param \Smarty\Template $_template
+ *
+ * @throws \Smarty\Exception
+ */
+ public function endRender(Template $_template) {
+ if ($this->captureCount) {
+ $this->error($_template);
+ } else {
+ $this->captureCount = array_pop($this->countStack);
+ }
+ }
+}
diff --git a/src/Runtime/DefaultPluginHandlerRuntime.php b/src/Runtime/DefaultPluginHandlerRuntime.php
new file mode 100644
index 00000000..ad6ed74d
--- /dev/null
+++ b/src/Runtime/DefaultPluginHandlerRuntime.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Smarty\Runtime;
+
+use Smarty\Exception;
+
+class DefaultPluginHandlerRuntime {
+
+ /**
+ * @var callable
+ */
+ private $defaultPluginHandler;
+
+ public function __construct(?callable $defaultPluginHandler = null) {
+ $this->defaultPluginHandler = $defaultPluginHandler;
+ }
+
+ public function hasPlugin($tag, $plugin_type): bool {
+ if ($this->defaultPluginHandler === null) {
+ return false;
+ }
+
+ $callback = null;
+
+ // these are not used here
+ $script = null;
+ $cacheable = null;
+
+ return (call_user_func_array(
+ $this->defaultPluginHandler,
+ [
+ $tag,
+ $plugin_type,
+ null, // This used to pass $this->template, but this parameter has been removed in 5.0
+ &$callback,
+ &$script,
+ &$cacheable,
+ ]
+ ) && $callback);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function getCallback($tag, $plugin_type) {
+
+ if ($this->defaultPluginHandler === null) {
+ return false;
+ }
+
+ $callback = null;
+
+ // these are not used here
+ $script = null;
+ $cacheable = null;
+
+ if (call_user_func_array(
+ $this->defaultPluginHandler,
+ [
+ $tag,
+ $plugin_type,
+ null, // This used to pass $this->template, but this parameter has been removed in 5.0
+ &$callback,
+ &$script,
+ &$cacheable,
+ ]
+ ) && $callback) {
+ return $callback;
+ }
+ throw new Exception("Default plugin handler: Returned callback for '{$tag}' not callable at runtime");
+ }
+
+} \ No newline at end of file
diff --git a/src/Runtime/ForeachRuntime.php b/src/Runtime/ForeachRuntime.php
new file mode 100644
index 00000000..fc311df0
--- /dev/null
+++ b/src/Runtime/ForeachRuntime.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Smarty\Runtime;
+use Smarty\Template;
+
+/**
+ * Foreach Runtime Methods count(), init(), restore()
+ *
+
+
+ * @author Uwe Tews
+ */
+class ForeachRuntime {
+
+ /**
+ * Stack of saved variables
+ *
+ * @var array
+ */
+ private $stack = [];
+
+ /**
+ * Init foreach loop
+ * - save item and key variables, named foreach property data if defined
+ * - init item and key variables, named foreach property data if required
+ * - count total if required
+ *
+ * @param \Smarty\Template $tpl
+ * @param mixed $from values to loop over
+ * @param string $item variable name
+ * @param bool $needTotal flag if we need to count values
+ * @param null|string $key variable name
+ * @param null|string $name of named foreach
+ * @param array $properties of named foreach
+ *
+ * @return mixed $from
+ */
+ public function init(
+ Template $tpl,
+ $from,
+ $item,
+ $needTotal = false,
+ $key = null,
+ $name = null,
+ $properties = []
+ ) {
+ $needTotal = $needTotal || isset($properties['total']);
+ $saveVars = [];
+ $total = null;
+ if (!is_array($from)) {
+ if (is_object($from)) {
+ if ($needTotal) {
+ $total = $this->count($from);
+ }
+ } else {
+ settype($from, 'array');
+ }
+ }
+ if (!isset($total)) {
+ $total = empty($from) ? 0 : ($needTotal ? count($from) : 1);
+ }
+ if ($tpl->hasVariable($item)) {
+ $saveVars['item'] = [
+ $item,
+ $tpl->getVariable($item)->getValue(),
+ ];
+ }
+ $tpl->assign($item,null);
+ if ($total === 0) {
+ $from = null;
+ } else {
+ if ($key) {
+ if ($tpl->hasVariable($key)) {
+ $saveVars['key'] = [
+ $key,
+ clone $tpl->getVariable($key),
+ ];
+ }
+ $tpl->assign($key, null);
+ }
+ }
+ if ($needTotal) {
+ $tpl->getVariable($item)->total = $total;
+ }
+ if ($name) {
+ $namedVar = "__smarty_foreach_{$name}";
+ if ($tpl->hasVariable($namedVar)) {
+ $saveVars['named'] = [
+ $namedVar,
+ clone $tpl->getVariable($namedVar),
+ ];
+ }
+ $namedProp = [];
+ if (isset($properties['total'])) {
+ $namedProp['total'] = $total;
+ }
+ if (isset($properties['iteration'])) {
+ $namedProp['iteration'] = 0;
+ }
+ if (isset($properties['index'])) {
+ $namedProp['index'] = -1;
+ }
+ if (isset($properties['show'])) {
+ $namedProp['show'] = ($total > 0);
+ }
+ $tpl->assign($namedVar, $namedProp);
+ }
+ $this->stack[] = $saveVars;
+ return $from;
+ }
+
+ /**
+ * [util function] counts an array, arrayAccess/traversable or PDOStatement object
+ *
+ * @param mixed $value
+ *
+ * @return int the count for arrays and objects that implement countable, 1 for other objects that don't, and 0
+ * for empty elements
+ */
+ public function count($value) {
+ if ($value instanceof IteratorAggregate) {
+ // Note: getIterator() returns a Traversable, not an Iterator
+ // thus rewind() and valid() methods may not be present
+ return iterator_count($value->getIterator());
+ } elseif ($value instanceof Iterator) {
+ return $value instanceof Generator ? 1 : iterator_count($value);
+ } elseif ($value instanceof Countable) {
+ return count($value);
+ } elseif ($value instanceof PDOStatement) {
+ return $value->rowCount();
+ } elseif ($value instanceof Traversable) {
+ return iterator_count($value);
+ }
+ return count((array)$value);
+ }
+
+ /**
+ * Restore saved variables
+ *
+ * will be called by {break n} or {continue n} for the required number of levels
+ *
+ * @param \Smarty\Template $tpl
+ * @param int $levels number of levels
+ */
+ public function restore(Template $tpl, $levels = 1) {
+ while ($levels) {
+ $saveVars = array_pop($this->stack);
+ if (!empty($saveVars)) {
+ if (isset($saveVars['item'])) {
+ $tpl->getVariable($saveVars['item'][0])->setValue($saveVars['item'][1]);
+ }
+ if (isset($saveVars['key'])) {
+ $tpl->setVariable($saveVars['key'][0], $saveVars['key'][1]);
+ }
+ if (isset($saveVars['named'])) {
+ $tpl->setVariable($saveVars['named'][0], $saveVars['named'][1]);
+ }
+ }
+ $levels--;
+ }
+ }
+}
diff --git a/src/Runtime/InheritanceRuntime.php b/src/Runtime/InheritanceRuntime.php
new file mode 100644
index 00000000..ffd7aae6
--- /dev/null
+++ b/src/Runtime/InheritanceRuntime.php
@@ -0,0 +1,243 @@
+<?php
+
+namespace Smarty\Runtime;
+use Smarty\Template;
+use Smarty\Template\Source;
+use Smarty\Exception;
+
+/**
+ * Inheritance Runtime Methods processBlock, endChild, init
+ *
+
+
+ * @author Uwe Tews
+ **/
+class InheritanceRuntime {
+
+ /**
+ * State machine
+ * - 0 idle next extends will create a new inheritance tree
+ * - 1 processing child template
+ * - 2 wait for next inheritance template
+ * - 3 assume parent template, if child will loaded goto state 1
+ * a call to a sub template resets the state to 0
+ *
+ * @var int
+ */
+ private $state = 0;
+
+ /**
+ * Array of root child {block} objects
+ *
+ * @var \Smarty\Runtime\Block[]
+ */
+ private $childRoot = [];
+
+ /**
+ * inheritance template nesting level
+ *
+ * @var int
+ */
+ private $inheritanceLevel = 0;
+
+ /**
+ * inheritance template index
+ *
+ * @var int
+ */
+ private $tplIndex = -1;
+
+ /**
+ * Array of template source objects
+ *
+ * @var Source[]
+ */
+ private $sources = [];
+
+ /**
+ * Stack of source objects while executing block code
+ *
+ * @var Source[]
+ */
+ private $sourceStack = [];
+
+ /**
+ * Initialize inheritance
+ *
+ * @param \Smarty\Template $tpl template object of caller
+ * @param bool $initChild if true init for child template
+ * @param array $blockNames outer level block name
+ */
+ public function init(Template $tpl, $initChild, $blockNames = []) {
+ // if called while executing parent template it must be a sub-template with new inheritance root
+ if ($initChild && $this->state === 3 && (strpos($tpl->template_resource, 'extendsall') === false)) {
+ $tpl->setInheritance(clone $tpl->getSmarty()->getRuntime('Inheritance'));
+ $tpl->getInheritance()->init($tpl, $initChild, $blockNames);
+ return;
+ }
+ ++$this->tplIndex;
+ $this->sources[$this->tplIndex] = $tpl->getSource();
+ // start of child sub template(s)
+ if ($initChild) {
+ $this->state = 1;
+ if (!$this->inheritanceLevel) {
+ //grab any output of child templates
+ ob_start();
+ }
+ ++$this->inheritanceLevel;
+ }
+ // if state was waiting for parent change state to parent
+ if ($this->state === 2) {
+ $this->state = 3;
+ }
+ }
+
+ /**
+ * End of child template(s)
+ * - if outer level is reached flush output buffer and switch to wait for parent template state
+ *
+ * @param \Smarty\Template $tpl
+ * @param null|string $template optional name of inheritance parent template
+ *
+ * @throws \Exception
+ * @throws \Smarty\Exception
+ */
+ public function endChild(Template $tpl, $template = null, ?string $currentDir = null) {
+ --$this->inheritanceLevel;
+ if (!$this->inheritanceLevel) {
+ ob_end_clean();
+ $this->state = 2;
+ }
+ if (isset($template)) {
+ $tpl->renderSubTemplate(
+ $template,
+ $tpl->cache_id,
+ $tpl->compile_id,
+ $tpl->caching ? \Smarty\Template::CACHING_NOCACHE_CODE : 0,
+ $tpl->cache_lifetime,
+ [],
+ null,
+ $currentDir
+ );
+ }
+ }
+
+ /**
+ * \Smarty\Runtime\Block constructor.
+ * - if outer level {block} of child template ($state === 1) save it as child root block
+ * - otherwise process inheritance and render
+ *
+ * @param \Smarty\Template $tpl
+ * @param $className
+ * @param string $name
+ * @param int|null $tplIndex index of outer level {block} if nested
+ *
+ * @throws \Smarty\Exception
+ */
+ public function instanceBlock(Template $tpl, $className, $name, $tplIndex = null) {
+ $block = new $className($name, isset($tplIndex) ? $tplIndex : $this->tplIndex);
+ if (isset($this->childRoot[$name])) {
+ $block->child = $this->childRoot[$name];
+ }
+ if ($this->state === 1) {
+ $this->childRoot[$name] = $block;
+ return;
+ }
+ // make sure we got child block of child template of current block
+ while ($block->child && $block->child->child && $block->tplIndex <= $block->child->tplIndex) {
+ $block->child = $block->child->child;
+ }
+ $this->processBlock($tpl, $block);
+ }
+
+ /**
+ * Goto child block or render this
+ *
+ * @param Template $tpl
+ * @param \Smarty\Runtime\Block $block
+ * @param \Smarty\Runtime\Block|null $parent
+ *
+ * @throws Exception
+ */
+ private function processBlock(
+ Template $tpl,
+ \Smarty\Runtime\Block $block,
+ \Smarty\Runtime\Block $parent = null
+ ) {
+ if ($block->hide && !isset($block->child)) {
+ return;
+ }
+ if (isset($block->child) && $block->child->hide && !isset($block->child->child)) {
+ $block->child = null;
+ }
+ $block->parent = $parent;
+ if ($block->append && !$block->prepend && isset($parent)) {
+ $this->callParent($tpl, $block, '\'{block append}\'');
+ }
+ if ($block->callsChild || !isset($block->child) || ($block->child->hide && !isset($block->child->child))) {
+ $this->callBlock($block, $tpl);
+ } else {
+ $this->processBlock($tpl, $block->child, $block);
+ }
+ if ($block->prepend && isset($parent)) {
+ $this->callParent($tpl, $block, '{block prepend}');
+ if ($block->append) {
+ if ($block->callsChild || !isset($block->child)
+ || ($block->child->hide && !isset($block->child->child))
+ ) {
+ $this->callBlock($block, $tpl);
+ } else {
+ $this->processBlock($tpl, $block->child, $block);
+ }
+ }
+ }
+ $block->parent = null;
+ }
+
+ /**
+ * Render child on \$smarty.block.child
+ *
+ * @param Template $tpl
+ * @param \Smarty\Runtime\Block $block
+ *
+ * @return null|string block content
+ * @throws Exception
+ */
+ public function callChild(Template $tpl, \Smarty\Runtime\Block $block) {
+ if (isset($block->child)) {
+ $this->processBlock($tpl, $block->child, $block);
+ }
+ }
+
+ /**
+ * Render parent block on \$smarty.block.parent or {block append/prepend}
+ *
+ * @param Template $tpl
+ * @param \Smarty\Runtime\Block $block
+ * @param string $tag
+ *
+ * @return null|string block content
+ * @throws Exception
+ */
+ public function callParent(Template $tpl, \Smarty\Runtime\Block $block) {
+ if (isset($block->parent)) {
+ $this->callBlock($block->parent, $tpl);
+ } else {
+ throw new Exception("inheritance: illegal '{\$smarty.block.parent}' used in child template '" .
+ "{$tpl->getInheritance()->sources[$block->tplIndex]->getResourceName()}' block '{$block->name}'");
+ }
+ }
+
+ /**
+ * render block
+ *
+ * @param \Smarty\Runtime\Block $block
+ * @param Template $tpl
+ */
+ public function callBlock(\Smarty\Runtime\Block $block, Template $tpl) {
+ $this->sourceStack[] = $tpl->getSource();
+ $tpl->setSource($this->sources[$block->tplIndex]);
+ $block->callBlock($tpl);
+ $tpl->setSource(array_pop($this->sourceStack));
+ }
+}
diff --git a/src/Runtime/TplFunctionRuntime.php b/src/Runtime/TplFunctionRuntime.php
new file mode 100644
index 00000000..905defb8
--- /dev/null
+++ b/src/Runtime/TplFunctionRuntime.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace Smarty\Runtime;
+use Smarty\Exception;
+use Smarty\Template;
+use Smarty\TemplateBase;
+
+/**
+ * TplFunction Runtime Methods callTemplateFunction
+ *
+
+
+ * @author Uwe Tews
+ **/
+class TplFunctionRuntime {
+
+ /**
+ * Call template function
+ *
+ * @param \Smarty\Template $tpl template object
+ * @param string $name template function name
+ * @param array $params parameter array
+ * @param bool $nocache true if called nocache
+ *
+ * @throws \Smarty\Exception
+ */
+ public function callTemplateFunction(Template $tpl, $name, $params, $nocache) {
+ $funcParam = $tpl->tplFunctions[$name] ?? ($tpl->getSmarty()->tplFunctions[$name] ?? null);
+ if (isset($funcParam)) {
+ if (!$tpl->caching || ($tpl->caching && $nocache)) {
+ $function = $funcParam['call_name'];
+ } else {
+ if (isset($funcParam['call_name_caching'])) {
+ $function = $funcParam['call_name_caching'];
+ } else {
+ $function = $funcParam['call_name'];
+ }
+ }
+ if (function_exists($function)) {
+ $this->saveTemplateVariables($tpl, $name);
+ $function($tpl, $params);
+ $this->restoreTemplateVariables($tpl, $name);
+ return;
+ }
+ // try to load template function dynamically
+ if ($this->addTplFuncToCache($tpl, $name, $function)) {
+ $this->saveTemplateVariables($tpl, $name);
+ $function($tpl, $params);
+ $this->restoreTemplateVariables($tpl, $name);
+ return;
+ }
+ }
+ throw new \Smarty\Exception("Unable to find template function '{$name}'");
+ }
+
+ /**
+ * Register template functions defined by template
+ *
+ * @param \Smarty|\Smarty\Template|\Smarty\TemplateBase $obj
+ * @param array $tplFunctions source information array of
+ * template functions defined
+ * in template
+ * @param bool $override if true replace existing
+ * functions with same name
+ */
+ public function registerTplFunctions(TemplateBase $obj, $tplFunctions, $override = true) {
+ $obj->tplFunctions =
+ $override ? array_merge($obj->tplFunctions, $tplFunctions) : array_merge($tplFunctions, $obj->tplFunctions);
+ // make sure that the template functions are known in parent templates
+ if ($obj->_isSubTpl()) {
+ $this->registerTplFunctions($obj->parent, $tplFunctions, false);
+ } else {
+ $obj->getSmarty()->tplFunctions = $override ? array_merge($obj->getSmarty()->tplFunctions, $tplFunctions) :
+ array_merge($tplFunctions, $obj->getSmarty()->tplFunctions);
+ }
+ }
+
+ /**
+ * Return source parameter array for single or all template functions
+ *
+ * @param \Smarty\Template $tpl template object
+ * @param null|string $name template function name
+ *
+ * @return array|bool|mixed
+ */
+ public function getTplFunction(Template $tpl, $name = null) {
+ if (isset($name)) {
+ return $tpl->tplFunctions[$name] ?? ($tpl->getSmarty()->tplFunctions[$name] ?? false);
+ } else {
+ return empty($tpl->tplFunctions) ? $tpl->getSmarty()->tplFunctions : $tpl->tplFunctions;
+ }
+ }
+
+ /**
+ * Add template function to cache file for nocache calls
+ *
+ * @param Template $tpl
+ * @param string $_name template function name
+ * @param string $_function PHP function name
+ *
+ * @return bool
+ * @throws Exception
+ */
+ private function addTplFuncToCache(Template $tpl, $_name, $_function) {
+ $funcParam = $tpl->tplFunctions[$_name];
+ if (is_file($funcParam['compiled_filepath'])) {
+ // read compiled file
+ $code = file_get_contents($funcParam['compiled_filepath']);
+ // grab template function
+ if (preg_match("/\/\* {$_function} \*\/([\S\s]*?)\/\*\/ {$_function} \*\//", $code, $match)) {
+ // grab source info from file dependency
+ preg_match("/\s*'{$funcParam['uid']}'([\S\s]*?)\),/", $code, $match1);
+ unset($code);
+ // make PHP function known
+ eval($match[0]);
+ if (function_exists($_function)) {
+
+ // Some magic code existed here, testing if the cached property had been set
+ // and then bubbling up until it found a parent template that had the cached property.
+ // This is no longer possible, so somehow this might break.
+
+ // add template function code to cache file
+ $content = $tpl->getCached()->readCache($tpl);
+ if ($content) {
+ // check if we must update file dependency
+ if (!preg_match("/'{$funcParam['uid']}'(.*?)'nocache_hash'/", $content, $match2)) {
+ $content = preg_replace("/('file_dependency'(.*?)\()/", "\\1{$match1[0]}", $content);
+ }
+ $tpl->getCached()->writeCache(
+ $tpl,
+ preg_replace('/\s*\?>\s*$/', "\n", $content) .
+ "\n" . preg_replace(
+ [
+ '/^\s*<\?php\s+/',
+ '/\s*\?>\s*$/',
+ ],
+ "\n",
+ $match[0]
+ )
+ );
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Save current template variables on stack
+ *
+ * @param \Smarty\Template $tpl
+ * @param string $name stack name
+ */
+ public function saveTemplateVariables(Template $tpl, $name) {
+ $tpl->_var_stack[] =
+ ['tpl' => $tpl->tpl_vars, 'config' => $tpl->config_vars, 'name' => "_tplFunction_{$name}"];
+ }
+
+ /**
+ * Restore saved variables into template objects
+ *
+ * @param \Smarty\Template $tpl
+ * @param string $name stack name
+ */
+ public function restoreTemplateVariables(Template $tpl, $name) {
+ if (isset($tpl->_var_stack)) {
+ $vars = array_pop($tpl->_var_stack);
+ $tpl->tpl_vars = $vars['tpl'];
+ $tpl->config_vars = $vars['config'];
+ }
+ }
+}
diff --git a/src/Security.php b/src/Security.php
new file mode 100644
index 00000000..46cddcee
--- /dev/null
+++ b/src/Security.php
@@ -0,0 +1,558 @@
+<?php
+/**
+ * Smarty plugin
+ *
+
+
+ * @author Uwe Tews
+ */
+
+/**
+ * FIXME: \Smarty\Security API
+ * - getter and setter instead of public properties would allow cultivating an internal cache properly
+ * - current implementation of isTrustedResourceDir() assumes that Smarty::$template_dir and Smarty::$config_dir
+ * are immutable the cache is killed every time either of the variables change. That means that two distinct
+ * Smarty objects with differing
+ * $template_dir or $config_dir should NOT share the same \Smarty\Security instance,
+ * as this would lead to (severe) performance penalty! how should this be handled?
+ */
+
+namespace Smarty;
+
+use Smarty\Exception;
+
+/**
+ * This class does contain the security settings
+ */
+#[\AllowDynamicProperties]
+class Security {
+
+ /**
+ * This is the list of template directories that are considered secure.
+ * $template_dir is in this list implicitly.
+ *
+ * @var array
+ */
+ public $secure_dir = [];
+
+ /**
+ * List of regular expressions (PCRE) that include trusted URIs
+ *
+ * @var array
+ */
+ public $trusted_uri = [];
+
+ /**
+ * List of trusted constants names
+ *
+ * @var array
+ */
+ public $trusted_constants = [];
+
+ /**
+ * This is an array of trusted static classes.
+ * If empty access to all static classes is allowed.
+ * If set to 'none' none is allowed.
+ *
+ * @var array
+ */
+ public $static_classes = [];
+
+ /**
+ * This is an nested array of trusted classes and static methods.
+ * If empty access to all static classes and methods is allowed.
+ * Format:
+ * array (
+ * 'class_1' => array('method_1', 'method_2'), // allowed methods listed
+ * 'class_2' => array(), // all methods of class allowed
+ * )
+ * If set to null none is allowed.
+ *
+ * @var array
+ */
+ public $trusted_static_methods = [];
+
+ /**
+ * This is an array of trusted static properties.
+ * If empty access to all static classes and properties is allowed.
+ * Format:
+ * array (
+ * 'class_1' => array('prop_1', 'prop_2'), // allowed properties listed
+ * 'class_2' => array(), // all properties of class allowed
+ * )
+ * If set to null none is allowed.
+ *
+ * @var array
+ */
+ public $trusted_static_properties = [];
+
+ /**
+ * This is an array of allowed tags.
+ * If empty no restriction by allowed_tags.
+ *
+ * @var array
+ */
+ public $allowed_tags = [];
+
+ /**
+ * This is an array of disabled tags.
+ * If empty no restriction by disabled_tags.
+ *
+ * @var array
+ */
+ public $disabled_tags = [];
+
+ /**
+ * This is an array of allowed modifier plugins.
+ * If empty no restriction by allowed_modifiers.
+ *
+ * @var array
+ */
+ public $allowed_modifiers = [];
+
+ /**
+ * This is an array of disabled modifier plugins.
+ * If empty no restriction by disabled_modifiers.
+ *
+ * @var array
+ */
+ public $disabled_modifiers = [];
+
+ /**
+ * This is an array of disabled special $smarty variables.
+ *
+ * @var array
+ */
+ public $disabled_special_smarty_vars = [];
+
+ /**
+ * This is an array of trusted streams.
+ * If empty all streams are allowed.
+ * To disable all streams set $streams = null.
+ *
+ * @var array
+ */
+ public $streams = ['file'];
+
+ /**
+ * + flag if constants can be accessed from template
+ *
+ * @var boolean
+ */
+ public $allow_constants = true;
+
+ /**
+ * + flag if super globals can be accessed from template
+ *
+ * @var boolean
+ */
+ public $allow_super_globals = true;
+
+ /**
+ * max template nesting level
+ *
+ * @var int
+ */
+ public $max_template_nesting = 0;
+
+ /**
+ * current template nesting level
+ *
+ * @var int
+ */
+ private $_current_template_nesting = 0;
+
+ /**
+ * Cache for $resource_dir lookup
+ *
+ * @var array
+ */
+ protected $_resource_dir = [];
+
+ /**
+ * Cache for $template_dir lookup
+ *
+ * @var array
+ */
+ protected $_template_dir = [];
+
+ /**
+ * Cache for $config_dir lookup
+ *
+ * @var array
+ */
+ protected $_config_dir = [];
+
+ /**
+ * Cache for $secure_dir lookup
+ *
+ * @var array
+ */
+ protected $_secure_dir = [];
+
+ /**
+ * @param Smarty $smarty
+ */
+ public function __construct(Smarty $smarty) {
+ $this->smarty = $smarty;
+ }
+
+ /**
+ * Check if static class is trusted.
+ *
+ * @param string $class_name
+ * @param object $compiler compiler object
+ *
+ * @return boolean true if class is trusted
+ */
+ public function isTrustedStaticClass($class_name, $compiler) {
+ if (isset($this->static_classes)
+ && (empty($this->static_classes) || in_array($class_name, $this->static_classes))
+ ) {
+ return true;
+ }
+ $compiler->trigger_template_error("access to static class '{$class_name}' not allowed by security setting");
+ return false; // should not, but who knows what happens to the compiler in the future?
+ }
+
+ /**
+ * Check if static class method/property is trusted.
+ *
+ * @param string $class_name
+ * @param string $params
+ * @param object $compiler compiler object
+ *
+ * @return boolean true if class method is trusted
+ */
+ public function isTrustedStaticClassAccess($class_name, $params, $compiler) {
+ if (!isset($params[2])) {
+ // fall back
+ return $this->isTrustedStaticClass($class_name, $compiler);
+ }
+ if ($params[2] === 'method') {
+ $allowed = $this->trusted_static_methods;
+ $name = substr($params[0], 0, strpos($params[0], '('));
+ } else {
+ $allowed = $this->trusted_static_properties;
+ // strip '$'
+ $name = substr($params[0], 1);
+ }
+ if (isset($allowed)) {
+ if (empty($allowed)) {
+ // fall back
+ return $this->isTrustedStaticClass($class_name, $compiler);
+ }
+ if (isset($allowed[$class_name])
+ && (empty($allowed[$class_name]) || in_array($name, $allowed[$class_name]))
+ ) {
+ return true;
+ }
+ }
+ $compiler->trigger_template_error("access to static class '{$class_name}' {$params[2]} '{$name}' not allowed by security setting");
+ return false; // should not, but who knows what happens to the compiler in the future?
+ }
+
+ /**
+ * Check if tag is trusted.
+ *
+ * @param string $tag_name
+ * @param object $compiler compiler object
+ *
+ * @return boolean true if tag is trusted
+ */
+ public function isTrustedTag($tag_name, $compiler) {
+ // check for internal always required tags
+ if (in_array($tag_name, ['assign', 'call'])) {
+ return true;
+ }
+ // check security settings
+ if (empty($this->allowed_tags)) {
+ if (empty($this->disabled_tags) || !in_array($tag_name, $this->disabled_tags)) {
+ return true;
+ } else {
+ $compiler->trigger_template_error("tag '{$tag_name}' disabled by security setting", null, true);
+ }
+ } elseif (in_array($tag_name, $this->allowed_tags) && !in_array($tag_name, $this->disabled_tags)) {
+ return true;
+ } else {
+ $compiler->trigger_template_error("tag '{$tag_name}' not allowed by security setting", null, true);
+ }
+ return false; // should not, but who knows what happens to the compiler in the future?
+ }
+
+ /**
+ * Check if special $smarty variable is trusted.
+ *
+ * @param string $var_name
+ * @param object $compiler compiler object
+ *
+ * @return boolean true if tag is trusted
+ */
+ public function isTrustedSpecialSmartyVar($var_name, $compiler) {
+ if (!in_array($var_name, $this->disabled_special_smarty_vars)) {
+ return true;
+ } else {
+ $compiler->trigger_template_error(
+ "special variable '\$smarty.{$var_name}' not allowed by security setting",
+ null,
+ true
+ );
+ }
+ return false; // should not, but who knows what happens to the compiler in the future?
+ }
+
+ /**
+ * Check if modifier plugin is trusted.
+ *
+ * @param string $modifier_name
+ * @param object $compiler compiler object
+ *
+ * @return boolean true if tag is trusted
+ */
+ public function isTrustedModifier($modifier_name, $compiler) {
+ // check for internal always allowed modifier
+ if (in_array($modifier_name, ['default'])) {
+ return true;
+ }
+ // check security settings
+ if (empty($this->allowed_modifiers)) {
+ if (empty($this->disabled_modifiers) || !in_array($modifier_name, $this->disabled_modifiers)) {
+ return true;
+ } else {
+ $compiler->trigger_template_error(
+ "modifier '{$modifier_name}' disabled by security setting",
+ null,
+ true
+ );
+ }
+ } elseif (in_array($modifier_name, $this->allowed_modifiers)
+ && !in_array($modifier_name, $this->disabled_modifiers)
+ ) {
+ return true;
+ } else {
+ $compiler->trigger_template_error(
+ "modifier '{$modifier_name}' not allowed by security setting",
+ null,
+ true
+ );
+ }
+ return false; // should not, but who knows what happens to the compiler in the future?
+ }
+
+ /**
+ * Check if constants are enabled or trusted
+ *
+ * @param string $const constant name
+ * @param object $compiler compiler object
+ *
+ * @return bool
+ */
+ public function isTrustedConstant($const, $compiler) {
+ if (in_array($const, ['true', 'false', 'null'])) {
+ return true;
+ }
+ if (!empty($this->trusted_constants)) {
+ if (!in_array(strtolower($const), $this->trusted_constants)) {
+ $compiler->trigger_template_error("Security: access to constant '{$const}' not permitted");
+ return false;
+ }
+ return true;
+ }
+ if ($this->allow_constants) {
+ return true;
+ }
+ $compiler->trigger_template_error("Security: access to constants not permitted");
+ return false;
+ }
+
+ /**
+ * Check if stream is trusted.
+ *
+ * @param string $stream_name
+ *
+ * @return boolean true if stream is trusted
+ * @throws Exception if stream is not trusted
+ */
+ public function isTrustedStream($stream_name) {
+ if (isset($this->streams) && (empty($this->streams) || in_array($stream_name, $this->streams))) {
+ return true;
+ }
+ throw new Exception("stream '{$stream_name}' not allowed by security setting");
+ }
+
+ /**
+ * Check if directory of file resource is trusted.
+ *
+ * @param string $filepath
+ * @param null|bool $isConfig
+ *
+ * @return bool true if directory is trusted
+ * @throws \Smarty\Exception if directory is not trusted
+ */
+ public function isTrustedResourceDir($filepath, $isConfig = null) {
+ $_dir = $this->smarty->getTemplateDir();
+ if ($this->_template_dir !== $_dir) {
+ $this->_updateResourceDir($this->_template_dir, $_dir);
+ $this->_template_dir = $_dir;
+ }
+ $_dir = $this->smarty->getConfigDir();
+ if ($this->_config_dir !== $_dir) {
+ $this->_updateResourceDir($this->_config_dir, $_dir);
+ $this->_config_dir = $_dir;
+ }
+ if ($this->_secure_dir !== $this->secure_dir) {
+ $this->secure_dir = (array)$this->secure_dir;
+ foreach ($this->secure_dir as $k => $d) {
+ $this->secure_dir[$k] = $this->smarty->_realpath($d . DIRECTORY_SEPARATOR, true);
+ }
+ $this->_updateResourceDir($this->_secure_dir, $this->secure_dir);
+ $this->_secure_dir = $this->secure_dir;
+ }
+ $addPath = $this->_checkDir($filepath, $this->_resource_dir);
+ if ($addPath !== false) {
+ $this->_resource_dir = array_merge($this->_resource_dir, $addPath);
+ }
+ return true;
+ }
+
+ /**
+ * Check if URI (e.g. {fetch} or {html_image}) is trusted
+ * To simplify things, isTrustedUri() resolves all input to "{$PROTOCOL}://{$HOSTNAME}".
+ * So "http://username:password@hello.world.example.org:8080/some-path?some=query-string"
+ * is reduced to "http://hello.world.example.org" prior to applying the patters from {@link $trusted_uri}.
+ *
+ * @param string $uri
+ *
+ * @return boolean true if URI is trusted
+ * @throws Exception if URI is not trusted
+ * @uses $trusted_uri for list of patterns to match against $uri
+ */
+ public function isTrustedUri($uri) {
+ $_uri = parse_url($uri);
+ if (!empty($_uri['scheme']) && !empty($_uri['host'])) {
+ $_uri = $_uri['scheme'] . '://' . $_uri['host'];
+ foreach ($this->trusted_uri as $pattern) {
+ if (preg_match($pattern, $_uri)) {
+ return true;
+ }
+ }
+ }
+ throw new Exception("URI '{$uri}' not allowed by security setting");
+ }
+
+ /**
+ * Remove old directories and its sub folders, add new directories
+ *
+ * @param array $oldDir
+ * @param array $newDir
+ */
+ private function _updateResourceDir($oldDir, $newDir) {
+ foreach ($oldDir as $directory) {
+ // $directory = $this->smarty->_realpath($directory, true);
+ $length = strlen($directory);
+ foreach ($this->_resource_dir as $dir) {
+ if (substr($dir, 0, $length) === $directory) {
+ unset($this->_resource_dir[$dir]);
+ }
+ }
+ }
+ foreach ($newDir as $directory) {
+ // $directory = $this->smarty->_realpath($directory, true);
+ $this->_resource_dir[$directory] = true;
+ }
+ }
+
+ /**
+ * Check if file is inside a valid directory
+ *
+ * @param string $filepath
+ * @param array $dirs valid directories
+ *
+ * @return array|bool
+ * @throws \Smarty\Exception
+ */
+ private function _checkDir($filepath, $dirs) {
+ $directory = dirname($this->smarty->_realpath($filepath, true)) . DIRECTORY_SEPARATOR;
+ $_directory = [];
+ if (!preg_match('#[\\\\/][.][.][\\\\/]#', $directory)) {
+ while (true) {
+ // test if the directory is trusted
+ if (isset($dirs[$directory])) {
+ return $_directory;
+ }
+ // abort if we've reached root
+ if (!preg_match('#[\\\\/][^\\\\/]+[\\\\/]$#', $directory)) {
+ // give up
+ break;
+ }
+ // remember the directory to add it to _resource_dir in case we're successful
+ $_directory[$directory] = true;
+ // bubble up one level
+ $directory = preg_replace('#[\\\\/][^\\\\/]+[\\\\/]$#', DIRECTORY_SEPARATOR, $directory);
+ }
+ }
+ // give up
+ throw new Exception(sprintf('Smarty Security: not trusted file path \'%s\' ', $filepath));
+ }
+
+ /**
+ * Loads security class and enables security
+ *
+ * @param \Smarty $smarty
+ * @param string|Security $security_class if a string is used, it must be class-name
+ *
+ * @return \Smarty current Smarty instance for chaining
+ * @throws \Smarty\Exception when an invalid class name is provided
+ */
+ public static function enableSecurity(Smarty $smarty, $security_class) {
+ if ($security_class instanceof Security) {
+ $smarty->security_policy = $security_class;
+ return $smarty;
+ } elseif (is_object($security_class)) {
+ throw new Exception("Class '" . get_class($security_class) . "' must extend \\Smarty\\Security.");
+ }
+ if ($security_class === null) {
+ $security_class = $smarty->security_class;
+ }
+ if (!class_exists($security_class)) {
+ throw new Exception("Security class '$security_class' is not defined");
+ } elseif ($security_class !== Security::class && !is_subclass_of($security_class, Security::class)) {
+ throw new Exception("Class '$security_class' must extend " . Security::class . ".");
+ } else {
+ $smarty->security_policy = new $security_class($smarty);
+ }
+ return $smarty;
+ }
+
+ /**
+ * Start template processing
+ *
+ * @param $template
+ *
+ * @throws Exception
+ */
+ public function startTemplate($template) {
+ if ($this->max_template_nesting > 0 && $this->_current_template_nesting++ >= $this->max_template_nesting) {
+ throw new Exception("maximum template nesting level of '{$this->max_template_nesting}' exceeded when calling '{$template->template_resource}'");
+ }
+ }
+
+ /**
+ * Exit template processing
+ */
+ public function endTemplate() {
+ if ($this->max_template_nesting > 0) {
+ $this->_current_template_nesting--;
+ }
+ }
+
+ /**
+ * Register callback functions call at start/end of template rendering
+ *
+ * @param \Smarty\Template $template
+ */
+ public function registerCallBacks(Template $template) {
+ $template->startRenderCallbacks[] = [$this, 'startTemplate'];
+ $template->endRenderCallbacks[] = [$this, 'endTemplate'];
+ }
+}
diff --git a/src/Smarty.php b/src/Smarty.php
new file mode 100644
index 00000000..08d580bf
--- /dev/null
+++ b/src/Smarty.php
@@ -0,0 +1,2236 @@
+<?php
+
+namespace Smarty;
+
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use Smarty\Cacheresource\File;
+use Smarty\Extension\Base;
+use Smarty\Extension\BCPluginsAdapter;
+use Smarty\Extension\CallbackWrapper;
+use Smarty\Extension\CoreExtension;
+use Smarty\Extension\DefaultExtension;
+use Smarty\Extension\ExtensionInterface;
+use Smarty\Filter\Output\TrimWhitespace;
+use Smarty\Resource\BasePlugin;
+use Smarty\Smarty\Runtime\CaptureRuntime;
+use Smarty\Smarty\Runtime\ForeachRuntime;
+use Smarty\Smarty\Runtime\InheritanceRuntime;
+use Smarty\Smarty\Runtime\TplFunctionRuntime;
+
+/**
+ * Project: Smarty: the PHP compiling template engine
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * For questions, help, comments, discussion, etc., please join the
+ * Smarty mailing list. Send a blank e-mail to
+ * smarty-discussion-subscribe@googlegroups.com
+ *
+ * @link https://www.smarty.net/
+ * @author Monte Ohrt <monte at ohrt dot com>
+ * @author Uwe Tews <uwe dot tews at gmail dot com>
+ * @author Rodney Rehm
+ * @author Simon Wisselink
+ */
+
+/**
+ * This is the main Smarty class
+ */
+class Smarty extends \Smarty\TemplateBase {
+
+ /**
+ * smarty version
+ */
+ const SMARTY_VERSION = '5.0.0';
+
+ /**
+ * define caching modes
+ */
+ const CACHING_OFF = 0;
+ const CACHING_LIFETIME_CURRENT = 1;
+ const CACHING_LIFETIME_SAVED = 2;
+ /**
+ * define constant for clearing cache files be saved expiration dates
+ */
+ const CLEAR_EXPIRED = -1;
+ /**
+ * define compile check modes
+ */
+ const COMPILECHECK_OFF = 0;
+ const COMPILECHECK_ON = 1;
+ /**
+ * filter types
+ */
+ const FILTER_POST = 'post';
+ const FILTER_PRE = 'pre';
+ const FILTER_OUTPUT = 'output';
+ const FILTER_VARIABLE = 'variable';
+ /**
+ * plugin types
+ */
+ const PLUGIN_FUNCTION = 'function';
+ const PLUGIN_BLOCK = 'block';
+ const PLUGIN_COMPILER = 'compiler';
+ const PLUGIN_MODIFIER = 'modifier';
+ const PLUGIN_MODIFIERCOMPILER = 'modifiercompiler';
+
+ /**
+ * The character set to adhere to (defaults to "UTF-8")
+ */
+ public static $_CHARSET = 'UTF-8';
+
+ /**
+ * The date format to be used internally
+ * (accepts date() and strftime())
+ */
+ public static $_DATE_FORMAT = '%b %e, %Y';
+
+ /**
+ * Flag denoting if PCRE should run in UTF-8 mode
+ */
+ public static $_UTF8_MODIFIER = 'u';
+
+ /**
+ * Flag denoting if operating system is windows
+ */
+ public static $_IS_WINDOWS = false;
+
+ /**
+ * auto literal on delimiters with whitespace
+ *
+ * @var boolean
+ */
+ public $auto_literal = true;
+
+ /**
+ * display error on not assigned variables
+ *
+ * @var boolean
+ */
+ public $error_unassigned = false;
+
+ /**
+ * flag if template_dir is normalized
+ *
+ * @var bool
+ */
+ public $_templateDirNormalized = false;
+
+ /**
+ * joined template directory string used in cache keys
+ *
+ * @var string
+ */
+ public $_joined_template_dir = null;
+
+ /**
+ * flag if config_dir is normalized
+ *
+ * @var bool
+ */
+ public $_configDirNormalized = false;
+
+ /**
+ * joined config directory string used in cache keys
+ *
+ * @var string
+ */
+ public $_joined_config_dir = null;
+
+ /**
+ * default template handler
+ *
+ * @var callable
+ */
+ public $default_template_handler_func = null;
+
+ /**
+ * default config handler
+ *
+ * @var callable
+ */
+ public $default_config_handler_func = null;
+
+ /**
+ * default plugin handler
+ *
+ * @var callable
+ */
+ private $default_plugin_handler_func = null;
+
+ /**
+ * flag if template_dir is normalized
+ *
+ * @var bool
+ */
+ public $_compileDirNormalized = false;
+
+ /**
+ * flag if template_dir is normalized
+ *
+ * @var bool
+ */
+ public $_cacheDirNormalized = false;
+
+ /**
+ * force template compiling?
+ *
+ * @var boolean
+ */
+ public $force_compile = false;
+
+ /**
+ * use sub dirs for compiled/cached files?
+ *
+ * @var boolean
+ */
+ public $use_sub_dirs = false;
+
+ /**
+ * merge compiled includes
+ *
+ * @var boolean
+ */
+ public $merge_compiled_includes = false;
+
+ /**
+ * force cache file creation
+ *
+ * @var boolean
+ */
+ public $force_cache = false;
+
+ /**
+ * template left-delimiter
+ *
+ * @var string
+ */
+ private $left_delimiter = "{";
+
+ /**
+ * template right-delimiter
+ *
+ * @var string
+ */
+ private $right_delimiter = "}";
+
+ /**
+ * array of strings which shall be treated as literal by compiler
+ *
+ * @var array string
+ */
+ public $literals = [];
+
+ /**
+ * class name
+ * This should be instance of \Smarty\Security.
+ *
+ * @var string
+ * @see \Smarty\Security
+ */
+ public $security_class = \Smarty\Security::class;
+
+ /**
+ * implementation of security class
+ *
+ * @var \Smarty\Security
+ */
+ public $security_policy = null;
+
+ /**
+ * debug mode
+ * Setting this to true enables the debug-console.
+ *
+ * @var boolean
+ */
+ public $debugging = false;
+
+ /**
+ * This determines if debugging is enable-able from the browser.
+ * <ul>
+ * <li>NONE => no debugging control allowed</li>
+ * <li>URL => enable debugging when SMARTY_DEBUG is found in the URL.</li>
+ * </ul>
+ *
+ * @var string
+ */
+ public $debugging_ctrl = 'NONE';
+
+ /**
+ * Name of debugging URL-param.
+ * Only used when $debugging_ctrl is set to 'URL'.
+ * The name of the URL-parameter that activates debugging.
+ *
+ * @var string
+ */
+ public $smarty_debug_id = 'SMARTY_DEBUG';
+
+ /**
+ * Path of debug template.
+ *
+ * @var string
+ */
+ public $debug_tpl = null;
+
+ /**
+ * When set, smarty uses this value as error_reporting-level.
+ *
+ * @var int
+ */
+ public $error_reporting = null;
+
+ /**
+ * Controls whether variables with the same name overwrite each other.
+ *
+ * @var boolean
+ */
+ public $config_overwrite = true;
+
+ /**
+ * Controls whether config values of on/true/yes and off/false/no get converted to boolean.
+ *
+ * @var boolean
+ */
+ public $config_booleanize = true;
+
+ /**
+ * Controls whether hidden config sections/vars are read from the file.
+ *
+ * @var boolean
+ */
+ public $config_read_hidden = false;
+
+ /**
+ * locking concurrent compiles
+ *
+ * @var boolean
+ */
+ public $compile_locking = true;
+
+ /**
+ * Controls whether cache resources should use locking mechanism
+ *
+ * @var boolean
+ */
+ public $cache_locking = false;
+
+ /**
+ * seconds to wait for acquiring a lock before ignoring the write lock
+ *
+ * @var float
+ */
+ public $locking_timeout = 10;
+
+ /**
+ * resource type used if none given
+ * Must be a valid key of $registered_resources.
+ *
+ * @var string
+ */
+ public $default_resource_type = 'file';
+
+ /**
+ * cache resource
+ * Must be a subclass of \Smarty\Cacheresource\Base
+ *
+ * @var \Smarty\Cacheresource\Base
+ */
+ private $cacheResource;
+
+ /**
+ * config type
+ *
+ * @var string
+ */
+ public $default_config_type = 'file';
+
+ /**
+ * check If-Modified-Since headers
+ *
+ * @var boolean
+ */
+ public $cache_modified_check = false;
+
+ /**
+ * registered plugins
+ *
+ * @var array
+ */
+ public $registered_plugins = [];
+
+ /**
+ * registered objects
+ *
+ * @var array
+ */
+ public $registered_objects = [];
+
+ /**
+ * registered classes
+ *
+ * @var array
+ */
+ public $registered_classes = [];
+
+ /**
+ * registered filters
+ *
+ * @var array
+ */
+ public $registered_filters = [];
+
+ /**
+ * registered resources
+ *
+ * @var array
+ */
+ public $registered_resources = [];
+
+ /**
+ * registered cache resources
+ *
+ * @var array
+ * @deprecated since 5.0
+ */
+ private $registered_cache_resources = [];
+
+ /**
+ * default modifier
+ *
+ * @var array
+ */
+ public $default_modifiers = [];
+
+ /**
+ * autoescape variable output
+ *
+ * @var boolean
+ */
+ public $escape_html = false;
+
+ /**
+ * start time for execution time calculation
+ *
+ * @var int
+ */
+ public $start_time = 0;
+
+ /**
+ * internal flag to enable parser debugging
+ *
+ * @var bool
+ */
+ public $_parserdebug = false;
+
+ /**
+ * Debug object
+ *
+ * @var \Smarty\Debug
+ */
+ public $_debug = null;
+
+ /**
+ * template directory
+ *
+ * @var array
+ */
+ protected $template_dir = ['./templates/'];
+
+ /**
+ * flags for normalized template directory entries
+ *
+ * @var array
+ */
+ protected $_processedTemplateDir = [];
+
+ /**
+ * config directory
+ *
+ * @var array
+ */
+ protected $config_dir = ['./configs/'];
+
+ /**
+ * flags for normalized template directory entries
+ *
+ * @var array
+ */
+ protected $_processedConfigDir = [];
+
+ /**
+ * compile directory
+ *
+ * @var string
+ */
+ protected $compile_dir = './templates_c/';
+
+ /**
+ * cache directory
+ *
+ * @var string
+ */
+ protected $cache_dir = './cache/';
+
+ /**
+ * PHP7 Compatibility mode
+ *
+ * @var bool
+ */
+ private $isMutingUndefinedOrNullWarnings = false;
+
+ /**
+ * Cache of loaded resource handlers.
+ *
+ * @var array
+ */
+ public $_resource_handlers = [];
+
+ /**
+ * Cache of loaded cacheresource handlers.
+ *
+ * @var array
+ */
+ public $_cacheresource_handlers = [];
+
+ /**
+ * List of extensions
+ *
+ * @var ExtensionInterface[]
+ */
+ private $extensions = [];
+ /**
+ * @var BCPluginsAdapter
+ */
+ private $BCPluginsAdapter;
+
+ /**
+ * Initialize new Smarty object
+ */
+ public function __construct() {
+
+ $this->start_time = microtime(true);
+ // Check if we're running on Windows
+ \Smarty\Smarty::$_IS_WINDOWS = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
+ // let PCRE (preg_*) treat strings as ISO-8859-1 if we're not dealing with UTF-8
+ if (\Smarty\Smarty::$_CHARSET !== 'UTF-8') {
+ \Smarty\Smarty::$_UTF8_MODIFIER = '';
+ }
+
+ $this->BCPluginsAdapter = new BCPluginsAdapter($this);
+
+ $this->extensions[] = new CoreExtension();
+ $this->extensions[] = new DefaultExtension();
+ $this->extensions[] = $this->BCPluginsAdapter;
+
+ $this->cacheResource = new File();
+ }
+
+ /**
+ * Load an additional extension.
+ *
+ * @param Base $extension
+ *
+ * @return void
+ */
+ public function addExtension(ExtensionInterface $extension) {
+ $this->extensions[] = $extension;
+ }
+
+ /**
+ * Returns all loaded extensions
+ *
+ * @return array|ExtensionInterface[]
+ */
+ public function getExtensions(): array {
+ return $this->extensions;
+ }
+
+ /**
+ * Replace the entire list extensions, allowing you to determine the exact order of the extensions.
+ *
+ * @param ExtensionInterface[] $extensions
+ *
+ * @return void
+ */
+ public function setExtensions(array $extensions): void {
+ $this->extensions = $extensions;
+ }
+
+ /**
+ * Check if a template resource exists
+ *
+ * @param string $resource_name template name
+ *
+ * @return bool status
+ * @throws \Smarty\Exception
+ */
+ public function templateExists($resource_name) {
+ // create source object
+ $source = Template\Source::load(null, $this, $resource_name);
+ return $source->exists;
+ }
+
+ /**
+ * Loads security class and enables security
+ *
+ * @param string|\Smarty\Security $security_class if a string is used, it must be class-name
+ *
+ * @return Smarty current Smarty instance for chaining
+ * @throws \Smarty\Exception
+ */
+ public function enableSecurity($security_class = null) {
+ \Smarty\Security::enableSecurity($this, $security_class);
+ return $this;
+ }
+
+ /**
+ * Disable security
+ *
+ * @return Smarty current Smarty instance for chaining
+ */
+ public function disableSecurity() {
+ $this->security_policy = null;
+ return $this;
+ }
+
+ /**
+ * Add template directory(s)
+ *
+ * @param string|array $template_dir directory(s) of template sources
+ * @param string $key of the array element to assign the template dir to
+ * @param bool $isConfig true for config_dir
+ *
+ * @return Smarty current Smarty instance for chaining
+ */
+ public function addTemplateDir($template_dir, $key = null, $isConfig = false) {
+ if ($isConfig) {
+ $processed = &$this->_processedConfigDir;
+ $dir = &$this->config_dir;
+ $this->_configDirNormalized = false;
+ } else {
+ $processed = &$this->_processedTemplateDir;
+ $dir = &$this->template_dir;
+ $this->_templateDirNormalized = false;
+ }
+ if (is_array($template_dir)) {
+ foreach ($template_dir as $k => $v) {
+ if (is_int($k)) {
+ // indexes are not merged but appended
+ $dir[] = $v;
+ } else {
+ // string indexes are overridden
+ $dir[$k] = $v;
+ unset($processed[$key]);
+ }
+ }
+ } else {
+ if ($key !== null) {
+ // override directory at specified index
+ $dir[$key] = $template_dir;
+ unset($processed[$key]);
+ } else {
+ // append new directory
+ $dir[] = $template_dir;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Get template directories
+ *
+ * @param mixed $index index of directory to get, null to get all
+ * @param bool $isConfig true for config_dir
+ *
+ * @return array|string list of template directories, or directory of $index
+ */
+ public function getTemplateDir($index = null, $isConfig = false) {
+ if ($isConfig) {
+ $dir = &$this->config_dir;
+ } else {
+ $dir = &$this->template_dir;
+ }
+ if ($isConfig ? !$this->_configDirNormalized : !$this->_templateDirNormalized) {
+ $this->_normalizeTemplateConfig($isConfig);
+ }
+ if ($index !== null) {
+ return isset($dir[$index]) ? $dir[$index] : null;
+ }
+ return $dir;
+ }
+
+ /**
+ * Set template directory
+ *
+ * @param string|array $template_dir directory(s) of template sources
+ * @param bool $isConfig true for config_dir
+ *
+ * @return Smarty current Smarty instance for chaining
+ */
+ public function setTemplateDir($template_dir, $isConfig = false) {
+ if ($isConfig) {
+ $this->config_dir = [];
+ $this->_processedConfigDir = [];
+ } else {
+ $this->template_dir = [];
+ $this->_processedTemplateDir = [];
+ }
+ $this->addTemplateDir($template_dir, null, $isConfig);
+ return $this;
+ }
+
+ /**
+ * Add config directory(s)
+ *
+ * @param string|array $config_dir directory(s) of config sources
+ * @param mixed $key key of the array element to assign the config dir to
+ *
+ * @return Smarty current Smarty instance for chaining
+ */
+ public function addConfigDir($config_dir, $key = null) {
+ return $this->addTemplateDir($config_dir, $key, true);
+ }
+
+ /**
+ * Get config directory
+ *
+ * @param mixed $index index of directory to get, null to get all
+ *
+ * @return array configuration directory
+ */
+ public function getConfigDir($index = null) {
+ return $this->getTemplateDir($index, true);
+ }
+
+ /**
+ * Set config directory
+ *
+ * @param $config_dir
+ *
+ * @return Smarty current Smarty instance for chaining
+ */
+ public function setConfigDir($config_dir) {
+ return $this->setTemplateDir($config_dir, true);
+ }
+
+ /**
+ * Registers plugin to be used in templates
+ *
+ * @param string $type plugin type
+ * @param string $name name of template tag
+ * @param callable $callback PHP callback to register
+ * @param bool $cacheable if true (default) this function is cache able
+ *
+ * @return $this
+ * @throws \Smarty\Exception
+ * @link https://www.smarty.net/docs/en/api.register.plugin.tpl
+ *
+ * @api Smarty::registerPlugin()
+ */
+ public function registerPlugin($type, $name, $callback, $cacheable = true) {
+ if (isset($this->registered_plugins[$type][$name])) {
+ throw new Exception("Plugin tag '{$name}' already registered");
+ } elseif (!is_callable($callback) && !class_exists($callback)) {
+ throw new Exception("Plugin '{$name}' not callable");
+ } else {
+ $this->registered_plugins[$type][$name] = [$callback, (bool)$cacheable];
+ }
+ return $this;
+ }
+
+ /**
+ * Returns plugin previously registered using ::registerPlugin as a numerical array as follows or null if not found:
+ * [
+ * 0 => the callback
+ * 1 => (bool) $cacheable
+ * 2 => (array) $cache_attr
+ * ]
+ *
+ * @param string $type plugin type
+ * @param string $name name of template tag
+ *
+ * @return array|null
+ * @link https://www.smarty.net/docs/en/api.unregister.plugin.tpl
+ *
+ * @api Smarty::unregisterPlugin()
+ */
+ public function getRegisteredPlugin($type, $name): ?array {
+ if (isset($this->registered_plugins[$type][$name])) {
+ return $this->registered_plugins[$type][$name];
+ }
+ return null;
+ }
+
+ /**
+ * Unregisters plugin previously registered using ::registerPlugin
+ *
+ * @param string $type plugin type
+ * @param string $name name of template tag
+ *
+ * @return $this
+ * @link https://www.smarty.net/docs/en/api.unregister.plugin.tpl
+ *
+ * @api Smarty::unregisterPlugin()
+ */
+ public function unregisterPlugin($type, $name) {
+ if (isset($this->registered_plugins[$type][$name])) {
+ unset($this->registered_plugins[$type][$name]);
+ }
+ return $this;
+ }
+
+ /**
+ * Adds directory of plugin files
+ *
+ * @param null|array|string $plugins_dir
+ *
+ * @return Smarty current Smarty instance for chaining
+ * @deprecated since 5.0
+ */
+ public function addPluginsDir($plugins_dir) {
+ trigger_error('Using Smarty::addPluginsDir() to load plugins is deprecated and will be ' .
+ 'removed in a future release. Use Smarty::addExtension() to add an extension or Smarty::registerPlugin to ' .
+ 'quickly register a plugin using a callback function.', E_USER_DEPRECATED);
+
+ foreach ((array)$plugins_dir as $v) {
+ $path = $this->_realpath(rtrim($v ?? '', '/\\') . DIRECTORY_SEPARATOR, true);
+ $this->BCPluginsAdapter->loadPluginsFromDir($path);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get plugin directories
+ *
+ * @return array list of plugin directories
+ * @deprecated since 5.0
+ */
+ public function getPluginsDir() {
+ trigger_error('Using Smarty::getPluginsDir() is deprecated and will be ' .
+ 'removed in a future release. It will always return an empty array.', E_USER_DEPRECATED);
+ return [];
+ }
+
+ /**
+ * Set plugins directory
+ *
+ * @param string|array $plugins_dir directory(s) of plugins
+ *
+ * @return Smarty current Smarty instance for chaining
+ * @deprecated since 5.0
+ */
+ public function setPluginsDir($plugins_dir) {
+ trigger_error('Using Smarty::getPluginsDir() is deprecated and will be ' .
+ 'removed in a future release. For now, it will remove the DefaultExtension from the extensions list and ' .
+ 'proceed to call Smartyy::addPluginsDir..', E_USER_DEPRECATED);
+
+ $this->extensions = array_filter(
+ $this->extensions,
+ function ($extension) {
+ return !($extension instanceof DefaultExtension);
+ }
+ );
+
+ return $this->addPluginsDir($plugins_dir);
+ }
+
+ /**
+ * Registers a default plugin handler
+ *
+ * @param callable $callback class/method name
+ *
+ * @return $this
+ * @throws Exception if $callback is not callable
+ * @link https://www.smarty.net/docs/en/api.register.default.plugin.handler.tpl
+ *
+ * @api Smarty::registerDefaultPluginHandler()
+ *
+ * @deprecated since 5.0
+ */
+ public function registerDefaultPluginHandler($callback) {
+
+ trigger_error('Using Smarty::registerDefaultPluginHandler() is deprecated and will be ' .
+ 'removed in a future release. Please rewrite your plugin handler as an extension.',
+ E_USER_DEPRECATED);
+
+ if (is_callable($callback)) {
+ $this->default_plugin_handler_func = $callback;
+ } else {
+ throw new Exception("Default plugin handler '$callback' not callable");
+ }
+ return $this;
+ }
+
+ /**
+ * Get compiled directory
+ *
+ * @return string path to compiled templates
+ */
+ public function getCompileDir() {
+ if (!$this->_compileDirNormalized) {
+ $this->_normalizeDir('compile_dir', $this->compile_dir);
+ $this->_compileDirNormalized = true;
+ }
+ return $this->compile_dir;
+ }
+
+ /**
+ *
+ * @param string $compile_dir directory to store compiled templates in
+ *
+ * @return Smarty current Smarty instance for chaining
+ */
+ public function setCompileDir($compile_dir) {
+ $this->_normalizeDir('compile_dir', $compile_dir);
+ $this->_compileDirNormalized = true;
+ return $this;
+ }
+
+ /**
+ * Get cache directory
+ *
+ * @return string path of cache directory
+ */
+ public function getCacheDir() {
+ if (!$this->_cacheDirNormalized) {
+ $this->_normalizeDir('cache_dir', $this->cache_dir);
+ $this->_cacheDirNormalized = true;
+ }
+ return $this->cache_dir;
+ }
+
+ /**
+ * Set cache directory
+ *
+ * @param string $cache_dir directory to store cached templates in
+ *
+ * @return Smarty current Smarty instance for chaining
+ */
+ public function setCacheDir($cache_dir) {
+ $this->_normalizeDir('cache_dir', $cache_dir);
+ $this->_cacheDirNormalized = true;
+ return $this;
+ }
+
+ private $templates = [];
+
+ /**
+ * Creates a template object
+ *
+ * @param string $template_name
+ * @param mixed $cache_id cache id to be used with this template
+ * @param mixed $compile_id compile id to be used with this template
+ * @param null $parent next higher level of Smarty variables
+ *
+ * @return Template template object
+ * @throws Exception
+ */
+ public function createTemplate($template_name, $cache_id = null, $compile_id = null, $parent = null): Template {
+
+ $data = [];
+
+ // Shuffle params for backward compatibility: if 2nd param is an object, it's the parent
+ if (is_object($cache_id)) {
+ $parent = $cache_id;
+ $cache_id = null;
+ }
+
+ // Shuffle params for backward compatibility: if 2nd param is an array, it's data
+ if (is_array($cache_id)) {
+ $data = $cache_id;
+ $cache_id = null;
+ }
+
+ return $this->doCreateTemplate($template_name, $cache_id, $compile_id, $parent, null, null, false, $data);
+ }
+
+ /**
+ * Get unique template id
+ *
+ * @param string $resource_name
+ * @param null|mixed $cache_id
+ * @param null|mixed $compile_id
+ * @param null $caching
+ *
+ * @return string
+ */
+ private function generateUniqueTemplateId(
+ $resource_name,
+ $cache_id = null,
+ $compile_id = null,
+ $caching = null
+ ): string {
+ // defaults for optional params
+ $cache_id = $cache_id ?? $this->cache_id;
+ $compile_id = $compile_id ?? $this->compile_id;
+ $caching = (int)$caching ?? $this->caching;
+
+ // Add default resource type to resource name if it is missing
+ if (strpos($resource_name, ':') === false) {
+ $resource_name = "{$this->default_resource_type}:{$resource_name}";
+ }
+
+ $_templateId = $resource_name . '#' . $cache_id . '#' . $compile_id . '#' . $caching;
+
+ // hash very long IDs to prevent problems with filename length
+ // do not hash shorter IDs, so they remain recognizable
+ if (strlen($_templateId) > 150) {
+ $_templateId = sha1($_templateId);
+ }
+
+ return $_templateId;
+ }
+
+ /**
+ * Normalize path
+ * - remove /./ and /../
+ * - make it absolute if required
+ *
+ * @param string $path file path
+ * @param bool $realpath if true - convert to absolute
+ * false - convert to relative
+ * null - keep as it is but
+ * remove /./ /../
+ *
+ * @return string
+ */
+ public function _realpath($path, $realpath = null) {
+ $nds = ['/' => '\\', '\\' => '/'];
+ preg_match(
+ '%^(?<root>(?:[[:alpha:]]:[\\\\/]|/|[\\\\]{2}[[:alpha:]]+|[[:print:]]{2,}:[/]{2}|[\\\\])?)(?<path>(.*))$%u',
+ $path,
+ $parts
+ );
+ $path = $parts['path'];
+ if ($parts['root'] === '\\') {
+ $parts['root'] = substr(getcwd(), 0, 2) . $parts['root'];
+ } else {
+ if ($realpath !== null && !$parts['root']) {
+ $path = getcwd() . DIRECTORY_SEPARATOR . $path;
+ }
+ }
+ // normalize DIRECTORY_SEPARATOR
+ $path = str_replace($nds[DIRECTORY_SEPARATOR], DIRECTORY_SEPARATOR, $path);
+ $parts['root'] = str_replace($nds[DIRECTORY_SEPARATOR], DIRECTORY_SEPARATOR, $parts['root']);
+ do {
+ $path = preg_replace(
+ ['#[\\\\/]{2}#', '#[\\\\/][.][\\\\/]#', '#[\\\\/]([^\\\\/.]+)[\\\\/][.][.][\\\\/]#'],
+ DIRECTORY_SEPARATOR,
+ $path,
+ -1,
+ $count
+ );
+ } while ($count > 0);
+ return $realpath !== false ? $parts['root'] . $path : str_ireplace(getcwd(), '.', $parts['root'] . $path);
+ }
+
+ /**
+ * @param boolean $use_sub_dirs
+ */
+ public function setUseSubDirs($use_sub_dirs) {
+ $this->use_sub_dirs = $use_sub_dirs;
+ }
+
+ /**
+ * @param int $error_reporting
+ */
+ public function setErrorReporting($error_reporting) {
+ $this->error_reporting = $error_reporting;
+ }
+
+ /**
+ * @param boolean $escape_html
+ */
+ public function setEscapeHtml($escape_html) {
+ $this->escape_html = $escape_html;
+ }
+
+ /**
+ * Return auto_literal flag
+ *
+ * @return boolean
+ */
+ public function getAutoLiteral() {
+ return $this->auto_literal;
+ }
+
+ /**
+ * Set auto_literal flag
+ *
+ * @param boolean $auto_literal
+ */
+ public function setAutoLiteral($auto_literal = true) {
+ $this->auto_literal = $auto_literal;
+ }
+
+ /**
+ * @param boolean $force_compile
+ */
+ public function setForceCompile($force_compile) {
+ $this->force_compile = $force_compile;
+ }
+
+ /**
+ * @param boolean $merge_compiled_includes
+ */
+ public function setMergeCompiledIncludes($merge_compiled_includes) {
+ $this->merge_compiled_includes = $merge_compiled_includes;
+ }
+
+ /**
+ * Get left delimiter
+ *
+ * @return string
+ */
+ public function getLeftDelimiter() {
+ return $this->left_delimiter;
+ }
+
+ /**
+ * Set left delimiter
+ *
+ * @param string $left_delimiter
+ */
+ public function setLeftDelimiter($left_delimiter) {
+ $this->left_delimiter = $left_delimiter;
+ }
+
+ /**
+ * Get right delimiter
+ *
+ * @return string $right_delimiter
+ */
+ public function getRightDelimiter() {
+ return $this->right_delimiter;
+ }
+
+ /**
+ * Set right delimiter
+ *
+ * @param string
+ */
+ public function setRightDelimiter($right_delimiter) {
+ $this->right_delimiter = $right_delimiter;
+ }
+
+ /**
+ * @param boolean $debugging
+ */
+ public function setDebugging($debugging) {
+ $this->debugging = $debugging;
+ }
+
+ /**
+ * @param boolean $config_overwrite
+ */
+ public function setConfigOverwrite($config_overwrite) {
+ $this->config_overwrite = $config_overwrite;
+ }
+
+ /**
+ * @param boolean $config_booleanize
+ */
+ public function setConfigBooleanize($config_booleanize) {
+ $this->config_booleanize = $config_booleanize;
+ }
+
+ /**
+ * @param boolean $config_read_hidden
+ */
+ public function setConfigReadHidden($config_read_hidden) {
+ $this->config_read_hidden = $config_read_hidden;
+ }
+
+ /**
+ * @param boolean $compile_locking
+ */
+ public function setCompileLocking($compile_locking) {
+ $this->compile_locking = $compile_locking;
+ }
+
+ /**
+ * @param string $default_resource_type
+ */
+ public function setDefaultResourceType($default_resource_type) {
+ $this->default_resource_type = $default_resource_type;
+ }
+
+ /**
+ * Test install
+ *
+ * @param null $errors
+ */
+ public function testInstall(&$errors = null) {
+ \Smarty\TestInstall::testInstall($this, $errors);
+ }
+
+ /**
+ * Get Smarty object
+ *
+ * @return Smarty
+ */
+ public function getSmarty() {
+ return $this;
+ }
+
+ /**
+ * Normalize and set directory string
+ *
+ * @param string $dirName cache_dir or compile_dir
+ * @param string $dir filepath of folder
+ */
+ private function _normalizeDir($dirName, $dir) {
+ $this->{$dirName} = $this->_realpath(rtrim($dir ?? '', "/\\") . DIRECTORY_SEPARATOR, true);
+ }
+
+ /**
+ * Normalize template_dir or config_dir
+ *
+ * @param bool $isConfig true for config_dir
+ */
+ private function _normalizeTemplateConfig($isConfig) {
+ if ($isConfig) {
+ $processed = &$this->_processedConfigDir;
+ $dir = &$this->config_dir;
+ } else {
+ $processed = &$this->_processedTemplateDir;
+ $dir = &$this->template_dir;
+ }
+ if (!is_array($dir)) {
+ $dir = (array)$dir;
+ }
+ foreach ($dir as $k => $v) {
+ if (!isset($processed[$k])) {
+ $dir[$k] = $this->_realpath(rtrim($v ?? '', "/\\") . DIRECTORY_SEPARATOR, true);
+ $processed[$k] = true;
+ }
+ }
+
+ if ($isConfig) {
+ $this->_configDirNormalized = true;
+ $this->_joined_config_dir = join('#', $this->config_dir);
+ } else {
+ $this->_templateDirNormalized = true;
+ $this->_joined_template_dir = join('#', $this->template_dir);
+ }
+
+ }
+
+ /**
+ * Mutes errors for "undefined index", "undefined array key" and "trying to read property of null".
+ *
+ * @void
+ */
+ public function muteUndefinedOrNullWarnings(): void {
+ $this->isMutingUndefinedOrNullWarnings = true;
+ }
+
+ /**
+ * Indicates if Smarty will mute errors for "undefined index", "undefined array key" and "trying to read property of null".
+ *
+ * @return bool
+ */
+ public function isMutingUndefinedOrNullWarnings(): bool {
+ return $this->isMutingUndefinedOrNullWarnings;
+ }
+
+ /**
+ * Empty cache for a specific template
+ *
+ * @param string $template_name template name
+ * @param string $cache_id cache id
+ * @param string $compile_id compile id
+ * @param integer $exp_time expiration time
+ * @param string $type resource type
+ *
+ * @return int number of cache files deleted
+ * @throws \Smarty\Exception
+ * @link https://www.smarty.net/docs/en/api.clear.cache.tpl
+ *
+ * @api Smarty::clearCache()
+ */
+ public function clearCache(
+ $template_name,
+ $cache_id = null,
+ $compile_id = null,
+ $exp_time = null
+ ) {
+ return $this->getCacheResource()->clear($this, $template_name, $cache_id, $compile_id, $exp_time);
+ }
+
+ /**
+ * Empty cache folder
+ *
+ * @param integer $exp_time expiration time
+ * @param string $type resource type
+ *
+ * @return int number of cache files deleted
+ * @link https://www.smarty.net/docs/en/api.clear.all.cache.tpl
+ *
+ * @api Smarty::clearAllCache()
+ */
+ public function clearAllCache($exp_time = null) {
+ return $this->getCacheResource()->clearAll($this, $exp_time);
+ }
+
+ /**
+ * Delete compiled template file
+ *
+ * @param string $resource_name template name
+ * @param string $compile_id compile id
+ * @param integer $exp_time expiration time
+ *
+ * @return int number of template files deleted
+ * @throws \Smarty\Exception
+ * @link https://www.smarty.net/docs/en/api.clear.compiled.template.tpl
+ *
+ * @api Smarty::clearCompiledTemplate()
+ */
+ public function clearCompiledTemplate($resource_name = null, $compile_id = null, $exp_time = null) {
+ $_compile_dir = $this->getCompileDir();
+ if ($_compile_dir === '/') { //We should never want to delete this!
+ return 0;
+ }
+ $_compile_id = isset($compile_id) ? preg_replace('![^\w]+!', '_', $compile_id) : null;
+ $_dir_sep = $this->use_sub_dirs ? DIRECTORY_SEPARATOR : '^';
+ if (isset($resource_name)) {
+ $_save_stat = $this->caching;
+ $this->caching = \Smarty\Smarty::CACHING_OFF;
+ /* @var Template $tpl */
+ $tpl = $this->doCreateTemplate($resource_name);
+ $this->caching = $_save_stat;
+ if (!$tpl->getSource()->handler->recompiled && $tpl->getSource()->exists) {
+ $_resource_part_1 = basename(str_replace('^', DIRECTORY_SEPARATOR, $tpl->getCompiled()->filepath));
+ $_resource_part_1_length = strlen($_resource_part_1);
+ } else {
+ return 0;
+ }
+ $_resource_part_2 = str_replace('.php', '.cache.php', $_resource_part_1);
+ $_resource_part_2_length = strlen($_resource_part_2);
+ }
+ $_dir = $_compile_dir;
+ if ($this->use_sub_dirs && isset($_compile_id)) {
+ $_dir .= $_compile_id . $_dir_sep;
+ }
+ if (isset($_compile_id)) {
+ $_compile_id_part = $_compile_dir . $_compile_id . $_dir_sep;
+ $_compile_id_part_length = strlen($_compile_id_part);
+ }
+ $_count = 0;
+ try {
+ $_compileDirs = new RecursiveDirectoryIterator($_dir);
+ } catch (\UnexpectedValueException $e) {
+ // path not found / not a dir
+ return 0;
+ }
+ $_compile = new RecursiveIteratorIterator($_compileDirs, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ($_compile as $_file) {
+ if (substr(basename($_file->getPathname()), 0, 1) === '.') {
+ continue;
+ }
+ $_filepath = (string)$_file;
+ if ($_file->isDir()) {
+ if (!$_compile->isDot()) {
+ // delete folder if empty
+ @rmdir($_file->getPathname());
+ }
+ } else {
+ // delete only php files
+ if (substr($_filepath, -4) !== '.php') {
+ continue;
+ }
+ $unlink = false;
+ if ((!isset($_compile_id) ||
+ (isset($_filepath[$_compile_id_part_length]) &&
+ $a = !strncmp($_filepath, $_compile_id_part, $_compile_id_part_length)))
+ && (!isset($resource_name) || (isset($_filepath[$_resource_part_1_length])
+ && substr_compare(
+ $_filepath,
+ $_resource_part_1,
+ -$_resource_part_1_length,
+ $_resource_part_1_length
+ ) === 0) || (isset($_filepath[$_resource_part_2_length])
+ && substr_compare(
+ $_filepath,
+ $_resource_part_2,
+ -$_resource_part_2_length,
+ $_resource_part_2_length
+ ) === 0))
+ ) {
+ if (isset($exp_time)) {
+ if (is_file($_filepath) && time() - filemtime($_filepath) >= $exp_time) {
+ $unlink = true;
+ }
+ } else {
+ $unlink = true;
+ }
+ }
+ if ($unlink && is_file($_filepath) && @unlink($_filepath)) {
+ $_count++;
+ if (function_exists('opcache_invalidate')
+ && (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api')) < 1)
+ ) {
+ opcache_invalidate($_filepath, true);
+ } elseif (function_exists('apc_delete_file')) {
+ apc_delete_file($_filepath);
+ }
+ }
+ }
+ }
+ return $_count;
+ }
+
+ /**
+ * Compile all template files
+ *
+ * @param string $extension file extension
+ * @param bool $force_compile force all to recompile
+ * @param int $time_limit
+ * @param int $max_errors
+ *
+ * @return integer number of template files recompiled
+ * @api Smarty::compileAllTemplates()
+ *
+ */
+ public function compileAllTemplates(
+ $extension = '.tpl',
+ $force_compile = false,
+ $time_limit = 0,
+ $max_errors = null
+ ) {
+ return $this->compileAll($extension, $force_compile, $time_limit, $max_errors);
+ }
+
+ /**
+ * Compile all config files
+ *
+ * @param string $extension file extension
+ * @param bool $force_compile force all to recompile
+ * @param int $time_limit
+ * @param int $max_errors
+ *
+ * @return int number of template files recompiled
+ * @api Smarty::compileAllConfig()
+ *
+ */
+ public function compileAllConfig(
+ $extension = '.conf',
+ $force_compile = false,
+ $time_limit = 0,
+ $max_errors = null
+ ) {
+ return $this->compileAll($extension, $force_compile, $time_limit, $max_errors, true);
+ }
+
+ /**
+ * Compile all template or config files
+ *
+ * @param string $extension template file name extension
+ * @param bool $force_compile force all to recompile
+ * @param int $time_limit set maximum execution time
+ * @param int $max_errors set maximum allowed errors
+ * @param bool $isConfig flag true if called for config files
+ *
+ * @return int number of template files compiled
+ */
+ protected function compileAll(
+ $extension,
+ $force_compile,
+ $time_limit,
+ $max_errors,
+ $isConfig = false
+ ) {
+ // switch off time limit
+ if (function_exists('set_time_limit')) {
+ @set_time_limit($time_limit);
+ }
+ $_count = 0;
+ $_error_count = 0;
+ $sourceDir = $isConfig ? $this->getConfigDir() : $this->getTemplateDir();
+ // loop over array of source directories
+ foreach ($sourceDir as $_dir) {
+ $_dir_1 = new RecursiveDirectoryIterator(
+ $_dir,
+ defined('FilesystemIterator::FOLLOW_SYMLINKS') ?
+ FilesystemIterator::FOLLOW_SYMLINKS : 0
+ );
+ $_dir_2 = new RecursiveIteratorIterator($_dir_1);
+ foreach ($_dir_2 as $_fileinfo) {
+ $_file = $_fileinfo->getFilename();
+ if (substr(basename($_fileinfo->getPathname()), 0, 1) === '.' || strpos($_file, '.svn') !== false) {
+ continue;
+ }
+ if (substr_compare($_file, $extension, -strlen($extension)) !== 0) {
+ continue;
+ }
+ if ($_fileinfo->getPath() !== substr($_dir, 0, -1)) {
+ $_file = substr($_fileinfo->getPath(), strlen($_dir)) . DIRECTORY_SEPARATOR . $_file;
+ }
+ echo "\n", $_dir, '---', $_file;
+ flush();
+ $_start_time = microtime(true);
+ $_smarty = clone $this;
+ //
+ $_smarty->force_compile = $force_compile;
+ try {
+ $_tpl = $this->doCreateTemplate($_file);
+ $_tpl->caching = self::CACHING_OFF;
+ $_tpl->setSource(
+ $isConfig ? \Smarty\Template\Config::load($_tpl) : \Smarty\Template\Source::load($_tpl)
+ );
+ if ($_tpl->mustCompile()) {
+ $_tpl->compileTemplateSource();
+ $_count++;
+ echo ' compiled in ', microtime(true) - $_start_time, ' seconds';
+ flush();
+ } else {
+ echo ' is up to date';
+ flush();
+ }
+ } catch (\Exception $e) {
+ echo "\n ------>Error: ", $e->getMessage(), "\n";
+ $_error_count++;
+ }
+ // free memory
+ unset($_tpl);
+ if ($max_errors !== null && $_error_count === $max_errors) {
+ echo "\ntoo many errors\n";
+ exit(1);
+ }
+ }
+ }
+ echo "\n";
+ return $_count;
+ }
+
+ /**
+ * check client side cache
+ *
+ * @param \Smarty\Template\Cached $cached
+ * @param Template $_template
+ * @param string $content
+ *
+ * @throws \Exception
+ * @throws \Smarty\Exception
+ */
+ public function cacheModifiedCheck(Template\Cached $cached, Template $_template, $content) {
+ $_isCached = $_template->isCached() && !$_template->getCompiled()->getNocacheCode();
+ $_last_modified_date =
+ @substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], 'GMT') + 3);
+ if ($_isCached && $cached->timestamp <= strtotime($_last_modified_date)) {
+ switch (PHP_SAPI) {
+ case 'cgi': // php-cgi < 5.3
+ case 'cgi-fcgi': // php-cgi >= 5.3
+ case 'fpm-fcgi': // php-fpm >= 5.3.3
+ header('Status: 304 Not Modified');
+ break;
+ case 'cli':
+ if (/* ^phpunit */
+ !empty($_SERVER['SMARTY_PHPUNIT_DISABLE_HEADERS']) /* phpunit$ */
+ ) {
+ $_SERVER['SMARTY_PHPUNIT_HEADERS'][] = '304 Not Modified';
+ }
+ break;
+ default:
+ if (/* ^phpunit */
+ !empty($_SERVER['SMARTY_PHPUNIT_DISABLE_HEADERS']) /* phpunit$ */
+ ) {
+ $_SERVER['SMARTY_PHPUNIT_HEADERS'][] = '304 Not Modified';
+ } else {
+ header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
+ }
+ break;
+ }
+ } else {
+ switch (PHP_SAPI) {
+ case 'cli':
+ if (/* ^phpunit */
+ !empty($_SERVER['SMARTY_PHPUNIT_DISABLE_HEADERS']) /* phpunit$ */
+ ) {
+ $_SERVER['SMARTY_PHPUNIT_HEADERS'][] =
+ 'Last-Modified: ' . gmdate('D, d M Y H:i:s', $cached->timestamp) . ' GMT';
+ }
+ break;
+ default:
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $cached->timestamp) . ' GMT');
+ break;
+ }
+ echo $content;
+ }
+ }
+
+ public function getModifierCallback(string $modifierName) {
+ foreach ($this->getExtensions() as $extension) {
+ if ($callback = $extension->getModifierCallback($modifierName)) {
+ return [new CallbackWrapper($modifierName, $callback), 'handle'];
+ }
+ }
+ return null;
+ }
+
+ public function getFunctionHandler(string $functionName): ?\Smarty\FunctionHandler\FunctionHandlerInterface {
+ foreach ($this->getExtensions() as $extension) {
+ if ($handler = $extension->getFunctionHandler($functionName)) {
+ return $handler;
+ }
+ }
+ return null;
+ }
+
+ public function getBlockHandler(string $blockTagName): ?\Smarty\BlockHandler\BlockHandlerInterface {
+ foreach ($this->getExtensions() as $extension) {
+ if ($handler = $extension->getBlockHandler($blockTagName)) {
+ return $handler;
+ }
+ }
+ return null;
+ }
+
+ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier\ModifierCompilerInterface {
+ foreach ($this->getExtensions() as $extension) {
+ if ($handler = $extension->getModifierCompiler($modifier)) {
+ return $handler;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Run pre-filters over template source
+ *
+ * @param string $source the content which shall be processed by the filters
+ * @param Template $template template object
+ *
+ * @return string the filtered source
+ */
+ public function runPreFilters($source, Template $template) {
+
+ foreach ($this->getExtensions() as $extension) {
+ /** @var \Smarty\Filter\FilterInterface $filter */
+ foreach ($extension->getPreFilters() as $filter) {
+ $source = $filter->filter($source, $template);
+ }
+ }
+
+ // return filtered output
+ return $source;
+ }
+
+ /**
+ * Run post-filters over template's compiled code
+ *
+ * @param string $code the content which shall be processed by the filters
+ * @param Template $template template object
+ *
+ * @return string the filtered code
+ */
+ public function runPostFilters($code, Template $template) {
+
+ foreach ($this->getExtensions() as $extension) {
+ /** @var \Smarty\Filter\FilterInterface $filter */
+ foreach ($extension->getPostFilters() as $filter) {
+ $code = $filter->filter($code, $template);
+ }
+ }
+
+ // return filtered output
+ return $code;
+ }
+
+ /**
+ * Run filters over template output
+ *
+ * @param string $content the content which shall be processed by the filters
+ * @param Template $template template object
+ *
+ * @return string the filtered (modified) output
+ */
+ public function runOutputFilters($content, Template $template) {
+
+ foreach ($this->getExtensions() as $extension) {
+ /** @var \Smarty\Filter\FilterInterface $filter */
+ foreach ($extension->getOutputFilters() as $filter) {
+ $content = $filter->filter($content, $template);
+ }
+ }
+
+ // return filtered output
+ return $content;
+ }
+
+ /**
+ * Writes file in a safe way to disk
+ *
+ * @param string $_filepath complete filepath
+ * @param string $_contents file content
+ *
+ * @return boolean true
+ * @throws Exception
+ */
+ public function writeFile($_filepath, $_contents) {
+ $_error_reporting = error_reporting();
+ error_reporting($_error_reporting & ~E_NOTICE & ~E_WARNING);
+ $_dirpath = dirname($_filepath);
+ // if subdirs, create dir structure
+ if ($_dirpath !== '.') {
+ $i = 0;
+ // loop if concurrency problem occurs
+ // see https://bugs.php.net/bug.php?id=35326
+ while (!is_dir($_dirpath)) {
+ if (@mkdir($_dirpath, 0777, true)) {
+ break;
+ }
+ clearstatcache();
+ if (++$i === 3) {
+ error_reporting($_error_reporting);
+ throw new Exception("unable to create directory {$_dirpath}");
+ }
+ sleep(1);
+ }
+ }
+ // write to tmp file, then move to overt file lock race condition
+ $_tmp_file = $_dirpath . DIRECTORY_SEPARATOR . str_replace(['.', ','], '_', uniqid('wrt', true));
+ if (!file_put_contents($_tmp_file, $_contents)) {
+ error_reporting($_error_reporting);
+ throw new Exception("unable to write file {$_tmp_file}");
+ }
+ /*
+ * Windows' rename() fails if the destination exists,
+ * Linux' rename() properly handles the overwrite.
+ * Simply unlink()ing a file might cause other processes
+ * currently reading that file to fail, but linux' rename()
+ * seems to be smart enough to handle that for us.
+ */
+ if (\Smarty\Smarty::$_IS_WINDOWS) {
+ // remove original file
+ if (is_file($_filepath)) {
+ @unlink($_filepath);
+ }
+ // rename tmp file
+ $success = @rename($_tmp_file, $_filepath);
+ } else {
+ // rename tmp file
+ $success = @rename($_tmp_file, $_filepath);
+ if (!$success) {
+ // remove original file
+ if (is_file($_filepath)) {
+ @unlink($_filepath);
+ }
+ // rename tmp file
+ $success = @rename($_tmp_file, $_filepath);
+ }
+ }
+ if (!$success) {
+ error_reporting($_error_reporting);
+ throw new Exception("unable to write file {$_filepath}");
+ }
+ // set file permissions
+ @chmod($_filepath, 0666 & ~umask());
+ error_reporting($_error_reporting);
+ return true;
+ }
+
+ private $runtimes = [];
+
+ /**
+ * Loads and returns a runtime extension or null if not found
+ *
+ * @param string $type
+ *
+ * @return object|null
+ */
+ public function getRuntime(string $type) {
+
+ if (isset($this->runtimes[$type])) {
+ return $this->runtimes[$type];
+ }
+
+ // Lazy load runtimes when/if needed
+ switch ($type) {
+ case 'Capture':
+ return $this->runtimes[$type] = new \Smarty\Runtime\CaptureRuntime();
+ case 'Foreach':
+ return $this->runtimes[$type] = new \Smarty\Runtime\ForeachRuntime();
+ case 'Inheritance':
+ return $this->runtimes[$type] = new \Smarty\Runtime\InheritanceRuntime();
+ case 'TplFunction':
+ return $this->runtimes[$type] = new \Smarty\Runtime\TplFunctionRuntime();
+ case 'DefaultPluginHandler':
+ return $this->runtimes[$type] = new \Smarty\Runtime\DefaultPluginHandlerRuntime(
+ $this->getDefaultPluginHandlerFunc()
+ );
+ }
+
+ throw new \Smarty\Exception('Trying to load invalid runtime ' . $type);
+ }
+
+ /**
+ * Indicates if a runtime is available.
+ *
+ * @param string $type
+ *
+ * @return bool
+ */
+ public function hasRuntime(string $type): bool {
+ try {
+ $this->getRuntime($type);
+ return true;
+ } catch (\Smarty\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @return callable|null
+ */
+ public function getDefaultPluginHandlerFunc(): ?callable {
+ return $this->default_plugin_handler_func;
+ }
+
+ /**
+ * load a filter of specified type and name
+ *
+ * @param string $type filter type
+ * @param string $name filter name
+ *
+ * @return bool
+ * @throws \Smarty\Exception
+ * @api Smarty::loadFilter()
+ * @link https://www.smarty.net/docs/en/api.load.filter.tpl
+ *
+ * @deprecated since 5.0
+ */
+ public function loadFilter($type, $name) {
+
+ if ($type == \Smarty\Smarty::FILTER_VARIABLE) {
+ foreach ($this->getExtensions() as $extension) {
+ if ($extension->getModifierCallback($name)) {
+
+ trigger_error('Using Smarty::loadFilter() to load variable filters is deprecated and will ' .
+ 'be removed in a future release. Use Smarty::addDefaultModifiers() to add a modifier.',
+ E_USER_DEPRECATED);
+
+ $this->addDefaultModifiers([$name]);
+ return true;
+ }
+ }
+ }
+
+ trigger_error('Using Smarty::loadFilter() to load filters is deprecated and will be ' .
+ 'removed in a future release. Use Smarty::addExtension() to add an extension or Smarty::registerFilter to ' .
+ 'quickly register a filter using a callback function.', E_USER_DEPRECATED);
+
+ if ($type == \Smarty\Smarty::FILTER_OUTPUT && $name == 'trimwhitespace') {
+ $this->BCPluginsAdapter->addOutputFilter(new TrimWhitespace());
+ return true;
+ }
+
+ $_plugin = "smarty_{$type}filter_{$name}";
+ if (!is_callable($_plugin) && class_exists($_plugin, false)) {
+ $_plugin = [$_plugin, 'execute'];
+ }
+
+ if (is_callable($_plugin)) {
+ $this->registerFilter($type, $_plugin, $name);
+ return true;
+ }
+
+ throw new Exception("{$type}filter '{$name}' not found or callable");
+ }
+
+ /**
+ * load a filter of specified type and name
+ *
+ * @param string $type filter type
+ * @param string $name filter name
+ *
+ * @return TemplateBase
+ * @throws \Smarty\Exception
+ * @api Smarty::unloadFilter()
+ *
+ * @link https://www.smarty.net/docs/en/api.unload.filter.tpl
+ *
+ * @deprecated since 5.0
+ */
+ public function unloadFilter($type, $name) {
+ trigger_error('Using Smarty::unloadFilter() to unload filters is deprecated and will be ' .
+ 'removed in a future release. Use Smarty::addExtension() to add an extension or Smarty::(un)registerFilter to ' .
+ 'quickly (un)register a filter using a callback function.', E_USER_DEPRECATED);
+
+ return $this->unregisterFilter($type, $name);
+ }
+
+ private $_caching_type = 'file';
+
+ /**
+ * @param $type
+ *
+ * @return void
+ * @deprecated since 5.0
+ */
+ public function setCachingType($type) {
+ trigger_error('Using Smarty::setCachingType() is deprecated and will be ' .
+ 'removed in a future release. Use Smarty::setCacheResource() instead.', E_USER_DEPRECATED);
+ $this->_caching_type = $type;
+ $this->activateBCCacheResource();
+ }
+
+ /**
+ * @return string
+ * @deprecated since 5.0
+ */
+ public function getCachingType(): string {
+ trigger_error('Using Smarty::getCachingType() is deprecated and will be ' .
+ 'removed in a future release.', E_USER_DEPRECATED);
+ return $this->_caching_type;
+ }
+
+ /**
+ * Registers a resource to fetch a template
+ *
+ * @param string $name name of resource type
+ * @param Base $resource_handler
+ *
+ * @return Smarty
+ * @link https://www.smarty.net/docs/en/api.register.cacheresource.tpl
+ *
+ * @api Smarty::registerCacheResource()
+ *
+ * @deprecated since 5.0
+ */
+ public function registerCacheResource($name, \Smarty\Cacheresource\Base $resource_handler) {
+
+ trigger_error('Using Smarty::registerCacheResource() is deprecated and will be ' .
+ 'removed in a future release. Use Smarty::setCacheResource() instead.', E_USER_DEPRECATED);
+
+ $this->registered_cache_resources[$name] = $resource_handler;
+ $this->activateBCCacheResource();
+ return $this;
+ }
+
+ /**
+ * Unregisters a resource to fetch a template
+ *
+ * @param $name
+ *
+ * @return Smarty
+ * @api Smarty::unregisterCacheResource()
+ * @link https://www.smarty.net/docs/en/api.unregister.cacheresource.tpl
+ *
+ * @deprecated since 5.0
+ *
+ */
+ public function unregisterCacheResource($name) {
+
+ trigger_error('Using Smarty::unregisterCacheResource() is deprecated and will be ' .
+ 'removed in a future release.', E_USER_DEPRECATED);
+
+ if (isset($this->registered_cache_resources[$name])) {
+ unset($this->registered_cache_resources[$name]);
+ }
+ return $this;
+ }
+
+ private function activateBCCacheResource() {
+ if ($this->_caching_type == 'file') {
+ $this->setCacheResource(new File());
+ }
+ if (isset($this->registered_cache_resources[$this->_caching_type])) {
+ $this->setCacheResource($this->registered_cache_resources[$this->_caching_type]);
+ }
+ }
+
+ /**
+ * Registers a filter function
+ *
+ * @param string $type filter type
+ * @param callable $callback
+ * @param string|null $name optional filter name
+ *
+ * @return TemplateBase
+ * @throws \Smarty\Exception
+ * @link https://www.smarty.net/docs/en/api.register.filter.tpl
+ *
+ * @api Smarty::registerFilter()
+ */
+ public function registerFilter($type, $callback, $name = null) {
+ $name = $name ?? $this->_getFilterName($callback);
+ if (!is_callable($callback)) {
+ throw new Exception("{$type}filter '{$name}' not callable");
+ }
+ switch ($type) {
+ case 'variable':
+ $this->registerPlugin(self::PLUGIN_MODIFIER, $name, $callback);
+ trigger_error('Using Smarty::registerFilter() to register variable filters is deprecated and ' .
+ 'will be removed in a future release. Use Smarty::addDefaultModifiers() to add a modifier.',
+ E_USER_DEPRECATED);
+
+ $this->addDefaultModifiers([$name]);
+ break;
+ case 'output':
+ $this->BCPluginsAdapter->addCallableAsOutputFilter($callback, $name);
+ break;
+ case 'pre':
+ $this->BCPluginsAdapter->addCallableAsPreFilter($callback, $name);
+ break;
+ case 'post':
+ $this->BCPluginsAdapter->addCallableAsPostFilter($callback, $name);
+ break;
+ default:
+ throw new Exception("Illegal filter type '{$type}'");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return internal filter name
+ *
+ * @param callback $callable
+ *
+ * @return string|null internal filter name or null if callable cannot be serialized
+ */
+ private function _getFilterName($callable) {
+ if (is_array($callable)) {
+ $_class_name = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];
+ return $_class_name . '_' . $callable[1];
+ } elseif (is_string($callable)) {
+ return $callable;
+ }
+ return null;
+ }
+
+ /**
+ * Unregisters a filter function. Smarty cannot unregister closures/anonymous functions if
+ * no name was given in ::registerFilter.
+ *
+ * @param string $type filter type
+ * @param callback|string $name the name previously used in ::registerFilter
+ *
+ * @return TemplateBase
+ * @throws \Smarty\Exception
+ * @api Smarty::unregisterFilter()
+ *
+ * @link https://www.smarty.net/docs/en/api.unregister.filter.tpl
+ *
+ */
+ public function unregisterFilter($type, $name) {
+
+ if (!is_string($name)) {
+ $name = $this->_getFilterName($name);
+ }
+
+ if ($name) {
+ switch ($type) {
+ case 'output':
+ $this->BCPluginsAdapter->removeOutputFilter($name);
+ break;
+ case 'pre':
+ $this->BCPluginsAdapter->removePreFilter($name);
+ break;
+ case 'post':
+ $this->BCPluginsAdapter->removePostFilter($name);
+ break;
+ default:
+ throw new Exception("Illegal filter type '{$type}'");
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add default modifiers
+ *
+ * @param array|string $modifiers modifier or list of modifiers
+ * to add
+ *
+ * @return \Smarty|Template
+ * @api Smarty::addDefaultModifiers()
+ *
+ */
+ public function addDefaultModifiers($modifiers) {
+ if (is_array($modifiers)) {
+ $this->default_modifiers = array_merge($this->default_modifiers, $modifiers);
+ } else {
+ $this->default_modifiers[] = $modifiers;
+ }
+ return $this;
+ }
+
+ /**
+ * Get default modifiers
+ *
+ * @return array list of default modifiers
+ * @api Smarty::getDefaultModifiers()
+ *
+ */
+ public function getDefaultModifiers() {
+ return $this->default_modifiers;
+ }
+
+ /**
+ * Set default modifiers
+ *
+ * @param array|string $modifiers modifier or list of modifiers
+ * to set
+ *
+ * @return TemplateBase
+ * @api Smarty::setDefaultModifiers()
+ *
+ */
+ public function setDefaultModifiers($modifiers) {
+ $this->default_modifiers = (array)$modifiers;
+ return $this;
+ }
+
+ /**
+ * @return Cacheresource\Base
+ */
+ public function getCacheResource(): Cacheresource\Base {
+ return $this->cacheResource;
+ }
+
+ /**
+ * @param Cacheresource\Base $cacheResource
+ */
+ public function setCacheResource(Cacheresource\Base $cacheResource): void {
+ $this->cacheResource = $cacheResource;
+ }
+
+ /**
+ * fetches a rendered Smarty template
+ *
+ * @param string $template the resource handle of the template file or template object
+ * @param mixed $cache_id cache id to be used with this template
+ * @param mixed $compile_id compile id to be used with this template
+ *
+ * @return string rendered template output
+ * @throws Exception
+ * @throws Exception
+ */
+ public function fetch($template = null, $cache_id = null, $compile_id = null) {
+ return $this->returnOrCreateTemplate($template, $cache_id, $compile_id)->fetch();
+ }
+
+ /**
+ * displays a Smarty template
+ *
+ * @param string $template the resource handle of the template file or template object
+ * @param mixed $cache_id cache id to be used with this template
+ * @param mixed $compile_id compile id to be used with this template
+ *
+ * @throws \Exception
+ * @throws \Smarty\Exception
+ */
+ public function display($template = null, $cache_id = null, $compile_id = null) {
+ return $this->returnOrCreateTemplate($template, $cache_id, $compile_id)->display();
+ }
+
+ /**
+ * @param $resource_name
+ * @param $cache_id
+ * @param $compile_id
+ * @param $parent
+ * @param $caching
+ * @param $cache_lifetime
+ * @param bool $isConfig
+ * @param array $data
+ *
+ * @return Template
+ * @throws Exception
+ */
+ public function doCreateTemplate(
+ $resource_name,
+ $cache_id = null,
+ $compile_id = null,
+ $parent = null,
+ $caching = null,
+ $cache_lifetime = null,
+ bool $isConfig = false,
+ array $data = []): Template {
+
+ if (!$this->_templateDirNormalized) {
+ $this->_normalizeTemplateConfig(false);
+ }
+
+ $_templateId = $this->generateUniqueTemplateId($resource_name, $cache_id, $compile_id, $caching);
+
+ if (!isset($this->templates[$_templateId])) {
+ $newTemplate = new Template($resource_name, $this, $parent ?: $this, $cache_id, $compile_id, $caching, $isConfig);
+ $newTemplate->templateId = $_templateId; // @TODO this could go in constructor ^?
+ $this->templates[$_templateId] = $newTemplate;
+ }
+
+ $tpl = clone $this->templates[$_templateId];
+
+ $tpl->setParent($parent ?: $this);
+
+ if ($cache_lifetime) {
+ $tpl->setCacheLifetime($cache_lifetime);
+ }
+
+ // fill data if present
+ foreach ($data as $_key => $_val) {
+ $tpl->assign($_key, $_val);
+ }
+
+ $tpl->tplFunctions = array_merge($parent->tplFunctions ?? [], $tpl->tplFunctions ?? []);
+
+ if (!$this->debugging && $this->debugging_ctrl === 'URL') {
+ $tpl->getSmarty()->getDebug()->debugUrl($tpl->getSmarty());
+ }
+ return $tpl;
+ }
+
+ /**
+ * test if cache is valid
+ *
+ * @param null|string|Template $template the resource handle of the template file or template
+ * object
+ * @param mixed $cache_id cache id to be used with this template
+ * @param mixed $compile_id compile id to be used with this template
+ *
+ * @return bool cache status
+ * @throws \Exception
+ * @throws \Smarty\Exception
+ * @link https://www.smarty.net/docs/en/api.is.cached.tpl
+ *
+ * @api Smarty::isCached()
+ */
+ public function isCached($template = null, $cache_id = null, $compile_id = null) {
+ return $this->returnOrCreateTemplate($template, $cache_id, $compile_id)->isCached();
+ }
+
+ /**
+ * @param $template
+ * @param $cache_id
+ * @param $compile_id
+ * @param $parent
+ *
+ * @return Template
+ * @throws Exception
+ */
+ private function returnOrCreateTemplate($template, $cache_id = null, $compile_id = null) {
+ if (!($template instanceof Template)) {
+ $template = $this->createTemplate($template, $cache_id, $compile_id, $this);
+ $template->caching = $this->caching;
+ }
+ return $template;
+ }
+
+}
+
diff --git a/src/Template.php b/src/Template.php
new file mode 100644
index 00000000..1c7fdf9b
--- /dev/null
+++ b/src/Template.php
@@ -0,0 +1,732 @@
+<?php
+/**
+ * Smarty Internal Plugin Template
+ * This file contains the Smarty template engine
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty;
+
+use Smarty\Resource\BasePlugin;
+use Smarty\Runtime\InheritanceRuntime;
+use Smarty\Template\Source;
+use Smarty\Template\Cached;
+use Smarty\Template\Compiled;
+use Smarty\Template\Config;
+
+/**
+ * Main class with template data structures and methods
+ */
+#[\AllowDynamicProperties]
+class Template extends TemplateBase {
+
+ /**
+ * caching mode to create nocache code but no cache file
+ */
+ public const CACHING_NOCACHE_CODE = 9999;
+
+ /**
+ * @var Compiled
+ */
+ private $compiled = null;
+
+ /**
+ * @var Cached
+ */
+ private $cached = null;
+
+ /**
+ * @var \Smarty\Compiler\Template
+ */
+ private $compiler = null;
+
+ /**
+ * Source instance
+ *
+ * @var Source|Config
+ */
+ private $source = null;
+
+ /**
+ * Template resource
+ *
+ * @var string
+ */
+ public $template_resource = null;
+
+ /**
+ * Template ID
+ *
+ * @var null|string
+ */
+ public $templateId = null;
+
+ /**
+ * Callbacks called before rendering template
+ *
+ * @var callback[]
+ */
+ public $startRenderCallbacks = [];
+
+ /**
+ * Callbacks called after rendering template
+ *
+ * @var callback[]
+ */
+ public $endRenderCallbacks = [];
+
+ /**
+ * Template left-delimiter. If null, defaults to $this->getSmarty()-getLeftDelimiter().
+ *
+ * @var string
+ */
+ private $left_delimiter = null;
+
+ /**
+ * Template right-delimiter. If null, defaults to $this->getSmarty()-getRightDelimiter().
+ *
+ * @var string
+ */
+ private $right_delimiter = null;
+
+ /**
+ * @var InheritanceRuntime|null
+ */
+ private $inheritance;
+
+ /**
+ * Create template data object
+ * Some of the global Smarty settings copied to template scope
+ * It load the required template resources and caching plugins
+ *
+ * @param string $template_resource template resource string
+ * @param Smarty $smarty Smarty instance
+ * @param \Smarty\Data|null $_parent back pointer to parent object with variables or null
+ * @param mixed $_cache_id cache id or null
+ * @param mixed $_compile_id compile id or null
+ * @param bool|int|null $_caching use caching?
+ * @param bool $_isConfig
+ *
+ * @throws \Smarty\Exception
+ */
+ public function __construct(
+ $template_resource,
+ Smarty $smarty,
+ \Smarty\Data $_parent = null,
+ $_cache_id = null,
+ $_compile_id = null,
+ $_caching = null,
+ $_isConfig = false
+ ) {
+ $this->smarty = $smarty;
+ // Smarty parameter
+ $this->cache_id = $_cache_id === null ? $this->smarty->cache_id : $_cache_id;
+ $this->compile_id = $_compile_id === null ? $this->smarty->compile_id : $_compile_id;
+ $this->caching = (int)($_caching === null ? $this->smarty->caching : $_caching);
+ $this->cache_lifetime = $this->smarty->cache_lifetime;
+ $this->compile_check = (int)$smarty->compile_check;
+ $this->parent = $_parent;
+ // Template resource
+ $this->template_resource = $template_resource;
+
+ $this->source = $_isConfig ? Config::load($this) : Source::load($this);
+ $this->compiled = Compiled::load($this);
+
+ if ($smarty->security_policy) {
+ $smarty->security_policy->registerCallBacks($this);
+ }
+ }
+
+ /**
+ * render template
+ *
+ * @param bool $no_output_filter if true do not run output filter
+ * @param null|bool $display true: display, false: fetch null: sub-template
+ *
+ * @return string
+ * @throws \Exception
+ * @throws \Smarty\Exception
+ */
+ private function render($no_output_filter = true, $display = null) {
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->start_template($this, $display);
+ }
+ // checks if template exists
+ if ($this->compile_check && !$this->getSource()->exists) {
+ throw new Exception(
+ "Unable to load '{$this->getSource()->type}:{$this->getSource()->name}'" .
+ ($this->_isSubTpl() ? " in '{$this->parent->template_resource}'" : '')
+ );
+ }
+
+ // disable caching for evaluated code
+ if ($this->getSource()->handler->recompiled) {
+ $this->caching = \Smarty\Smarty::CACHING_OFF;
+ }
+
+ foreach ($this->startRenderCallbacks as $callback) {
+ call_user_func($callback, $this);
+ }
+
+ try {
+
+ // read from cache or render
+ if ($this->caching === \Smarty\Smarty::CACHING_LIFETIME_CURRENT || $this->caching === \Smarty\Smarty::CACHING_LIFETIME_SAVED) {
+ $this->getCached()->render($this, $no_output_filter);
+ } else {
+ $this->getCompiled()->render($this);
+ }
+
+ } finally {
+ foreach ($this->endRenderCallbacks as $callback) {
+ call_user_func($callback, $this);
+ }
+ }
+
+ // display or fetch
+ if ($display) {
+ if ($this->caching && $this->smarty->cache_modified_check) {
+ $this->smarty->cacheModifiedCheck(
+ $this->getCached(),
+ $this,
+ isset($content) ? $content : ob_get_clean()
+ );
+ } else {
+ if ((!$this->caching || $this->getCached()->getNocacheCode() || $this->getSource()->handler->recompiled)
+ && !$no_output_filter && isset($this->smarty->registered_filters['output'])
+ ) {
+ echo $this->smarty->runOutputFilters(ob_get_clean(), $this);
+ } else {
+ echo ob_get_clean();
+ }
+ }
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->end_template($this);
+ // debug output
+ $this->smarty->getDebug()->display_debug($this, true);
+ }
+ return '';
+ } else {
+ if ($this->smarty->debugging) {
+ $this->smarty->getDebug()->end_template($this);
+ if ($this->smarty->debugging === 2 && $display === false) {
+ $this->smarty->getDebug()->display_debug($this, true);
+ }
+ }
+ if (
+ !$no_output_filter
+ && (!$this->caching || $this->getCached()->getNocacheCode() || $this->getSource()->handler->recompiled)
+ ) {
+
+ return $this->smarty->runOutputFilters(ob_get_clean(), $this);
+ }
+ // return cache content
+ return null;
+ }
+ }
+
+ /**
+ * Runtime function to render sub-template
+ *
+ * @param string $template_name template name
+ * @param mixed $cache_id cache id
+ * @param mixed $compile_id compile id
+ * @param integer $caching cache mode
+ * @param integer $cache_lifetime lifetime of cache data
+ * @param array $extra_vars passed parameter template variables
+ * @param int|null $scope
+ *
+ * @throws Exception
+ */
+ public function renderSubTemplate(
+ $template_name,
+ $cache_id,
+ $compile_id,
+ $caching,
+ $cache_lifetime,
+ array $extra_vars = [],
+ int $scope = null,
+ ?string $currentDir = null
+ ) {
+
+ $name = $this->parseResourceName($template_name);
+ if ($currentDir && preg_match('/^\.{1,2}\//', $name)) {
+ // relative template resource name, append it to current template name
+ $template_name = $currentDir . DIRECTORY_SEPARATOR . $name;
+ }
+
+ $tpl = $this->smarty->doCreateTemplate($template_name, $cache_id, $compile_id, $this, $caching, $cache_lifetime);
+
+ $tpl->inheritance = $this->getInheritance(); // re-use the same Inheritance object inside the inheritance tree
+
+ if ($scope) {
+ $tpl->defaultScope = $scope;
+ }
+
+ if ($caching) {
+ if ($tpl->templateId !== $this->templateId && $caching !== \Smarty\Template::CACHING_NOCACHE_CODE) {
+ $tpl->getCached(true);
+ } else {
+ // re-use the same Cache object across subtemplates to gather hashes and file dependencies.
+ $tpl->setCached($this->getCached());
+ }
+ }
+
+ foreach ($extra_vars as $_key => $_val) {
+ $tpl->assign($_key, $_val);
+ }
+ if ($tpl->caching === \Smarty\Template::CACHING_NOCACHE_CODE) {
+ if ($tpl->getCompiled()->getNocacheCode()) {
+ $this->getCached()->hashes[$tpl->getCompiled()->nocache_hash] = true;
+ }
+ }
+
+ $tpl->render();
+ }
+
+ /**
+ * Remove type indicator from resource name if present.
+ * E.g. $this->parseResourceName('file:template.tpl') returns 'template.tpl'
+ *
+ * @note "C:/foo.tpl" was forced to file resource up till Smarty 3.1.3 (including).
+ *
+ * @param string $resource_name template_resource or config_resource to parse
+ *
+ * @return string
+ */
+ private function parseResourceName($resource_name): string {
+ if (preg_match('/^([A-Za-z0-9_\-]{2,}):/', $resource_name, $match)) {
+ return substr($resource_name, strlen($match[0]));
+ }
+ return $resource_name;
+ }
+
+ /**
+ * Check if this is a sub template
+ *
+ * @return bool true is sub template
+ */
+ public function _isSubTpl() {
+ return isset($this->parent) && $this->parent instanceof Template;
+ }
+
+ public function assign($tpl_var, $value = null, $nocache = false, $scope = null) {
+ return parent::assign($tpl_var, $value, $nocache, $scope);
+ }
+
+ /**
+ * Compiles the template
+ * If the template is not evaluated the compiled template is saved on disk
+ *
+ * @TODO only used in compileAll and 1 unit test: can we move this and make compileAndWrite private?
+ *
+ * @throws \Exception
+ */
+ public function compileTemplateSource() {
+ return $this->getCompiled()->compileAndWrite($this);
+ }
+
+ /**
+ * Return cached content
+ *
+ * @return null|string
+ * @throws Exception
+ */
+ public function getCachedContent() {
+ return $this->getCached()->getContent($this);
+ }
+
+ /**
+ * Writes the content to cache resource
+ *
+ * @param string $content
+ *
+ * @return bool
+ *
+ * @TODO this method is only used in unit tests that (mostly) try to test CacheResources.
+ */
+ public function writeCachedContent($content) {
+ if ($this->getSource()->handler->recompiled || !$this->caching
+ ) {
+ // don't write cache file
+ return false;
+ }
+ $codeframe = $this->createCodeFrame($content, '', true);
+ return $this->getCached()->writeCache($this, $codeframe);
+ }
+
+ /**
+ * Get unique template id
+ *
+ * @return string
+ */
+ public function getTemplateId() {
+ return $this->templateId;
+ }
+
+ /**
+ * runtime error not matching capture tags
+ *
+ * @throws \Smarty\Exception
+ */
+ public function capture_error() {
+ throw new Exception("Not matching {capture} open/close in '{$this->template_resource}'");
+ }
+
+ /**
+ * Return Compiled object
+ *
+ * @param bool $forceNew force new compiled object
+ */
+ public function getCompiled($forceNew = false) {
+ if ($forceNew || !isset($this->compiled)) {
+ $this->compiled = Compiled::load($this);
+ }
+ return $this->compiled;
+ }
+
+ /**
+ * Return Cached object
+ *
+ * @param bool $forceNew force new cached object
+ *
+ * @throws Exception
+ */
+ public function getCached($forceNew = false): Cached {
+ if ($forceNew || !isset($this->cached)) {
+ $cacheResource = $this->smarty->getCacheResource();
+ $this->cached = new Cached(
+ $this->source,
+ $cacheResource,
+ $this->compile_id,
+ $this->cache_id
+ );
+ if ($this->isCachingEnabled()) {
+ $cacheResource->populate($this->cached, $this);
+ } else {
+ $this->cached->setValid(false);
+ }
+ }
+ return $this->cached;
+ }
+
+ private function isCachingEnabled(): bool {
+ return $this->caching && !$this->getSource()->handler->recompiled;
+ }
+
+ /**
+ * Helper function for InheritanceRuntime object
+ *
+ * @return InheritanceRuntime
+ * @throws Exception
+ */
+ public function getInheritance(): InheritanceRuntime {
+ if (is_null($this->inheritance)) {
+ $this->inheritance = clone $this->getSmarty()->getRuntime('Inheritance');
+ }
+ return $this->inheritance;
+ }
+
+ /**
+ * Sets a new InheritanceRuntime object.
+ *
+ * @param InheritanceRuntime $inheritanceRuntime
+ *
+ * @return void
+ */
+ public function setInheritance(InheritanceRuntime $inheritanceRuntime) {
+ $this->inheritance = $inheritanceRuntime;
+ }
+
+ /**
+ * Return Compiler object
+ */
+ public function getCompiler() {
+ if (!isset($this->compiler)) {
+ $this->compiler = $this->getSource()->createCompiler();
+ }
+ return $this->compiler;
+ }
+
+ /**
+ * Create code frame for compiled and cached templates
+ *
+ * @param string $content optional template content
+ * @param string $functions compiled template function and block code
+ * @param bool $cache flag for cache file
+ * @param Compiler\Template|null $compiler
+ *
+ * @return string
+ * @throws Exception
+ */
+ public function createCodeFrame($content = '', $functions = '', $cache = false, \Smarty\Compiler\Template $compiler = null) {
+ return $this->getCodeFrameCompiler()->create($content, $functions, $cache, $compiler);
+ }
+
+ /**
+ * Template data object destructor
+ */
+ public function __destruct() {
+ if ($this->smarty->cache_locking && $this->getCached()->is_locked) {
+ $this->getCached()->handler->releaseLock($this->smarty, $this->getCached());
+ }
+ }
+
+ /**
+ * Returns if the current template must be compiled by the Smarty compiler
+ * It does compare the timestamps of template source and the compiled templates and checks the force compile
+ * configuration
+ *
+ * @return bool
+ * @throws \Smarty\Exception
+ */
+ public function mustCompile(): bool {
+ if (!$this->getSource()->exists) {
+ if ($this->_isSubTpl()) {
+ $parent_resource = " in '{$this->parent->template_resource}'";
+ } else {
+ $parent_resource = '';
+ }
+ throw new Exception("Unable to load {$this->getSource()->type} '{$this->getSource()->name}'{$parent_resource}");
+ }
+
+ // @TODO move this logic to Compiled
+ return $this->smarty->force_compile
+ || $this->getSource()->handler->recompiled
+ || !$this->getCompiled()->exists
+ || ($this->compile_check && $this->getCompiled()->getTimeStamp() < $this->getSource()->getTimeStamp());
+ }
+
+ private function getCodeFrameCompiler(): Compiler\CodeFrame {
+ return new \Smarty\Compiler\CodeFrame($this);
+ }
+
+ /**
+ * Get left delimiter
+ *
+ * @return string
+ */
+ public function getLeftDelimiter()
+ {
+ return $this->left_delimiter ?? $this->getSmarty()->getLeftDelimiter();
+ }
+
+ /**
+ * Set left delimiter
+ *
+ * @param string $left_delimiter
+ */
+ public function setLeftDelimiter($left_delimiter)
+ {
+ $this->left_delimiter = $left_delimiter;
+ }
+
+ /**
+ * Get right delimiter
+ *
+ * @return string $right_delimiter
+ */
+ public function getRightDelimiter()
+ {
+ return $this->right_delimiter ?? $this->getSmarty()->getRightDelimiter();;
+ }
+
+ /**
+ * Set right delimiter
+ *
+ * @param string
+ */
+ public function setRightDelimiter($right_delimiter)
+ {
+ $this->right_delimiter = $right_delimiter;
+ }
+
+ /**
+ * gets a stream variable
+ *
+ * @param string $variable the stream of the variable
+ *
+ * @return mixed
+ * @throws \Smarty\Exception
+ *
+ */
+ public function getStreamVariable($variable)
+ {
+ $_result = '';
+ $fp = fopen($variable, 'r+');
+ if ($fp) {
+ while (!feof($fp) && ($current_line = fgets($fp)) !== false) {
+ $_result .= $current_line;
+ }
+ fclose($fp);
+ return $_result;
+ }
+ if ($this->getSmarty()->error_unassigned) {
+ throw new Exception('Undefined stream variable "' . $variable . '"');
+ }
+ return null;
+ }
+ /**
+ * @inheritdoc
+ */
+ public function configLoad($config_file, $sections = null)
+ {
+ $confObj = parent::configLoad($config_file, $sections);
+
+ $this->getCompiled()->file_dependency[ $confObj->getSource()->uid ] =
+ array($confObj->getSource()->getResourceName(), $confObj->getSource()->getTimeStamp(), $confObj->getSource()->type);
+
+ return $confObj;
+ }
+
+ public function fetch() {
+ $result = $this->_execute(0);
+ return $result === null ? ob_get_clean() : $result;
+ }
+
+ public function display() {
+ $this->_execute(1);
+ }
+
+ /**
+ * test if cache is valid
+ *
+ * @param mixed $cache_id cache id to be used with this template
+ * @param mixed $compile_id compile id to be used with this template
+ * @param object $parent next higher level of Smarty variables
+ *
+ * @return bool cache status
+ * @throws \Exception
+ * @throws \Smarty\Exception
+ * @link https://www.smarty.net/docs/en/api.is.cached.tpl
+ *
+ * @api Smarty::isCached()
+ */
+ public function isCached(): bool {
+ return (bool) $this->_execute(2);
+ }
+
+ /**
+ * fetches a rendered Smarty template
+ *
+ * @param string $function function type 0 = fetch, 1 = display, 2 = isCache
+ *
+ * @return mixed
+ * @throws Exception
+ * @throws \Throwable
+ */
+ private function _execute($function) {
+
+ $smarty = $this->getSmarty();
+
+ // make sure we have integer values
+ $this->caching = (int)$this->caching;
+ // fetch template content
+ $level = ob_get_level();
+ try {
+ $_smarty_old_error_level =
+ isset($smarty->error_reporting) ? error_reporting($smarty->error_reporting) : null;
+
+ if ($smarty->isMutingUndefinedOrNullWarnings()) {
+ $errorHandler = new \Smarty\ErrorHandler();
+ $errorHandler->activate();
+ }
+
+ if ($function === 2) {
+ if ($this->caching) {
+ // return cache status of template
+ $result = $this->getCached()->isCached($this);
+ } else {
+ return false;
+ }
+ } else {
+
+ // After rendering a template, the tpl/config variables are reset, so the template can be re-used.
+ $savedTplVars = $this->tpl_vars;
+ $savedConfigVars = $this->config_vars;
+
+ // Start output-buffering.
+ ob_start();
+
+ $result = $this->render(false, $function);
+
+ // Restore the template to its previous state
+ $this->tpl_vars = $savedTplVars;
+ $this->config_vars = $savedConfigVars;
+ }
+
+ if (isset($errorHandler)) {
+ $errorHandler->deactivate();
+ }
+
+ if (isset($_smarty_old_error_level)) {
+ error_reporting($_smarty_old_error_level);
+ }
+ return $result;
+ } catch (\Throwable $e) {
+ while (ob_get_level() > $level) {
+ ob_end_clean();
+ }
+ if (isset($errorHandler)) {
+ $errorHandler->deactivate();
+ }
+
+ if (isset($_smarty_old_error_level)) {
+ error_reporting($_smarty_old_error_level);
+ }
+ throw $e;
+ }
+ }
+
+ /**
+ * @return Config|Source|null
+ */
+ public function getSource() {
+ return $this->source;
+ }
+
+ /**
+ * @param Config|Source|null $source
+ */
+ public function setSource($source): void {
+ $this->source = $source;
+ }
+
+ /**
+ * Sets the Cached object, so subtemplates can share one Cached object to gather meta-data.
+ *
+ * @param Cached $cached
+ *
+ * @return void
+ */
+ private function setCached(Cached $cached) {
+ $this->cached = $cached;
+ }
+
+ /**
+ * @param string $compile_id
+ *
+ * @throws Exception
+ */
+ public function setCompileId($compile_id) {
+ parent::setCompileId($compile_id);
+ $this->getCompiled(true);
+ if ($this->caching) {
+ $this->getCached(true);
+ }
+ }
+
+ /**
+ * @param string $cache_id
+ *
+ * @throws Exception
+ */
+ public function setCacheId($cache_id) {
+ parent::setCacheId($cache_id);
+ $this->getCached(true);
+ }
+
+}
diff --git a/src/Template/Cached.php b/src/Template/Cached.php
new file mode 100644
index 00000000..78635db0
--- /dev/null
+++ b/src/Template/Cached.php
@@ -0,0 +1,428 @@
+<?php
+
+namespace Smarty\Template;
+
+use Smarty\Exception;
+use Smarty\Template;
+use Smarty\Template\Cacheresource\Base;
+use Smarty\Template\Compiler\CodeFrame;
+
+/**
+ * Represents a cached version of a template or config file.
+ * @author Rodney Rehm
+ */
+class Cached extends GeneratedPhpFile {
+
+ /**
+ * Cache Is Valid
+ *
+ * @var boolean
+ */
+ private $valid = null;
+
+ /**
+ * @param bool|null $valid
+ */
+ public function setValid(?bool $valid): void {
+ $this->valid = $valid;
+ }
+
+ /**
+ * CacheResource Handler
+ *
+ * @var \Smarty\Cacheresource\Base
+ */
+ public $handler = null;
+
+ /**
+ * Template Cache Id (\Smarty\Template::$cache_id)
+ *
+ * @var string
+ */
+ public $cache_id = null;
+
+ /**
+ * saved cache lifetime in seconds
+ *
+ * @var int
+ */
+ public $cache_lifetime = 0;
+
+ /**
+ * Id for cache locking
+ *
+ * @var string
+ */
+ public $lock_id = null;
+
+ /**
+ * flag that cache is locked by this instance
+ *
+ * @var bool
+ */
+ public $is_locked = false;
+
+ /**
+ * Source Object
+ *
+ * @var Source
+ */
+ public $source = null;
+
+ /**
+ * Nocache hash codes of processed compiled templates
+ *
+ * @var array
+ */
+ public $hashes = [];
+
+ /**
+ * Content buffer
+ *
+ * @var string
+ */
+ public $content = null;
+
+ /**
+ * create Cached Object container
+ *
+ * @param Source $source
+ * @param \Smarty\Cacheresource\Base $handler
+ * @param $compile_id
+ * @param $cache_id
+ */
+ public function __construct(Source $source, \Smarty\Cacheresource\Base $handler, $compile_id, $cache_id) {
+ $this->compile_id = $compile_id;
+ $this->cache_id = $cache_id;
+ $this->source = $source;
+ $this->handler = $handler;
+ }
+
+ /**
+ * Render cache template
+ *
+ * @param \Smarty\Template $_template
+ * @param bool $no_output_filter
+ *
+ * @throws \Exception
+ */
+ public function render(Template $_template, $no_output_filter = true) {
+
+ if (!$this->isCached($_template)) {
+ $this->updateCache($_template, $no_output_filter);
+ } else {
+ if (!$this->processed) {
+ $this->process($_template);
+ }
+ }
+
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->start_cache($_template);
+ }
+
+ $this->getRenderedTemplateCode($_template, $this->unifunc);
+
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->end_cache($_template);
+ }
+
+ }
+
+ /**
+ * Check if cache is valid, lock cache if required
+ *
+ * @param Template $_template
+ *
+ * @return bool flag true if cache is valid
+ * @throws Exception
+ */
+ public function isCached(Template $_template) {
+ if ($this->valid !== null) {
+ return $this->valid;
+ }
+ while (true) {
+ while (true) {
+ if ($this->exists === false || $_template->getSmarty()->force_compile || $_template->getSmarty()->force_cache) {
+ $this->valid = false;
+ } else {
+ $this->valid = true;
+ }
+ if ($this->valid && $_template->caching === \Smarty\Smarty::CACHING_LIFETIME_CURRENT
+ && $_template->cache_lifetime >= 0 && time() > ($this->timestamp + $_template->cache_lifetime)
+ ) {
+ // lifetime expired
+ $this->valid = false;
+ }
+ if ($this->valid && $_template->compile_check === \Smarty\Smarty::COMPILECHECK_ON
+ && $_template->getSource()->getTimeStamp() > $this->timestamp
+ ) {
+ $this->valid = false;
+ }
+ if ($this->valid || !$_template->getSmarty()->cache_locking) {
+ break;
+ }
+ if (!$this->handler->locked($_template->getSmarty(), $this)) {
+ $this->handler->acquireLock($_template->getSmarty(), $this);
+ break 2;
+ }
+ $this->handler->populate($this, $_template);
+ }
+ if ($this->valid) {
+ if (!$_template->getSmarty()->cache_locking || $this->handler->locked($_template->getSmarty(), $this) === null) {
+ // load cache file for the following checks
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->start_cache($_template);
+ }
+ if ($this->handler->process($_template, $this) === false) {
+ $this->valid = false;
+ } else {
+ $this->processed = true;
+ }
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->end_cache($_template);
+ }
+ } else {
+ $this->is_locked = true;
+ continue;
+ }
+ } else {
+ return $this->valid;
+ }
+ if ($this->valid && $_template->caching === \Smarty\Smarty::CACHING_LIFETIME_SAVED
+ && $_template->getCached()->cache_lifetime >= 0
+ && (time() > ($_template->getCached()->timestamp + $_template->getCached()->cache_lifetime))
+ ) {
+ $this->valid = false;
+ }
+ if ($_template->getSmarty()->cache_locking) {
+ if (!$this->valid) {
+ $this->handler->acquireLock($_template->getSmarty(), $this);
+ } elseif ($this->is_locked) {
+ $this->handler->releaseLock($_template->getSmarty(), $this);
+ }
+ }
+ return $this->valid;
+ }
+ return $this->valid;
+ }
+
+ /**
+ * Process cached template
+ *
+ * @param Template $_template template object
+ */
+ private function process(Template $_template) {
+ if ($this->handler->process($_template, $this) === false) {
+ $this->valid = false;
+ }
+ $this->processed = $this->valid;
+ }
+
+ /**
+ * Read cache content from handler
+ *
+ * @param Template $_template template object
+ *
+ * @return string|false content
+ */
+ public function readCache(Template $_template) {
+ if (!$_template->getSource()->handler->recompiled) {
+ return $this->handler->retrieveCachedContent($_template);
+ }
+ return false;
+ }
+
+ /**
+ * Write this cache object to handler
+ *
+ * @param string $content content to cache
+ *
+ * @return bool success
+ */
+ public function writeCache(Template $_template, $content) {
+ if (!$_template->getSource()->handler->recompiled) {
+ if ($this->handler->storeCachedContent($_template, $content)) {
+ $this->content = null;
+ $this->timestamp = time();
+ $this->exists = true;
+ $this->valid = true;
+ $this->cache_lifetime = $_template->cache_lifetime;
+ $this->processed = false;
+ if ($_template->getSmarty()->cache_locking) {
+ $this->handler->releaseLock($_template->getSmarty(), $this);
+ }
+ return true;
+ }
+ $this->content = null;
+ $this->timestamp = false;
+ $this->exists = false;
+ $this->valid = false;
+ $this->processed = false;
+ }
+ return false;
+ }
+
+ /**
+ * Cache was invalid , so render from compiled and write to cache
+ *
+ * @param Template $_template
+ * @param bool $no_output_filter
+ *
+ * @throws \Smarty\Exception
+ */
+ private function updateCache(Template $_template, $no_output_filter) {
+
+ ob_start();
+
+ $_template->getCompiled()->render($_template);
+
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->start_cache($_template);
+ }
+
+ $this->removeNoCacheHash($_template, $no_output_filter);
+ $this->process($_template);
+
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->end_cache($_template);
+ }
+ }
+
+ /**
+ * Sanitize content and write it to cache resource
+ *
+ * @param Template $_template
+ * @param bool $no_output_filter
+ *
+ * @throws \Smarty\Exception
+ */
+ private function removeNoCacheHash(Template $_template, $no_output_filter) {
+ $php_pattern = '/(<%|%>|<\?php|<\?|\?>|<script\s+language\s*=\s*[\"\']?\s*php\s*[\"\']?\s*>)/';
+ $content = ob_get_clean();
+ $hash_array = $this->hashes;
+ $hash_array[$_template->getCompiled()->nocache_hash] = true;
+ $hash_array = array_keys($hash_array);
+ $nocache_hash = '(' . implode('|', $hash_array) . ')';
+ $_template->getCached()->setNocacheCode(false);
+ // get text between non-cached items
+ $cache_split =
+ preg_split(
+ "!/\*%%SmartyNocache:{$nocache_hash}%%\*\/(.+?)/\*/%%SmartyNocache:{$nocache_hash}%%\*/!s",
+ $content
+ );
+ // get non-cached items
+ preg_match_all(
+ "!/\*%%SmartyNocache:{$nocache_hash}%%\*\/(.+?)/\*/%%SmartyNocache:{$nocache_hash}%%\*/!s",
+ $content,
+ $cache_parts
+ );
+ $content = '';
+ // loop over items, stitch back together
+ foreach ($cache_split as $curr_idx => $curr_split) {
+ if (preg_match($php_pattern, $curr_split)) {
+ // escape PHP tags in template content
+ $php_split = preg_split(
+ $php_pattern,
+ $curr_split
+ );
+ preg_match_all(
+ $php_pattern,
+ $curr_split,
+ $php_parts
+ );
+ foreach ($php_split as $idx_php => $curr_php) {
+ $content .= $curr_php;
+ if (isset($php_parts[0][$idx_php])) {
+ $content .= "<?php echo '{$php_parts[ 1 ][ $idx_php ]}'; ?>\n";
+ }
+ }
+ } else {
+ $content .= $curr_split;
+ }
+ if (isset($cache_parts[0][$curr_idx])) {
+ $_template->getCached()->setNocacheCode(true);
+ $content .= $cache_parts[2][$curr_idx];
+ }
+ }
+ if (
+ !$no_output_filter
+ && !$_template->getCached()->getNocacheCode()
+ ) {
+ $content = $_template->getSmarty()->runOutputFilters($content, $_template);
+ }
+
+ $codeframe = (new \Smarty\Compiler\CodeFrame($_template))->create($content, '', true);
+ $this->writeCache($_template, $codeframe);
+ }
+
+ /**
+ * @return Source|null
+ */
+ public function getSource(): ?Source {
+ return $this->source;
+ }
+
+ /**
+ * @param Source|null $source
+ */
+ public function setSource(?Source $source): void {
+ $this->source = $source;
+ }
+
+ /**
+ * Returns the generated content
+ *
+ * @param Template $template
+ *
+ * @return string|null
+ * @throws \Exception
+ */
+ public function getContent(Template $template) {
+ ob_start();
+ $this->render($template);
+ return ob_get_clean();
+ }
+
+ /**
+ * This function is executed automatically when a generated file is included
+ * - Decode saved properties
+ * - Check if file is valid
+ *
+ * @param Template $_template
+ * @param array $properties special template properties
+ *
+ * @return bool flag if compiled or cache file is valid
+ * @throws Exception
+ */
+ public function isFresh(Template $_template, array $properties): bool {
+
+ // on cache resources other than file check version stored in cache code
+ if (\Smarty\Smarty::SMARTY_VERSION !== $properties['version']) {
+ return false;
+ }
+
+ $is_valid = true;
+
+ if (!empty($properties['file_dependency']) && ($_template->compile_check === \Smarty\Smarty::COMPILECHECK_ON)) {
+ $is_valid = $this->checkFileDependencies($properties['file_dependency'], $_template);
+ }
+
+ // CACHING_LIFETIME_SAVED cache expiry has to be validated here since otherwise we'd define the unifunc
+ if ($_template->caching === \Smarty\Smarty::CACHING_LIFETIME_SAVED && $properties['cache_lifetime'] >= 0
+ && (time() > ($this->timestamp + $properties['cache_lifetime']))
+ ) {
+ $is_valid = false;
+ }
+
+ $this->cache_lifetime = $properties['cache_lifetime'];
+ $this->setValid($is_valid);
+
+ if ($is_valid) {
+ $this->unifunc = $properties['unifunc'];
+ $this->setNocacheCode($properties['has_nocache_code']);
+ $this->file_dependency = $properties['file_dependency'];
+ }
+ return $is_valid && !function_exists($properties['unifunc']);
+ }
+
+}
diff --git a/src/Template/Compiled.php b/src/Template/Compiled.php
new file mode 100644
index 00000000..caee87f3
--- /dev/null
+++ b/src/Template/Compiled.php
@@ -0,0 +1,302 @@
+<?php
+
+namespace Smarty\Template;
+
+use Smarty\Exception;
+use Smarty\Template;
+
+/**
+ * Represents a compiled version of a template or config file.
+ * @author Rodney Rehm
+ */
+class Compiled extends GeneratedPhpFile {
+
+ /**
+ * nocache hash
+ *
+ * @var string|null
+ */
+ public $nocache_hash = null;
+
+ /**
+ * Included sub templates
+ * - index name
+ * - value use count
+ *
+ * @var int[]
+ */
+ public $includes = [];
+ /**
+ * @var bool
+ */
+ private $isValid = false;
+
+ /**
+ * get a Compiled Object of this source
+ *
+ * @param Template $_template template object
+ *
+ * @return Compiled compiled object
+ */
+ public static function load($_template) {
+ $compiled = new Compiled();
+ if ($_template->getSource()->handler->supportsCompiledTemplates()) {
+ $compiled->populateCompiledFilepath($_template);
+ }
+ return $compiled;
+ }
+
+ /**
+ * populate Compiled Object with compiled filepath
+ *
+ * @param Template $_template template object
+ **/
+ private function populateCompiledFilepath(Template $_template) {
+ $source = $_template->getSource();
+ $smarty = $_template->getSmarty();
+ $this->filepath = $smarty->getCompileDir();
+ if (isset($_template->compile_id)) {
+ $this->filepath .= preg_replace('![^\w]+!', '_', $_template->compile_id) .
+ ($smarty->use_sub_dirs ? DIRECTORY_SEPARATOR : '^');
+ }
+ // if use_sub_dirs, break file into directories
+ if ($smarty->use_sub_dirs) {
+ $this->filepath .= $source->uid[0] . $source->uid[1] . DIRECTORY_SEPARATOR . $source->uid[2] .
+ $source->uid[3] . DIRECTORY_SEPARATOR . $source->uid[4] . $source->uid[5] .
+ DIRECTORY_SEPARATOR;
+ }
+ $this->filepath .= $source->uid . '_';
+ if ($source->isConfig) {
+ $this->filepath .= (int)$smarty->config_read_hidden + (int)$smarty->config_booleanize * 2 +
+ (int)$smarty->config_overwrite * 4;
+ } else {
+ $this->filepath .= (int)$smarty->escape_html * 2;
+ }
+ $this->filepath .= '.' . $source->type . '_' . $source->getBasename();
+
+ if ($_template->caching) {
+ $this->filepath .= '.cache';
+ }
+ $this->filepath .= '.php';
+ $this->timestamp = $this->exists = is_file($this->filepath);
+ if ($this->exists) {
+ $this->timestamp = filemtime($this->filepath);
+ }
+ }
+
+ /**
+ * render compiled template code
+ *
+ * @param Template $_template
+ *
+ * @return string
+ * @throws \Smarty\Exception
+ */
+ public function render(Template $_template) {
+
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->start_render($_template);
+ }
+ if (!$this->processed) {
+ $this->compileAndLoad($_template);
+ }
+
+ // @TODO Can't Cached handle this? Maybe introduce an event to decouple.
+ if ($_template->caching) {
+ $_template->getCached()->file_dependency =
+ array_merge($_template->getCached()->file_dependency, $this->file_dependency);
+ }
+
+ $this->getRenderedTemplateCode($_template, $this->unifunc);
+
+ // @TODO Can't Cached handle this? Maybe introduce an event to decouple and remove the $_template->caching property.
+ if ($_template->caching && $this->getNocacheCode()) {
+ $_template->getCached()->hashes[$this->nocache_hash] = true;
+ }
+
+ if ($_template->getSmarty()->debugging) {
+ $_template->getSmarty()->getDebug()->end_render($_template);
+ }
+ }
+
+ /**
+ * load compiled template or compile from source
+ *
+ * @param Template $_smarty_tpl do not change variable name, is used by compiled template
+ *
+ * @throws Exception
+ */
+ private function compileAndLoad(Template $_smarty_tpl) {
+
+ if ($_smarty_tpl->getSource()->handler->recompiled) {
+ $this->recompile($_smarty_tpl);
+ return;
+ }
+
+ if ($this->exists && !$_smarty_tpl->getSmarty()->force_compile
+ && !($_smarty_tpl->compile_check && $_smarty_tpl->getSource()->getTimeStamp() > $this->getTimeStamp())
+ ) {
+ $this->loadCompiledTemplate($_smarty_tpl);
+ }
+
+ if (!$this->isValid) {
+ $this->compileAndWrite($_smarty_tpl);
+ $this->loadCompiledTemplate($_smarty_tpl);
+ }
+
+ $this->processed = true;
+ }
+
+ /**
+ * compile template from source
+ *
+ * @param Template $_smarty_tpl do not change variable name, is used by compiled template
+ *
+ * @throws Exception
+ */
+ private function recompile(Template $_smarty_tpl) {
+ $level = ob_get_level();
+ ob_start();
+ // call compiler
+ try {
+ eval('?>' . $this->doCompile($_smarty_tpl));
+ } catch (\Exception $e) {
+ while (ob_get_level() > $level) {
+ ob_end_clean();
+ }
+ throw $e;
+ }
+ ob_get_clean();
+ $this->timestamp = time();
+ $this->exists = true;
+ }
+
+ /**
+ * compile template from source
+ *
+ * @param Template $_template
+ *
+ * @throws Exception
+ */
+ public function compileAndWrite(Template $_template) {
+ // compile locking
+ if ($saved_timestamp = (!$_template->getSource()->handler->recompiled && is_file($this->filepath))) {
+ $saved_timestamp = $this->getTimeStamp();
+ touch($this->filepath);
+ }
+ // compile locking
+ try {
+ // call compiler
+ $this->write($_template, $this->doCompile($_template));
+ } catch (\Exception $e) {
+ // restore old timestamp in case of error
+ if ($saved_timestamp && is_file($this->filepath)) {
+ touch($this->filepath, $saved_timestamp);
+ }
+ throw $e;
+ }
+ }
+
+ /**
+ * Do the actual compiling.
+ *
+ * @param Template $_smarty_tpl
+ *
+ * @return string
+ * @throws Exception
+ */
+ private function doCompile(Template $_smarty_tpl): string {
+ $this->file_dependency = [];
+ $this->includes = [];
+ $this->nocache_hash = null;
+ $this->unifunc = null;
+ return $_smarty_tpl->getCompiler()->compileTemplate($_smarty_tpl);
+ }
+
+ /**
+ * Write compiled code by handler
+ *
+ * @param Template $_template template object
+ * @param string $code compiled code
+ *
+ * @return bool success
+ * @throws \Smarty\Exception
+ */
+ private function write(Template $_template, $code) {
+ if (!$_template->getSource()->handler->recompiled) {
+ if ($_template->getSmarty()->writeFile($this->filepath, $code) === true) {
+ $this->timestamp = $this->exists = is_file($this->filepath);
+ if ($this->exists) {
+ $this->timestamp = filemtime($this->filepath);
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Load fresh compiled template by including the PHP file
+ * HHVM requires a workaround because of a PHP incompatibility
+ *
+ * @param Template $_smarty_tpl do not change/remove variable name, is used by compiled template
+ *
+ */
+ private function loadCompiledTemplate(Template $_smarty_tpl) {
+
+ if (function_exists('opcache_invalidate')
+ && (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
+ ) {
+ opcache_invalidate($this->filepath, true);
+ } elseif (function_exists('apc_compile_file')) {
+ apc_compile_file($this->filepath);
+ }
+ if (defined('HHVM_VERSION')) {
+ eval('?>' . file_get_contents($this->filepath));
+ } else {
+ include $this->filepath;
+ }
+
+ }
+
+ /**
+ * This function is executed automatically when a compiled or cached template file is included
+ * - Decode saved properties from compiled template and cache files
+ * - Check if compiled or cache file is valid
+ *
+ * @param Template $_template
+ * @param array $properties special template properties
+ *
+ * @return bool flag if compiled or cache file is valid
+ * @throws Exception
+ */
+ public function isFresh(Template $_template, array $properties): bool {
+
+ // on cache resources other than file check version stored in cache code
+ if (\Smarty\Smarty::SMARTY_VERSION !== $properties['version']) {
+ return false;
+ }
+
+ $is_valid = true;
+ if (!empty($properties['file_dependency']) && $_template->compile_check) {
+ $is_valid = $this->checkFileDependencies($properties['file_dependency'], $_template);
+ }
+
+ $this->isValid = $is_valid;
+ $this->includes = $properties['includes'] ?? [];
+
+ if ($is_valid) {
+ $this->unifunc = $properties['unifunc'];
+ $this->setNocacheCode($properties['has_nocache_code']);
+ $this->file_dependency = $properties['file_dependency'];
+ }
+ return $is_valid && !function_exists($properties['unifunc']);
+ }
+
+ /**
+ * This method is here only to fix an issue when upgrading from Smarty v4 to v5.
+ */
+ public function _decodeProperties($a, $b, $c = false): bool { return false; }
+
+}
diff --git a/src/Template/Config.php b/src/Template/Config.php
new file mode 100644
index 00000000..b2fcbf81
--- /dev/null
+++ b/src/Template/Config.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Smarty\Template;
+
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Exception;
+
+/**
+ * Smarty Config Resource Data Object
+ * Metadata Container for Config Files
+ *
+ * @author Uwe Tews
+ */
+class Config extends Source {
+
+ /**
+ * Flag that source is a config file
+ *
+ * @var bool
+ */
+ public $isConfig = true;
+
+ /**
+ * @var array
+ */
+ static protected $_incompatible_resources = ['extends' => true];
+
+ public function createCompiler(): \Smarty\Compiler\BaseCompiler {
+ return new \Smarty\Compiler\Configfile($this->smarty);
+ }
+
+ protected static function getDefaultHandlerFunc(Smarty $smarty) {
+ return $smarty->default_config_handler_func;
+ }
+}
diff --git a/src/Template/GeneratedPhpFile.php b/src/Template/GeneratedPhpFile.php
new file mode 100644
index 00000000..f436e976
--- /dev/null
+++ b/src/Template/GeneratedPhpFile.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace Smarty\Template;
+
+use Smarty\Exception;
+use Smarty\Resource\FilePlugin;
+use Smarty\Template;
+
+/**
+ * Base class for generated PHP files, such as compiled and cached versions of templates and config files.
+ *
+ * @author Rodney Rehm
+ */
+abstract class GeneratedPhpFile {
+
+ /**
+ * Compiled Filepath
+ *
+ * @var string
+ */
+ public $filepath = null;
+
+ /**
+ * Compiled Timestamp
+ *
+ * @var int|bool
+ */
+ public $timestamp = false;
+
+ /**
+ * Compiled Existence
+ *
+ * @var boolean
+ */
+ public $exists = false;
+
+ /**
+ * Template Compile Id (\Smarty\Template::$compile_id)
+ *
+ * @var string
+ */
+ public $compile_id = null;
+
+ /**
+ * Compiled Content Loaded
+ *
+ * @var boolean
+ */
+ protected $processed = false;
+
+ /**
+ * unique function name for compiled template code
+ *
+ * @var string
+ */
+ public $unifunc = '';
+
+ /**
+ * flag if template does contain nocache code sections
+ *
+ * @var bool
+ */
+ private $has_nocache_code = false;
+
+ /**
+ * resource file dependency
+ *
+ * @var array
+ */
+ public $file_dependency = [];
+
+ /**
+ * Get compiled time stamp
+ *
+ * @return int
+ */
+ public function getTimeStamp() {
+ if ($this->exists && !$this->timestamp) {
+ $this->timestamp = filemtime($this->filepath);
+ }
+ return $this->timestamp;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getNocacheCode(): bool {
+ return $this->has_nocache_code;
+ }
+
+ /**
+ * @param bool $has_nocache_code
+ */
+ public function setNocacheCode(bool $has_nocache_code): void {
+ $this->has_nocache_code = $has_nocache_code;
+ }
+
+ /**
+ * get rendered template content by calling compiled or cached template code
+ *
+ * @param string $unifunc function with template code
+ *
+ * @throws \Exception
+ */
+ protected function getRenderedTemplateCode(\Smarty\Template $_template, $unifunc) {
+ $level = ob_get_level();
+ try {
+ if (empty($unifunc) || !function_exists($unifunc)) {
+ throw new \Smarty\Exception("Invalid compiled template for '{$this->filepath}'");
+ }
+ $unifunc($_template);
+ } catch (\Exception $e) {
+ while (ob_get_level() > $level) {
+ ob_end_clean();
+ }
+
+ throw $e;
+ }
+ }
+
+ /**
+ * @param $file_dependency
+ * @param Template $_template
+ *
+ * @return bool
+ * @throws Exception
+ */
+ protected function checkFileDependencies($file_dependency, Template $_template): bool {
+ // check file dependencies at compiled code
+ foreach ($file_dependency as $_file_to_check) {
+
+ $handler = \Smarty\Resource\BasePlugin::load($_template->getSmarty(), $_file_to_check[2]);
+
+ if ($handler instanceof FilePlugin) {
+ if ($_template->getSource()->getResourceName() === $_file_to_check[0]) {
+ // do not recheck current template
+ continue;
+ }
+ $mtime = $handler->getResourceNameTimestamp($_file_to_check[0], $_template->getSmarty(), $_template->getSource()->isConfig);
+ } else {
+
+ if ($handler->checkTimestamps()) {
+ // @TODO this doesn't actually check any dependencies, but only the main source file
+ // and that might to be irrelevant, as the comment "do not recheck current template" above suggests
+ $source = Source::load($_template, $_template->getSmarty());
+ $mtime = $source->getTimeStamp();
+ } else {
+ continue;
+ }
+ }
+
+ if ($mtime === false || $mtime > $_file_to_check[1]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/src/Template/Source.php b/src/Template/Source.php
new file mode 100644
index 00000000..cba8106f
--- /dev/null
+++ b/src/Template/Source.php
@@ -0,0 +1,285 @@
+<?php
+
+namespace Smarty\Template;
+
+use Smarty\Resource\FilePlugin;
+use Smarty\Smarty;
+use Smarty\Template;
+use Smarty\Exception;
+
+/**
+ * Meta-data Container for Template source files
+ * @author Rodney Rehm
+ */
+class Source {
+
+ /**
+ * Unique Template ID
+ *
+ * @var string|null
+ */
+ public $uid = null;
+
+ /**
+ * Template Resource (\Smarty\Template::$template_resource)
+ *
+ * @var string
+ */
+ public $resource = null;
+
+ /**
+ * Resource Type
+ *
+ * @var string
+ */
+ public $type = null;
+
+ /**
+ * Resource Name
+ *
+ * @var string
+ */
+ public $name = null;
+
+ /**
+ * Source Timestamp
+ *
+ * @var int
+ */
+ public $timestamp = null;
+
+ /**
+ * Source Existence
+ *
+ * @var boolean
+ */
+ public $exists = false;
+
+ /**
+ * Source File Base name
+ *
+ * @var string
+ */
+ public $basename = null;
+
+ /**
+ * The Components an extended template is made of
+ *
+ * @var \Smarty\Template\Source[]
+ */
+ public $components = null;
+
+ /**
+ * Resource Handler
+ *
+ * @var \Smarty\Resource\BasePlugin
+ */
+ public $handler = null;
+
+ /**
+ * Smarty instance
+ *
+ * @var Smarty
+ */
+ protected $smarty = null;
+
+ /**
+ * Resource is source
+ *
+ * @var bool
+ */
+ public $isConfig = false;
+
+ /**
+ * Template source content eventually set by default handler
+ *
+ * @var string
+ */
+ public $content = null;
+
+ /**
+ * @var array
+ */
+ static protected $_incompatible_resources = [];
+
+ /**
+ * create Source Object container
+ *
+ * @param Smarty $smarty Smarty instance this source object belongs to
+ * @param string $resource full template_resource
+ * @param string $type type of resource
+ * @param string $name resource name
+ *
+ * @throws \Smarty\Exception
+ * @internal param \Smarty\Resource\Base $handler Resource Handler this source object communicates with
+ */
+ public function __construct(Smarty $smarty, $type, $name) {
+ $this->handler = \Smarty\Resource\BasePlugin::load($smarty, $type);
+
+ $this->smarty = $smarty;
+ $this->resource = $type . ':' . $name;
+ $this->type = $type;
+ $this->name = $name;
+ }
+
+ /**
+ * initialize Source Object for given resource
+ * Either [$_template] or [$smarty, $template_resource] must be specified
+ *
+ * @param Template|null $_template template object
+ * @param Smarty|null $smarty smarty object
+ * @param null $template_resource resource identifier
+ *
+ * @return Source Source Object
+ * @throws Exception
+ */
+ public static function load(
+ Template $_template = null,
+ Smarty $smarty = null,
+ $template_resource = null
+ ) {
+ if ($_template) {
+ $smarty = $_template->getSmarty();
+ $template_resource = $_template->template_resource;
+ }
+ if (empty($template_resource)) {
+ throw new Exception('Source: Missing name');
+ }
+ // parse resource_name, load resource handler, identify unique resource name
+ if (preg_match('/^([A-Za-z0-9_\-]{2,}):([\s\S]*)$/', $template_resource, $match)) {
+ $type = $match[1];
+ $name = $match[2];
+ } else {
+ // no resource given, use default
+ // or single character before the colon is not a resource type, but part of the filepath
+ $type = $smarty->default_resource_type;
+ $name = $template_resource;
+ }
+
+ if (isset(self::$_incompatible_resources[$type])) {
+ throw new Exception("Unable to use resource '{$type}' for " . __METHOD__);
+ }
+
+ // create new source object
+ $source = new static($smarty, $type, $name);
+ $source->handler->populate($source, $_template);
+ if (!$source->exists && static::getDefaultHandlerFunc($smarty)) {
+ $source->_getDefaultTemplate(static::getDefaultHandlerFunc($smarty));
+ $source->handler->populate($source, $_template);
+ }
+ return $source;
+ }
+
+ protected static function getDefaultHandlerFunc(Smarty $smarty) {
+ return $smarty->default_template_handler_func;
+ }
+
+ /**
+ * Get source time stamp
+ *
+ * @return int
+ */
+ public function getTimeStamp() {
+ if (!isset($this->timestamp)) {
+ $this->handler->populateTimestamp($this);
+ }
+ return $this->timestamp;
+ }
+
+ /**
+ * Get source content
+ *
+ * @return string
+ * @throws \Smarty\Exception
+ */
+ public function getContent() {
+ return $this->content ?? $this->handler->getContent($this);
+ }
+
+ /**
+ * get default content from template or config resource handler
+ *
+ * @throws \Smarty\Exception
+ */
+ public function _getDefaultTemplate($default_handler) {
+ $_content = $_timestamp = null;
+ $_return = call_user_func_array(
+ $default_handler,
+ [$this->type, $this->name, &$_content, &$_timestamp, $this->smarty]
+ );
+ if (is_string($_return)) {
+ $this->exists = is_file($_return);
+ if ($this->exists) {
+ $this->timestamp = filemtime($_return);
+ } else {
+ throw new Exception(
+ 'Default handler: Unable to load ' .
+ "default file '{$_return}' for '{$this->type}:{$this->name}'"
+ );
+ }
+ $this->name = $_return;
+ $this->uid = sha1($_return);
+ } elseif ($_return === true) {
+ $this->content = $_content;
+ $this->exists = true;
+ $this->uid = $this->name = sha1($_content);
+ $this->handler = \Smarty\Resource\BasePlugin::load($this->smarty, 'eval');
+ } else {
+ $this->exists = false;
+ throw new Exception(
+ 'Default handler: No ' . ($this->isConfig ? 'config' : 'template') .
+ " default content for '{$this->type}:{$this->name}'"
+ );
+ }
+ }
+
+ public function createCompiler(): \Smarty\Compiler\BaseCompiler {
+ return new \Smarty\Compiler\Template($this->smarty);
+ }
+
+ public function getSmarty() {
+ return $this->smarty;
+ }
+
+ /**
+ * Determine basename for compiled filename
+ *
+ * @return string resource's basename
+ */
+ public function getBasename()
+ {
+ return $this->handler->getBasename($this);
+ }
+
+ /**
+ * Return source name
+ * e.g.: 'sub/index.tpl'
+ *
+ * @return string
+ */
+ public function getResourceName(): string {
+ return (string) $this->name;
+ }
+
+ /**
+ * Return source name, including the type prefix.
+ * e.g.: 'file:sub/index.tpl'
+ *
+ * @return string
+ */
+ public function getFullResourceName(): string {
+ return $this->type . ':' . $this->name;
+ }
+
+ public function getFilepath(): string {
+ if ($this->handler instanceof FilePlugin) {
+ return $this->handler->getFilePath($this->name, $this->smarty, $this->isConfig);
+ }
+ return '.';
+ }
+
+ public function isConfig(): bool {
+ return $this->isConfig;
+ }
+
+}
diff --git a/src/TemplateBase.php b/src/TemplateBase.php
new file mode 100644
index 00000000..11849c9a
--- /dev/null
+++ b/src/TemplateBase.php
@@ -0,0 +1,439 @@
+<?php
+/**
+ * Smarty Internal Plugin Smarty Template Base
+ * This file contains the basic shared methods for template handling
+ *
+
+
+ * @author Uwe Tews
+ */
+
+namespace Smarty;
+
+/**
+ * Class with shared smarty/template methods
+ */
+abstract class TemplateBase extends Data {
+
+ /**
+ * Set this if you want different sets of cache files for the same
+ * templates.
+ *
+ * @var string
+ */
+ public $cache_id = null;
+
+ /**
+ * Set this if you want different sets of compiled files for the same
+ * templates.
+ *
+ * @var string
+ */
+ public $compile_id = null;
+
+ /**
+ * caching enabled
+ *
+ * @var int
+ */
+ public $caching = \Smarty\Smarty::CACHING_OFF;
+
+ /**
+ * check template for modifications?
+ *
+ * @var int
+ */
+ public $compile_check = \Smarty\Smarty::COMPILECHECK_ON;
+
+ /**
+ * cache lifetime in seconds
+ *
+ * @var int
+ */
+ public $cache_lifetime = 3600;
+
+ /**
+ * Array of source information for known template functions
+ *
+ * @var array
+ */
+ public $tplFunctions = [];
+
+ /**
+ * When initialized to an (empty) array, this variable will hold a stack of template variables.
+ *
+ * @var null|array
+ */
+ public $_var_stack = null;
+
+ /**
+ * @var Debug
+ */
+ private $debug;
+
+ /**
+ * Registers object to be used in templates
+ *
+ * @param string $object_name
+ * @param object $object the referenced PHP object to register
+ * @param array $allowed_methods_properties list of allowed methods (empty = all)
+ * @param bool $format smarty argument format, else traditional
+ * @param array $block_methods list of block-methods
+ *
+ * @return \Smarty|\Smarty\Template
+ * @throws \Smarty\Exception
+ * @link https://www.smarty.net/docs/en/api.register.object.tpl
+ *
+ * @api Smarty::registerObject()
+ */
+ public function registerObject(
+ $object_name,
+ $object,
+ $allowed_methods_properties = [],
+ $format = true,
+ $block_methods = []
+ ) {
+ $smarty = $this->getSmarty();
+ // test if allowed methods callable
+ if (!empty($allowed_methods_properties)) {
+ foreach ((array)$allowed_methods_properties as $method) {
+ if (!is_callable([$object, $method]) && !property_exists($object, $method)) {
+ throw new Exception("Undefined method or property '$method' in registered object");
+ }
+ }
+ }
+ // test if block methods callable
+ if (!empty($block_methods)) {
+ foreach ((array)$block_methods as $method) {
+ if (!is_callable([$object, $method])) {
+ throw new Exception("Undefined method '$method' in registered object");
+ }
+ }
+ }
+ // register the object
+ $smarty->registered_objects[$object_name] =
+ [$object, (array)$allowed_methods_properties, (boolean)$format, (array)$block_methods];
+ return $this;
+ }
+
+ /**
+ * Registers plugin to be used in templates
+ *
+ * @param string $object_name name of object
+ *
+ * @return TemplateBase
+ * @api Smarty::unregisterObject()
+ * @link https://www.smarty.net/docs/en/api.unregister.object.tpl
+ *
+ */
+ public function unregisterObject($object_name) {
+ $smarty = $this->getSmarty();
+ if (isset($smarty->registered_objects[$object_name])) {
+ unset($smarty->registered_objects[$object_name]);
+ }
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCompileCheck(): int {
+ return $this->compile_check;
+ }
+
+ /**
+ * @param int $compile_check
+ */
+ public function setCompileCheck($compile_check) {
+ $this->compile_check = (int)$compile_check;
+ }
+
+ /**
+ * @param int $caching
+ */
+ public function setCaching($caching) {
+ $this->caching = (int)$caching;
+ }
+
+ /**
+ * @param int $cache_lifetime
+ */
+ public function setCacheLifetime($cache_lifetime) {
+ $this->cache_lifetime = $cache_lifetime;
+ }
+
+ /**
+ * @param string $compile_id
+ */
+ public function setCompileId($compile_id) {
+ $this->compile_id = $compile_id;
+ }
+
+ /**
+ * @param string $cache_id
+ */
+ public function setCacheId($cache_id) {
+ $this->cache_id = $cache_id;
+ }
+
+ /**
+ * creates a data object
+ *
+ * @param Data|null $parent next higher level of Smarty
+ * variables
+ * @param null $name optional data block name
+ *
+ * @return Data data object
+ * @throws Exception
+ * @api Smarty::createData()
+ * @link https://www.smarty.net/docs/en/api.create.data.tpl
+ *
+ */
+ public function createData(Data $parent = null, $name = null) {
+ /* @var Smarty $smarty */
+ $smarty = $this->getSmarty();
+ $dataObj = new Data($parent, $smarty, $name);
+ if ($smarty->debugging) {
+ $smarty->getDebug()->register_data($dataObj);
+ }
+ return $dataObj;
+ }
+
+ /**
+ * return name of debugging template
+ *
+ * @return string
+ * @api Smarty::getDebugTemplate()
+ *
+ */
+ public function getDebugTemplate() {
+ $smarty = $this->getSmarty();
+ return $smarty->debug_tpl;
+ }
+
+ /**
+ * @return Debug
+ */
+ public function getDebug(): Debug {
+ if (!isset($this->debug)) {
+ $this->debug = new \Smarty\Debug();
+ }
+ return $this->debug;
+ }
+
+
+ /**
+ * return a reference to a registered object
+ *
+ * @param string $object_name object name
+ *
+ * @return object
+ * @throws \Smarty\Exception if no such object is found
+ * @link https://www.smarty.net/docs/en/api.get.registered.object.tpl
+ *
+ * @api Smarty::getRegisteredObject()
+ */
+ public function getRegisteredObject($object_name) {
+ $smarty = $this->getSmarty();
+ if (!isset($smarty->registered_objects[$object_name])) {
+ throw new Exception("'$object_name' is not a registered object");
+ }
+ if (!is_object($smarty->registered_objects[$object_name][0])) {
+ throw new Exception("registered '$object_name' is not an object");
+ }
+ return $smarty->registered_objects[$object_name][0];
+ }
+
+ /**
+ * Get literals
+ *
+ * @return array list of literals
+ * @api Smarty::getLiterals()
+ *
+ */
+ public function getLiterals() {
+ $smarty = $this->getSmarty();
+ return (array)$smarty->literals;
+ }
+
+ /**
+ * Add literals
+ *
+ * @param array|string $literals literal or list of literals
+ * to addto add
+ *
+ * @return TemplateBase
+ * @throws \Smarty\Exception
+ * @api Smarty::addLiterals()
+ *
+ */
+ public function addLiterals($literals = null) {
+ if (isset($literals)) {
+ $this->_setLiterals($this->getSmarty(), (array)$literals);
+ }
+ return $this;
+ }
+
+ /**
+ * Set literals
+ *
+ * @param array|string $literals literal or list of literals
+ * to setto set
+ *
+ * @return TemplateBase
+ * @throws \Smarty\Exception
+ * @api Smarty::setLiterals()
+ *
+ */
+ public function setLiterals($literals = null) {
+ $smarty = $this->getSmarty();
+ $smarty->literals = [];
+ if (!empty($literals)) {
+ $this->_setLiterals($smarty, (array)$literals);
+ }
+ return $this;
+ }
+
+ /**
+ * common setter for literals for easier handling of duplicates the
+ * Smarty::$literals array gets filled with identical key values
+ *
+ * @param Smarty $smarty
+ * @param array $literals
+ *
+ * @throws \Smarty\Exception
+ */
+ private function _setLiterals(Smarty $smarty, $literals) {
+ $literals = array_combine($literals, $literals);
+ $error = isset($literals[$smarty->getLeftDelimiter()]) ? [$smarty->getLeftDelimiter()] : [];
+ $error = isset($literals[$smarty->getRightDelimiter()]) ? $error[] = $smarty->getRightDelimiter() : $error;
+ if (!empty($error)) {
+ throw new Exception(
+ 'User defined literal(s) "' . $error .
+ '" may not be identical with left or right delimiter'
+ );
+ }
+ $smarty->literals = array_merge((array)$smarty->literals, (array)$literals);
+ }
+
+ /**
+ * Registers static classes to be used in templates
+ *
+ * @param string $class_name
+ * @param string $class_impl the referenced PHP class to
+ * register
+ *
+ * @return TemplateBase
+ * @throws \Smarty\Exception
+ * @api Smarty::registerClass()
+ * @link https://www.smarty.net/docs/en/api.register.class.tpl
+ *
+ */
+ public function registerClass($class_name, $class_impl) {
+ $smarty = $this->getSmarty();
+ // test if exists
+ if (!class_exists($class_impl)) {
+ throw new Exception("Undefined class '$class_impl' in register template class");
+ }
+ // register the class
+ $smarty->registered_classes[$class_name] = $class_impl;
+ return $this;
+ }
+
+ /**
+ * Register config default handler
+ *
+ * @param callable $callback class/method name
+ *
+ * @return TemplateBase
+ * @throws Exception if $callback is not callable
+ * @api Smarty::registerDefaultConfigHandler()
+ *
+ */
+ public function registerDefaultConfigHandler($callback) {
+ $smarty = $this->getSmarty();
+ if (is_callable($callback)) {
+ $smarty->default_config_handler_func = $callback;
+ } else {
+ throw new Exception('Default config handler not callable');
+ }
+ return $this;
+ }
+
+ /**
+ * Register template default handler
+ *
+ * @param callable $callback class/method name
+ *
+ * @return TemplateBase
+ * @throws Exception if $callback is not callable
+ * @api Smarty::registerDefaultTemplateHandler()
+ *
+ */
+ public function registerDefaultTemplateHandler($callback) {
+ $smarty = $this->getSmarty();
+ if (is_callable($callback)) {
+ $smarty->default_template_handler_func = $callback;
+ } else {
+ throw new Exception('Default template handler not callable');
+ }
+ return $this;
+ }
+
+ /**
+ * Registers a resource to fetch a template
+ *
+ * @param string $name name of resource type
+ * @param Smarty\Resource\Base $resource_handler instance of Smarty\Resource\Base
+ *
+ * @return \Smarty|\Smarty\Template
+ * @link https://www.smarty.net/docs/en/api.register.resource.tpl
+ *
+ * @api Smarty::registerResource()
+ */
+ public function registerResource($name, \Smarty\Resource\BasePlugin $resource_handler) {
+ $smarty = $this->getSmarty();
+ $smarty->registered_resources[$name] = $resource_handler;
+ return $this;
+ }
+
+ /**
+ * Unregisters a resource to fetch a template
+ *
+ * @param string $type name of resource type
+ *
+ * @return TemplateBase
+ * @api Smarty::unregisterResource()
+ * @link https://www.smarty.net/docs/en/api.unregister.resource.tpl
+ *
+ */
+ public function unregisterResource($type) {
+ $smarty = $this->getSmarty();
+ if (isset($smarty->registered_resources[$type])) {
+ unset($smarty->registered_resources[$type]);
+ }
+ return $this;
+ }
+
+ /**
+ * set the debug template
+ *
+ * @param string $tpl_name
+ *
+ * @return TemplateBase
+ * @throws Exception if file is not readable
+ * @api Smarty::setDebugTemplate()
+ *
+ */
+ public function setDebugTemplate($tpl_name) {
+ $smarty = $this->getSmarty();
+ if (!is_readable($tpl_name)) {
+ throw new Exception("Unknown file '{$tpl_name}'");
+ }
+ $smarty->debug_tpl = $tpl_name;
+ return $this;
+ }
+
+
+
+}
diff --git a/src/TestInstall.php b/src/TestInstall.php
new file mode 100644
index 00000000..e24c3984
--- /dev/null
+++ b/src/TestInstall.php
@@ -0,0 +1,211 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * Smarty Internal TestInstall
+ * Test Smarty installation
+ *
+
+
+ * @author Uwe Tews
+ */
+
+/**
+ * TestInstall class
+ *
+
+
+ */
+class TestInstall
+{
+ /**
+ * diagnose Smarty setup
+ * If $errors is secified, the diagnostic report will be appended to the array, rather than being output.
+ *
+ * @param \Smarty $smarty
+ * @param array $errors array to push results into rather than outputting them
+ *
+ * @return bool status, true if everything is fine, false else
+ */
+ public static function testInstall(Smarty $smarty, &$errors = null)
+ {
+ $status = true;
+ if ($errors === null) {
+ echo "<PRE>\n";
+ echo "Smarty Installation test...\n";
+ echo "Testing template directory...\n";
+ }
+ $_stream_resolve_include_path = function_exists('stream_resolve_include_path');
+ // test if all registered template_dir are accessible
+ foreach ($smarty->getTemplateDir() as $template_dir) {
+ $_template_dir = $template_dir;
+ $template_dir = realpath($template_dir);
+ // resolve include_path or fail existence
+ if (!$template_dir) {
+ $status = false;
+ $message = "FAILED: $_template_dir does not exist";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'template_dir' ] = $message;
+ }
+ continue;
+ }
+ if (!is_dir($template_dir)) {
+ $status = false;
+ $message = "FAILED: $template_dir is not a directory";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'template_dir' ] = $message;
+ }
+ } elseif (!is_readable($template_dir)) {
+ $status = false;
+ $message = "FAILED: $template_dir is not readable";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'template_dir' ] = $message;
+ }
+ } else {
+ if ($errors === null) {
+ echo "$template_dir is OK.\n";
+ }
+ }
+ }
+ if ($errors === null) {
+ echo "Testing compile directory...\n";
+ }
+ // test if registered compile_dir is accessible
+ $__compile_dir = $smarty->getCompileDir();
+ $_compile_dir = realpath($__compile_dir);
+ if (!$_compile_dir) {
+ $status = false;
+ $message = "FAILED: {$__compile_dir} does not exist";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'compile_dir' ] = $message;
+ }
+ } elseif (!is_dir($_compile_dir)) {
+ $status = false;
+ $message = "FAILED: {$_compile_dir} is not a directory";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'compile_dir' ] = $message;
+ }
+ } elseif (!is_readable($_compile_dir)) {
+ $status = false;
+ $message = "FAILED: {$_compile_dir} is not readable";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'compile_dir' ] = $message;
+ }
+ } elseif (!is_writable($_compile_dir)) {
+ $status = false;
+ $message = "FAILED: {$_compile_dir} is not writable";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'compile_dir' ] = $message;
+ }
+ } else {
+ if ($errors === null) {
+ echo "{$_compile_dir} is OK.\n";
+ }
+ }
+ if ($errors === null) {
+ echo "Testing plugins directory...\n";
+ }
+ if ($errors === null) {
+ echo "Testing cache directory...\n";
+ }
+ // test if all registered cache_dir is accessible
+ $__cache_dir = $smarty->getCacheDir();
+ $_cache_dir = realpath($__cache_dir);
+ if (!$_cache_dir) {
+ $status = false;
+ $message = "FAILED: {$__cache_dir} does not exist";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'cache_dir' ] = $message;
+ }
+ } elseif (!is_dir($_cache_dir)) {
+ $status = false;
+ $message = "FAILED: {$_cache_dir} is not a directory";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'cache_dir' ] = $message;
+ }
+ } elseif (!is_readable($_cache_dir)) {
+ $status = false;
+ $message = "FAILED: {$_cache_dir} is not readable";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'cache_dir' ] = $message;
+ }
+ } elseif (!is_writable($_cache_dir)) {
+ $status = false;
+ $message = "FAILED: {$_cache_dir} is not writable";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'cache_dir' ] = $message;
+ }
+ } else {
+ if ($errors === null) {
+ echo "{$_cache_dir} is OK.\n";
+ }
+ }
+ if ($errors === null) {
+ echo "Testing configs directory...\n";
+ }
+ // test if all registered config_dir are accessible
+ foreach ($smarty->getConfigDir() as $config_dir) {
+ $_config_dir = $config_dir;
+ // resolve include_path or fail existence
+ if (!$config_dir) {
+ $status = false;
+ $message = "FAILED: $_config_dir does not exist";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'config_dir' ] = $message;
+ }
+ continue;
+ }
+ if (!is_dir($config_dir)) {
+ $status = false;
+ $message = "FAILED: $config_dir is not a directory";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'config_dir' ] = $message;
+ }
+ } elseif (!is_readable($config_dir)) {
+ $status = false;
+ $message = "FAILED: $config_dir is not readable";
+ if ($errors === null) {
+ echo $message . ".\n";
+ } else {
+ $errors[ 'config_dir' ] = $message;
+ }
+ } else {
+ if ($errors === null) {
+ echo "$config_dir is OK.\n";
+ }
+ }
+ }
+ if ($errors === null) {
+ echo "Tests complete.\n";
+ echo "</PRE>\n";
+ }
+ return $status;
+ }
+}
diff --git a/src/UndefinedVariable.php b/src/UndefinedVariable.php
new file mode 100644
index 00000000..53f13f41
--- /dev/null
+++ b/src/UndefinedVariable.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * class for undefined variable object
+ * This class defines an object for undefined variable handling
+ */
+class UndefinedVariable extends Variable {
+
+ /**
+ * Always returns an empty string.
+ *
+ * @return string
+ */
+ public function __toString() {
+ return '';
+ }
+}
diff --git a/src/Variable.php b/src/Variable.php
new file mode 100644
index 00000000..0e38d125
--- /dev/null
+++ b/src/Variable.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Smarty;
+
+/**
+ * class for the Smarty variable object
+ * This class defines the Smarty variable object
+ *
+
+
+ */
+#[\AllowDynamicProperties]
+class Variable
+{
+ /**
+ * template variable
+ *
+ * @var mixed
+ */
+ public $value = null;
+
+ /**
+ * Other r/w properties for foreach, for, while, etc.
+ */
+ public $step, $total, $first, $last, $key, $show, $iteration, $index = null;
+
+ /**
+ * @param mixed|null $value
+ */
+ public function setValue($value): void {
+ $this->value = $value;
+ }
+
+ /**
+ * if true any output of this variable will be not cached
+ *
+ * @var boolean
+ */
+ private $nocache = false;
+
+ /**
+ * @param bool $nocache
+ */
+ public function setNocache(bool $nocache): void {
+ $this->nocache = $nocache;
+ }
+
+ /**
+ * create Smarty variable object
+ *
+ * @param mixed $value the value to assign
+ * @param boolean $nocache if true any output of this variable will be not cached
+ */
+ public function __construct($value = null, $nocache = false)
+ {
+ $this->value = $value;
+ $this->nocache = $nocache;
+ }
+
+ public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * <<magic>> String conversion
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string)$this->value;
+ }
+
+ /**
+ * Handles ++$a and --$a in templates.
+ *
+ * @param $operator '++' or '--', defaults to '++'
+ *
+ * @return int|mixed
+ * @throws Exception
+ */
+ public function preIncDec($operator = '++') {
+ if ($operator == '--') {
+ return --$this->value;
+ } elseif ($operator == '++') {
+ return ++$this->value;
+ } else {
+ throw new Exception("Invalid incdec operator. Use '--' or '++'.");
+ }
+ return $this->value;
+ }
+
+ /**
+ * Handles $a++ and $a-- in templates.
+ *
+ * @param $operator '++' or '--', defaults to '++'
+ *
+ * @return int|mixed
+ * @throws Exception
+ */
+ public function postIncDec($operator = '++') {
+ if ($operator == '--') {
+ return $this->value--;
+ } elseif ($operator == '++') {
+ return $this->value++;
+ } else {
+ throw new Exception("Invalid incdec operator. Use '--' or '++'.");
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNocache(): bool {
+ return $this->nocache;
+ }
+
+}
diff --git a/src/debug.tpl b/src/debug.tpl
new file mode 100644
index 00000000..cd932566
--- /dev/null
+++ b/src/debug.tpl
@@ -0,0 +1,173 @@
+{capture name='_smarty_debug' assign=debug_output}
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <title>Smarty Debug Console</title>
+ <style>
+ {literal}
+ body, h1, h2, h3, td, th, p {
+ font-family: sans-serif;
+ font-weight: normal;
+ font-size: 0.9em;
+ margin: 1px;
+ padding: 0;
+ }
+
+ h1 {
+ margin: 0;
+ text-align: left;
+ padding: 2px;
+ background-color: #f0c040;
+ color: black;
+ font-weight: bold;
+ font-size: 1.2em;
+ }
+
+ h2 {
+ background-color: #9B410E;
+ color: white;
+ text-align: left;
+ font-weight: bold;
+ padding: 2px;
+ border-top: 1px solid black;
+ }
+
+ h3 {
+ text-align: left;
+ font-weight: bold;
+ color: black;
+ font-size: 0.7em;
+ padding: 2px;
+ }
+
+ body {
+ background: black;
+ }
+
+ p, table, div {
+ background: #f0ead8;
+ }
+
+ p {
+ margin: 0;
+ font-style: italic;
+ text-align: center;
+ }
+
+ table {
+ width: 100%;
+ }
+
+ th, td {
+ font-family: monospace;
+ vertical-align: top;
+ text-align: left;
+ }
+
+ td {
+ color: green;
+ }
+
+ tr:nth-child(odd) {
+ background-color: #eeeeee;
+ }
+
+ tr:nth-child(even) {
+ background-color: #fafafa;
+ }
+
+ .exectime {
+ font-size: 0.8em;
+ font-style: italic;
+ }
+
+ #bold div {
+ color: black;
+ font-weight: bold;
+ }
+
+ #blue h3 {
+ color: blue;
+ }
+
+ #normal div {
+ color: black;
+ font-weight: normal;
+ }
+
+ #table_assigned_vars th {
+ color: blue;
+ font-weight: bold;
+ }
+
+ #table_config_vars th {
+ color: maroon;
+ }
+ {/literal}
+ </style>
+ </head>
+ <body>
+
+ <h1>Smarty {Smarty::SMARTY_VERSION} Debug Console
+ - {if isset($template_name)}{$template_name|debug_print_var nofilter} {/if}{if !empty($template_data)}Total Time {$execution_time|string_format:"%.5f"}{/if}</h1>
+
+ {if !empty($template_data)}
+ <h2>included templates &amp; config files (load time in seconds)</h2>
+ <div>
+ {foreach $template_data as $template}
+ <span style="color: brown;">{$template.name}</span>
+ <br>&nbsp;&nbsp;<span class="exectime">
+ (compile {$template['compile_time']|string_format:"%.5f"}) (render {$template['render_time']|string_format:"%.5f"}) (cache {$template['cache_time']|string_format:"%.5f"})
+ </span>
+ <br>
+ {/foreach}
+ </div>
+ {/if}
+
+ <h2>assigned template variables</h2>
+
+ <table id="table_assigned_vars">
+ {foreach $assigned_vars as $vars}
+ <tr>
+ <td>
+ <h3 style="color: blue;">${$vars@key}</h3>
+ {if isset($vars['nocache'])}<strong>Nocache</strong><br>{/if}
+ {if isset($vars['scope'])}<strong>Origin:</strong> {$vars['scope']|debug_print_var nofilter}{/if}
+ </td>
+ <td>
+ <h3>Value</h3>
+ {$vars['value']|debug_print_var:10:80 nofilter}
+ </td>
+ <td>
+ {if isset($vars['attributes'])}
+ <h3>Attributes</h3>
+ {$vars['attributes']|debug_print_var nofilter}
+ {/if}
+ </td>
+ {/foreach}
+ </table>
+
+ <h2>assigned config file variables</h2>
+
+ <table id="table_config_vars">
+ {foreach $config_vars as $vars}
+ <tr>
+ <td>
+ <h3 style="color: blue;">#{$vars@key}#</h3>
+ {if isset($vars['scope'])}<strong>Origin:</strong> {$vars['scope']|debug_print_var nofilter}{/if}
+ </td>
+ <td>
+ {$vars['value']|debug_print_var:10:80 nofilter}
+ </td>
+ </tr>
+ {/foreach}
+
+ </table>
+ </body>
+ </html>
+{/capture}
+<script type="text/javascript">
+ _smarty_console = window.open("", "console{$targetWindow}", "width=1024,height=600,left={$offset},top={$offset},resizable,scrollbars=yes");
+ _smarty_console.document.write("{$debug_output|escape:'javascript' nofilter}");
+ _smarty_console.document.close();
+</script>
diff --git a/src/functions.php b/src/functions.php
new file mode 100644
index 00000000..5396ffcb
--- /dev/null
+++ b/src/functions.php
@@ -0,0 +1,253 @@
+<?php
+/**
+ * This file is part of the Smarty package.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Smarty\Exception;
+
+/**
+ * Converts the first characters in $string to uppercase (A-Z) if it is an ASCII lowercase character (a-z).
+ *
+ * May not be required when running PHP8.2+: https://wiki.php.net/rfc/strtolower-ascii
+ *
+ * @param $string
+ *
+ * @return string
+ */
+function smarty_ucfirst_ascii($string): string {
+ return smarty_strtoupper_ascii(substr($string, 0, 1)) . substr($string, 1);
+}
+
+/**
+ * Converts all uppercase ASCII characters (A-Z) in $string to lowercase (a-z).
+ *
+ * May not be required when running PHP8.2+: https://wiki.php.net/rfc/strtolower-ascii
+ *
+ * @param $string
+ *
+ * @return string
+ */
+function smarty_strtolower_ascii($string): string {
+ return strtr($string, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+}
+
+/**
+ * Converts all lowercase ASCII characters (a-z) in $string to uppercase (A-Z).
+ *
+ * May not be required when running PHP8.2+: https://wiki.php.net/rfc/strtolower-ascii
+ *
+ * @param $string
+ *
+ * @return string
+ */
+function smarty_strtoupper_ascii($string): string {
+ return strtr($string, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
+}
+
+/**
+ * Function: smarty_make_timestamp
+ * Purpose: used by other smarty functions to make a timestamp from a string.
+ *
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param DateTime|int|string $string date object, timestamp or string that can be converted using strtotime()
+ *
+ * @return int
+ */
+function smarty_make_timestamp($string)
+{
+ if (empty($string)) {
+ // use "now":
+ return time();
+ } elseif ($string instanceof DateTime
+ || (interface_exists('DateTimeInterface', false) && $string instanceof DateTimeInterface)
+ ) {
+ return (int)$string->format('U'); // PHP 5.2 BC
+ } elseif (strlen($string) === 14 && ctype_digit($string)) {
+ // it is mysql timestamp format of YYYYMMDDHHMMSS?
+ return mktime(
+ substr($string, 8, 2),
+ substr($string, 10, 2),
+ substr($string, 12, 2),
+ substr($string, 4, 2),
+ substr($string, 6, 2),
+ substr($string, 0, 4)
+ );
+ } elseif (is_numeric($string)) {
+ // it is a numeric string, we handle it as timestamp
+ return (int)$string;
+ } else {
+ // strtotime should handle it
+ $time = strtotime($string);
+ if ($time === -1 || $time === false) {
+ // strtotime() was not able to parse $string, use "now":
+ return time();
+ }
+ return $time;
+ }
+}
+
+/**
+ * Multibyte string replace
+ *
+ * @param string|string[] $search the string to be searched
+ * @param string|string[] $replace the replacement string
+ * @param string $subject the source string
+ * @param int &$count number of matches found
+ *
+ * @return string replaced string
+ * @author Rodney Rehm
+ */
+function smarty_mb_str_replace($search, $replace, $subject, &$count = 0)
+{
+ if (!is_array($search) && is_array($replace)) {
+ return false;
+ }
+ if (is_array($subject)) {
+ // call mb_replace for each single string in $subject
+ foreach ($subject as &$string) {
+ $string = smarty_mb_str_replace($search, $replace, $string, $c);
+ $count += $c;
+ }
+ } elseif (is_array($search)) {
+ if (!is_array($replace)) {
+ foreach ($search as &$string) {
+ $subject = smarty_mb_str_replace($string, $replace, $subject, $c);
+ $count += $c;
+ }
+ } else {
+ $n = max(count($search), count($replace));
+ while ($n--) {
+ $subject = smarty_mb_str_replace(current($search), current($replace), $subject, $c);
+ $count += $c;
+ next($search);
+ next($replace);
+ }
+ }
+ } else {
+ $mb_reg_charset = mb_regex_encoding();
+ // Check if mbstring regex is using UTF-8
+ $reg_is_unicode = !strcasecmp($mb_reg_charset, "UTF-8");
+ if(!$reg_is_unicode) {
+ // ...and set to UTF-8 if not
+ mb_regex_encoding("UTF-8");
+ }
+
+ // See if charset used by Smarty is matching one used by regex...
+ $current_charset = mb_regex_encoding();
+ $convert_result = (bool)strcasecmp(\Smarty\Smarty::$_CHARSET, $current_charset);
+ if($convert_result) {
+ // ...convert to it if not.
+ $subject = mb_convert_encoding($subject, $current_charset, \Smarty\Smarty::$_CHARSET);
+ $search = mb_convert_encoding($search, $current_charset, \Smarty\Smarty::$_CHARSET);
+ $replace = mb_convert_encoding($replace, $current_charset, \Smarty\Smarty::$_CHARSET);
+ }
+
+ $parts = mb_split(preg_quote($search), $subject ?? "") ?: array();
+ // If original regex encoding was not unicode...
+ if(!$reg_is_unicode) {
+ // ...restore original regex encoding to avoid breaking the system.
+ mb_regex_encoding($mb_reg_charset);
+ }
+ if($parts === false) {
+ // This exception is thrown if call to mb_split failed.
+ // Usually it happens, when $search or $replace are not valid for given mb_regex_encoding().
+ // There may be other cases for it to fail, please file an issue if you find a reproducible one.
+ throw new Exception("Source string is not a valid $current_charset sequence (probably)");
+ }
+
+ $count = count($parts) - 1;
+ $subject = implode($replace, $parts);
+ // Convert results back to charset used by Smarty, if needed.
+ if($convert_result) {
+ $subject = mb_convert_encoding($subject, \Smarty\Smarty::$_CHARSET, $current_charset);
+ }
+ }
+ return $subject;
+}
+/**
+ * escape_special_chars common function
+ * Function: smarty_function_escape_special_chars
+ * Purpose: used by other smarty functions to escape
+ * special chars except for already escaped ones
+ *
+ * @author Monte Ohrt <monte at ohrt dot com>
+ *
+ * @param string $string text that should by escaped
+ *
+ * @return string
+ */
+function smarty_function_escape_special_chars($string)
+{
+ if (!is_array($string)) {
+ $string = htmlspecialchars((string) $string, ENT_COMPAT, \Smarty\Smarty::$_CHARSET, false);
+ }
+ return $string;
+}
+
+/**
+ * Smarty wordwrap supporting multibyte
+ * Name: smarty_mb_wordwrap
+ * Purpose: Wrap a string to a given number of characters
+ *
+ * @link https://php.net/manual/en/function.wordwrap.php for similarity
+ *
+ * @param string $str the string to wrap
+ * @param int $width the width of the output
+ * @param string $break the character used to break the line
+ * @param boolean $cut ignored parameter, just for the sake of
+ *
+ * @return string wrapped string
+ * @author Rodney Rehm
+ */
+function smarty_mb_wordwrap($str, $width = 75, $break = "\n", $cut = false)
+{
+ // break words into tokens using white space as a delimiter
+ $tokens = preg_split('!(\s)!S' . \Smarty\Smarty::$_UTF8_MODIFIER, $str, -1, PREG_SPLIT_NO_EMPTY + PREG_SPLIT_DELIM_CAPTURE);
+ $length = 0;
+ $t = '';
+ $_previous = false;
+ $_space = false;
+ foreach ($tokens as $_token) {
+ $token_length = mb_strlen($_token, \Smarty\Smarty::$_CHARSET);
+ $_tokens = array($_token);
+ if ($token_length > $width) {
+ if ($cut) {
+ $_tokens = preg_split(
+ '!(.{' . $width . '})!S' . \Smarty\Smarty::$_UTF8_MODIFIER,
+ $_token,
+ -1,
+ PREG_SPLIT_NO_EMPTY + PREG_SPLIT_DELIM_CAPTURE
+ );
+ }
+ }
+ foreach ($_tokens as $token) {
+ $_space = !!preg_match('!^\s$!S' . \Smarty\Smarty::$_UTF8_MODIFIER, $token);
+ $token_length = mb_strlen($token, \Smarty\Smarty::$_CHARSET);
+ $length += $token_length;
+ if ($length > $width) {
+ // remove space before inserted break
+ if ($_previous) {
+ $t = mb_substr($t, 0, -1, \Smarty\Smarty::$_CHARSET);
+ }
+ if (!$_space) {
+ // add the break before the token
+ if (!empty($t)) {
+ $t .= $break;
+ }
+ $length = $token_length;
+ }
+ } elseif ($token === "\n") {
+ // hard break must reset counters
+ $length = 0;
+ }
+ $_previous = $_space;
+ // add the token
+ $t .= $token;
+ }
+ }
+ return $t;
+}