Continued:
[core.git] / framework / main / classes / template / class_BaseTemplateEngine.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Template\Engine;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\EntryPoint\ApplicationEntryPoint;
8 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
9 use Org\Mxchange\CoreFramework\Filesystem\FileNotFoundException;
10 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
11 use Org\Mxchange\CoreFramework\Generic\NullPointerException;
12 use Org\Mxchange\CoreFramework\Helper\Application\ApplicationHelper;
13 use Org\Mxchange\CoreFramework\Manager\ManageableApplication;
14 use Org\Mxchange\CoreFramework\Object\BaseFrameworkSystem;
15 use Org\Mxchange\CoreFramework\Response\Responseable;
16 use Org\Mxchange\CoreFramework\Traits\Handler\Io\IoHandlerTrait;
17 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
18
19 // Import SPL stuff
20 use \BadMethodCallException;
21 use \InvalidArgumentException;
22 use \SplFileInfo;
23
24 /**
25  * A generic template engine
26  *
27  * @author              Roland Haeder <webmaster@shipsimu.org>
28  * @version             0.0.0
29  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
30  * @license             GNU GPL 3.0 or any newer version
31  * @link                http://www.shipsimu.org
32  *
33  * This program is free software: you can redistribute it and/or modify
34  * it under the terms of the GNU General Public License as published by
35  * the Free Software Foundation, either version 3 of the License, or
36  * (at your option) any later version.
37  *
38  * This program is distributed in the hope that it will be useful,
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
41  * GNU General Public License for more details.
42  *
43  * You should have received a copy of the GNU General Public License
44  * along with this program. If not, see <http://www.gnu.org/licenses/>.
45  */
46 abstract class BaseTemplateEngine extends BaseFrameworkSystem {
47         // Load traits
48         use IoHandlerTrait;
49
50         // Exception codes for the template engine
51         const EXCEPTION_TEMPLATE_TYPE_IS_UNEXPECTED   = 0x110;
52         const EXCEPTION_TEMPLATE_CONTAINS_INVALID_VAR = 0x111;
53         const EXCEPTION_INVALID_VIEW_HELPER           = 0x112;
54         const EXCEPTION_VARIABLE_IS_MISSING           = 0x113;
55
56         /**
57          * HTML template type
58          */
59         private static $htmlTemplateType = 'invalid';
60
61         /**
62          * The local path name where all templates and sub folders for special
63          * templates are stored. We will internally determine the language plus
64          * "html" for web templates or "emails" for email templates
65          */
66         private $templateBasePath = '';
67
68         /**
69          * Template type
70          */
71         private $templateType = 'html';
72
73         /**
74          * The extension for web and email templates (not compiled templates)
75          */
76         private $templateExtension = '.tpl';
77
78         /**
79          * The extension for code templates (not compiled templates)
80          */
81         private $codeExtension = '.ctp';
82
83         /**
84          * Path relative to $templateBasePath and language code for compiled code-templates
85          */
86         private $compileOutputPath = 'templates/_compiled/';
87
88         /**
89          * The path name for all templates
90          */
91         private $genericBasePath = 'templates/';
92
93         /**
94          * The raw (maybe uncompiled) template
95          */
96         private $rawTemplateData = '';
97
98         /**
99          * Template data with compiled-in variables
100          */
101         private $compiledData = '';
102
103         /**
104          * The last loaded template's file instance (SplFileInfo)
105          */
106         private $lastTemplate = NULL;
107
108         /**
109          * The variable stack for the templates
110          */
111         private $varStack = [];
112
113         /**
114          * Loaded templates for recursive protection and detection
115          */
116         private $loadedTemplates = [];
117
118         /**
119          * Compiled templates for recursive protection and detection
120          */
121         private $compiledTemplates = [];
122
123         /**
124          * Loaded raw template data
125          */
126         private $loadedRawData = NULL;
127
128         /**
129          * Raw templates which are linked in code templates
130          */
131         private $rawTemplates = NULL;
132
133         /**
134          * A regular expression for variable=value pairs
135          */
136         private $regExpVarValue = '/([\w_]+)(="([^"]*)"|=([\w_]+))?/';
137
138         /**
139          * A regular expression for filtering out code tags
140          *
141          * E.g.: {?template:variable=value;var2=value2;[...]?}
142          */
143         private $regExpCodeTags = '/\{\?([a-z_]+)(:("[^"]+"|[^?}]+)+)?\?\}/';
144
145         /**
146          * A regular expression to find template comments like <!-- Comment here //-->
147          */
148         private $regExpComments = '/<!--[\w\W]*?(\/\/){0,1}-->/';
149
150         /**
151          * Loaded helpers
152          */
153         private $helpers = [];
154
155         /**
156          * Current variable group
157          */
158         private $currGroup = 'general';
159
160         /**
161          * All template groups except "general"
162          */
163         private $variableGroups = [];
164
165         /**
166          * Code begin
167          */
168         private $codeBegin = '<?php';
169
170         /**
171          * Code end
172          */
173         private $codeEnd = '?>';
174
175         /**
176          * Language support is enabled by default
177          */
178         private $languageSupport = true;
179
180         /**
181          * Protected constructor
182          *
183          * @param       $className      Name of the class
184          * @return      void
185          */
186         protected function __construct (string $className) {
187                 // Call parent constructor
188                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: className=%s - CONSTRUCTED!', $className));
189                 parent::__construct($className);
190
191                 // Init file I/O instance
192                 $ioInstance = ObjectFactory::createObjectByConfiguredName('file_io_class');
193
194                 // Set it
195                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: ioInstance=%s', $ioInstance->__toString()));
196                 $this->setFileIoInstance($ioInstance);
197
198                 // "Cache" config entry
199                 self::$htmlTemplateType = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('html_template_type');
200
201                 // Trace message
202                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: self::htmlTemplateType=%s - EXIT!', self::$htmlTemplateType));
203         }
204
205         /**
206          * Search for a variable in the stack
207          *
208          * @param       $variableName   The variable we are looking for
209          * @param       $variableGroup  Optional variable group to look in
210          * @return      $index                  false means not found, >=0 means found on a specific index
211          */
212         private function getVariableIndex (string $variableName, string $variableGroup = NULL) {
213                 // Replace all dashes to underscores to match variables with configuration entries
214                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableName=%s,variableGroup[%s]=%s - CALLED!', $variableName, gettype($variableGroup), $variableGroup));
215                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
216
217                 // First everything is not found
218                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableName=%s', $variableName));
219                 $found = false;
220
221                 // If the stack is NULL, use the current group
222                 if (is_null($variableGroup)) {
223                         // Use current group
224                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: this->currGroup=%s set as stack!', $this->currGroup));
225                         $variableGroup = $this->currGroup;
226                 }
227
228                 // Is the group there?
229                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableGroup=%s', $variableGroup));
230                 if ($this->isVarStackSet($variableGroup)) {
231                         // Now search for it
232                         foreach ($this->getVarStack($variableGroup) as $index => $currEntry) {
233                                 // Is the entry found?
234                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: index=%d,currGroup=%s,currEntry[name]=%s,variableName=%s', $index, $variableGroup, $currEntry['name'], $variableName));
235                                 if ($currEntry['name'] == $variableName) {
236                                         // Found!
237                                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: index=%d - BREAK!', $index));
238                                         $found = $index;
239                                         break;
240                                 }
241                         }
242                 }
243
244                 // Return the current position
245                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: found=%d - EXIT!', $found));
246                 return $found;
247         }
248
249         /**
250          * Checks whether the given variable group is set
251          *
252          * @param       $variableGroup  Variable group to check
253          * @return      $isSet                  Whether the given variable group is set
254          * @throws      InvalidArgumentException        If the variable name is left empty
255          */
256         protected final function isVarStackSet (string $variableGroup) {
257                 // Validate parameter
258                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableGroup=%s - CALLED!', $variableGroup));
259                 if (empty($variableGroup)) {
260                         // Throw an exception
261                         throw new InvalidArgumentException('Parameter "variableGroup" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
262                 }
263
264                 // Check it
265                 $isset = isset($this->varStack[$variableGroup]);
266
267                 // Return result
268                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: isset=%d - EXIT!', intval($isset)));
269                 return $isset;
270         }
271
272         /**
273          * Getter for given variable group
274          *
275          * @param       $variableGroup  Variable group to check
276          * @return      $varStack               Found variable group
277          * @throws      InvalidArgumentException        If the variable name is left empty
278          */
279         public final function getVarStack (string $variableGroup) {
280                 // Validate parameter
281                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableGroup=%s - CALLED!', $variableGroup));
282                 if (empty($variableGroup)) {
283                         // Throw an exception
284                         throw new InvalidArgumentException('Parameter "variableGroup" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
285                 }
286
287                 // Return value
288                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: this->varStack[%s]()=%d - EXIT!', $variableGroup, count($this->varStack[$variableGroup])));
289                 return $this->varStack[$variableGroup];
290         }
291
292         /**
293          * Setter for given variable group
294          *
295          * @param       $variableGroup  Variable group to check
296          * @param       $varStack               Variable stack to check
297          * @return      void
298          * @throws      InvalidArgumentException        If the variable name is left empty
299          */
300         protected final function setVarStack (string $variableGroup, array $varStack) {
301                 // Validate parameter
302                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableGroup=%s,varStack()=%d - CALLED!', $variableGroup, count($varStack)));
303                 if (empty($variableGroup)) {
304                         // Throw an exception
305                         throw new InvalidArgumentException('Parameter "variableGroup" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
306                 }
307
308                 // Set var stack
309                 $this->varStack[$variableGroup]  = $varStack;
310
311                 // Trace message
312                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
313         }
314
315         /**
316          * Return a content of a variable or null if not found
317          *
318          * @param       $variableName   The variable we are looking for
319          * @param       $variableGroup  Optional variable group to look in
320          * @return      $content                Content of the variable or null if not found
321          * @throws      InvalidArgumentException        If the variable name is left empty
322          */
323         protected function readVariable (string $variableName, string $variableGroup = NULL) {
324                 // Check parameters
325                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableName=%s,variableGroup[%s]=%s - CALLED!', $variableName, gettype($variableGroup), $variableGroup));
326                 if (empty($variableName)) {
327                         // Throw an exception
328                         throw new InvalidArgumentException('Parameter "variableName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
329                 } elseif (!is_null($variableGroup) && empty($variableGroup)) {
330                         // Throw an exception
331                         throw new InvalidArgumentException('Parameter "variableGroup" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
332                 }
333
334                 // Replace all dashes to underscores to match variables with configuration entries
335                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
336
337                 // First everything is not found
338                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableName=%s', $variableName));
339                 $content = NULL;
340
341                 // If the stack is NULL, use the current group
342                 if (is_null($variableGroup)) {
343                         // Use current group
344                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: this->currGroup=%s set as stack!', $this->currGroup));
345                         $variableGroup = $this->currGroup;
346                 }
347
348                 // Get variable index
349                 $found = $this->getVariableIndex($variableName, $variableGroup);
350
351                 // Is the variable found?
352                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: found[]=%s', gettype($found)));
353                 if ($found !== false) {
354                         // Read it
355                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->getVariableValue(%s,%s) ...', $variableGroup, $found));
356                         $content = $this->getVariableValue($variableGroup, $found);
357                 }
358
359                 // Return the current position
360                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: content()=%d - EXIT!', strlen($content)));
361                 return $content;
362         }
363
364         /**
365          * Add a variable to the stack
366          *
367          * @param       $variableName   Name of variable to add
368          * @param       $value                  Value we want to store in the variable
369          * @return      void
370          */
371         private function addVariable (string $variableName, $value) {
372                 // Set general variable group
373                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableName=%s,value[]=%s - CALLED!', $variableName, gettype($value)));
374                 $this->setVariableGroup('general');
375
376                 // Add it to the stack
377                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->addVariableGroup(%s,value[]=%s) ...', $variableName, gettype($value)));
378                 $this->addGroupVariable($variableName, $value);
379
380                 // Trace message
381                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
382         }
383
384         /**
385          * Returns all variables of current group or empty array
386          *
387          * @return      $result         Whether array of found variables or empty array
388          */
389         private function readCurrentGroup () {
390                 // Default is not found
391                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: CALLED!');
392                 $result = [];
393
394                 // Is the group there?
395                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: this->currGroup=%s', $this->currGroup));
396                 if ($this->isVarStackSet($this->currGroup)) {
397                         // Then use it
398                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->getVarStack(%s) ...', $this->currGroup));
399                         $result = $this->getVarStack($this->currGroup);
400                 }
401
402                 // Return result
403                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: result()=%d - EXIT!', count($result)));
404                 return $result;
405         }
406
407         /**
408          * Settter for variable group
409          *
410          * @param       $groupName      Name of variable group
411          * @param       $add            Whether add this group
412          * @return      void
413          * @throws      InvalidArgumentException        If the variable name is left empty
414          */
415         public function setVariableGroup (string $groupName, bool $add = true) {
416                 // Check parameter
417                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: groupName=%s,add=%d - CALLED!', $groupName, intval($add)));
418                 if (empty($groupName)) {
419                         // Throw an exception
420                         throw new InvalidArgumentException('Parameter "groupName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
421                 }
422
423                 // Set group name
424                 $this->currGroup = $groupName;
425
426                 // Skip group 'general'
427                 if (($groupName != 'general') && ($add === true)) {
428                         // Mark as 'OK'
429                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: Marking groupName=%s as OK ...', $groupdName));
430                         $this->variableGroups[$groupName] = 'OK';
431                 }
432
433                 // Trace message
434                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
435         }
436
437         /**
438          * Adds a variable to current group
439          *
440          * @param       $variableName   Variable to set
441          * @param       $value                  Value to store in variable
442          * @return      void
443          * @throws      InvalidArgumentException        If the variable name is left empty
444          */
445         public function addGroupVariable (string $variableName, $value) {
446                 // Check parameter
447                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableName=%s,value[]=%s - CALLED!', $variableName, gettype($value)));
448                 if (empty($variableName)) {
449                         // Throw an exception
450                         throw new InvalidArgumentException('Parameter "variableName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
451                 } elseif (is_object($value) || is_resource($value)) {
452                         // Throw an exception
453                         throw new InvalidArgumentException(sprintf('value[]=%s is not supported', gettype($value)), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
454                 }
455
456                 // Replace all dashes to underscores to match variables with configuration entries
457                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking StringUtils::convertDashesToUnderscores(%s) ...', $variableName));
458                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
459
460                 // Get current variables in group
461                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: this->currGroup=%s,variableName=%s,value[]=%s', $this->currGroup, $variableName, gettype($value)));
462                 $currVars = $this->readCurrentGroup();
463
464                 // Append our variable
465                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: currVars()=%d - BEFORE!', count($currVars)));
466                 array_push($currVars, $this->generateVariableArray($variableName, $value));
467
468                 // Add it to the stack
469                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->setVarStack(%s,currVars()=%d) - AFTER!', $this->currGroup, count($currVars)));
470                 $this->setVarStack($this->currGroup, $currVars);
471
472                 // Trace message
473                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
474         }
475
476         /**
477          * Getter for variable value, throws a NoVariableException if the variable is not found
478          *
479          * @param       $variableGroup  Variable group to use
480          * @param       $index  Index in variable array
481          * @return      $value  Value to set
482          */
483         private function getVariableValue (string $variableGroup, int $index) {
484                 // Return it
485                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableGroup=%s,index[]=%d - CALLED!', $variableGroup, $index));
486                 $value = $this->varStack[$variableGroup][$index]['value'];
487
488                 // Trace message
489                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: value[]=%s - EXIT!', gettype($value)));
490                 return $value;
491         }
492
493         /**
494          * Modify an entry on the stack
495          *
496          * @param       $variableName   The variable we are looking for
497          * @param       $value                  The value we want to store in the variable
498          * @return      void
499          */
500         private function modifyVariable (string $variableName, $value) {
501                 // Replace all dashes to underscores to match variables with configuration entries
502                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableName=%s,value[]=%s - CALLED!', $variableName, gettype($value)));
503                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
504
505                 // Get index for variable
506                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: this->getVariableIndex(%s) ...', $variableName));
507                 $index = $this->getVariableIndex($variableName);
508
509                 // Then modify it
510                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: this->setVariableValue(%s,%d,value[]=%s) ...', $this->currGroup, $index, gettype($value)));
511                 $this->setVariableValue($this->currGroup, $index, $value);
512
513                 // Trace message
514                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
515         }
516
517         /**
518          * Sets a variable value for given variable group and index
519          *
520          * @param       $variableGroup  Variable group to use
521          * @param       $index          Index in variable array
522          * @param       $value          Value to set
523          * @return      void
524          */
525         private function setVariableValue (string $variableGroup, int $index, $value) {
526                 // Set variable
527                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableGroup=%s,index[]=%d,value[]=%s - CALLED!', $variableGroup, $index, gettype($value)));
528                 $this->varStack[$variableGroup][$index]['value'] = $value;
529
530                 // Trace message
531                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
532         }
533
534         /**
535          * Sets a variable within given group. This method does detect if the
536          * variable is already set. If so, the variable got modified, otherwise
537          * added.
538          *
539          * @param       $variableGroup          Variable group to use
540          * @param       $variableName   Variable to set
541          * @param       $value                  Value to set
542          * @return      void
543          * @throws      InvalidArgumentException        If the variable name is left empty
544          */
545         protected function setVariable (string $variableGroup, string $variableName, $value) {
546                 // Check parameters
547                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableGroup=%s,variableName=%s,value[]=%s - CALLED!', $variableGroup, $variableName, gettype($value)));
548                 if (empty($variableGroup)) {
549                         // Throw an exception
550                         throw new InvalidArgumentException('Parameter "variableGroup" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
551                 } elseif (empty($variableName)) {
552                         // Throw an exception
553                         throw new InvalidArgumentException('Parameter "variableName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
554                 }
555
556                 // Replace all dashes to underscores to match variables with configuration entries
557                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
558
559                 // Get index for variable
560                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: this->getVariableIndex(%s) ...', $variableName));
561                 $index = $this->getVariableIndex($variableName);
562
563                 // Is the variable set?
564                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: index[]=%s', gettype($index)));
565                 if ($index === false) {
566                         // Is the stack there?
567                         if (!isset($this->varStack[$variableGroup])) {
568                                 // Then initialize it here
569                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: Initializing this->varStack[%s] ...', $variableGroup));
570                                 $this->varStack[$variableGroup] = [];
571                         }
572
573                         // Not found, add it
574                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: Pushing variableGroup=%s,variableName=%s,value[]=%s ...', $variableGroup, $variableName, gettype($value)));
575                         array_push($this->varStack[$variableGroup], $this->generateVariableArray($variableName, $value));
576                 } else {
577                         // Then modify it
578                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->setVariableValue(%s,%d,value[]=%s) ...', $this->currGroup, $index, gettype($value)));
579                         $this->setVariableValue($this->currGroup, $index, $value);
580                 }
581
582                 // Trace message
583                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
584         }
585
586         /**
587          * "Generates" (better returns) an array for all variables for given
588          * variable/value pay.
589          *
590          * @param       $variableName   Variable to set
591          * @param       $value                  Value to set
592          * @return      $varData                Variable data array
593          */
594         private function generateVariableArray (string $variableName, $value) {
595                 // Replace all dashes to underscores to match variables with configuration entries
596                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableName=%s,value[]=%s - CALLED!', $variableName, gettype($value)));
597                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
598
599                 // Generate the temporary array
600                 $varData = [
601                         'name'  => $variableName,
602                         'value' => $value
603                 ];
604
605                 // And return it
606                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: varData()=%d - EXIT!', count($varData)));
607                 return $varData;
608         }
609
610         /**
611          * Setter for template type. Only 'html', 'emails' and 'compiled' should
612          * be sent here
613          *
614          * @param       $templateType   The current template's type
615          * @return      void
616          */
617         protected final function setTemplateType (string $templateType) {
618                 $this->templateType = $templateType;
619         }
620
621         /**
622          * Getter for template type
623          *
624          * @return      $templateType   The current template's type
625          */
626         public final function getTemplateType () {
627                 return $this->templateType;
628         }
629
630         /**
631          * Setter for the last loaded template's file instance
632          *
633          * @param       $template       The last loaded template
634          * @return      void
635          */
636         private final function setLastTemplate (SplFileInfo $fileInstance) {
637                 $this->lastTemplate = $fileInstance;
638         }
639
640         /**
641          * Getter for the last loaded template's file instance
642          *
643          * @return      $template       The last loaded template
644          */
645         private final function getLastTemplate () {
646                 return $this->lastTemplate;
647         }
648
649         /**
650          * Setter for base path
651          *
652          * @param               $templateBasePath               The relative base path for all templates
653          * @return      void
654          */
655         protected final function setTemplateBasePath (string $templateBasePath) {
656                 // And set it
657                 $this->templateBasePath = $templateBasePath;
658         }
659
660         /**
661          * Getter for base path
662          *
663          * @return      $templateBasePath               The relative base path for all templates
664          */
665         public final function getTemplateBasePath () {
666                 // And set it
667                 return $this->templateBasePath;
668         }
669
670         /**
671          * Getter for generic base path
672          *
673          * @return      $templateBasePath               The relative base path for all templates
674          */
675         public final function getGenericBasePath () {
676                 // And set it
677                 return $this->genericBasePath;
678         }
679
680         /**
681          * Setter for template extension
682          *
683          * @param               $templateExtension      The file extension for all uncompiled
684          *                                                      templates
685          * @return      void
686          */
687         protected final function setRawTemplateExtension (string $templateExtension) {
688                 // And set it
689                 $this->templateExtension = $templateExtension;
690         }
691
692         /**
693          * Setter for code template extension
694          *
695          * @param               $codeExtension          The file extension for all uncompiled
696          *                                                      templates
697          * @return      void
698          */
699         protected final function setCodeTemplateExtension (string $codeExtension) {
700                 // And set it
701                 $this->codeExtension = $codeExtension;
702         }
703
704         /**
705          * Getter for template extension
706          *
707          * @return      $templateExtension      The file extension for all uncompiled
708          *                                                      templates
709          */
710         public final function getRawTemplateExtension () {
711                 // And set it
712                 return $this->templateExtension;
713         }
714
715         /**
716          * Getter for code-template extension
717          *
718          * @return      $codeExtension          The file extension for all code-
719          *                                                      templates
720          */
721         public final function getCodeTemplateExtension () {
722                 // And set it
723                 return $this->codeExtension;
724         }
725
726         /**
727          * Setter for path of compiled templates
728          *
729          * @param       $compileOutputPath      The local base path for all compiled
730          *                                                              templates
731          * @return      void
732          */
733         protected final function setCompileOutputPath (string $compileOutputPath) {
734                 // And set it
735                 $this->compileOutputPath = $compileOutputPath;
736         }
737
738         /**
739          * Unsets the given offset in the variable group
740          *
741          * @param       $index                  Index to unset
742          * @param       $variableGroup  Variable group (default: currGroup)
743          * @return      void
744          * @throws      InvalidArgumentException        If the variable name is left empty
745          * @throws      BadMethodCallException  If this method was called but combination of variableGroup/index isn't found
746          */
747         protected final function unsetVariableStackOffset (int $index, string $variableGroup = NULL) {
748                 // Check variables
749                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: index=%d,variableGroup[%s]=%s - CALLED!', $index, gettype($variableGroup), $variableGroup));
750                 if ($index < 0) {
751                         // Invalid index
752                         throw new InvalidArgumentException(sprintf('index=%d is below zero', $index), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
753                 } elseif (!is_null($variableGroup) && empty($variableGroup)) {
754                         // Throw an exception
755                         throw new InvalidArgumentException('Parameter "variableGroup" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
756                 }
757
758                 // Is the variable group not set?
759                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableGroup[]=%s', gettype($variableGroup)));
760                 if (is_null($variableGroup)) {
761                         // Then set it to current
762                         $variableGroup = $this->currGroup;
763                 }
764
765                 // Is the entry there?
766                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: Checking variableGroup=%s,index=%d ...', $variableGroup, $index));
767                 if (!isset($this->varStack[$variableGroup][$index])) {
768                         // Abort here, we need fixing!
769                         throw new BadMethodCallException(sprintf('variableGroup=%s,index=%d does not exist, but method was invoked', $variableGroup, $index), FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
770                 }
771
772                 // Remove it
773                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: Unsetting variableGroup=%s,index=%d ...', $variableGroup, $index));
774                 unset($this->varStack[$variableGroup][$index]);
775
776                 // Trace message
777                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
778         }
779
780         /**
781          * Private setter for raw template data
782          *
783          * @param       $rawTemplateData        The raw data from the template
784          * @return      void
785          */
786         protected final function setRawTemplateData (string $rawTemplateData) {
787                 // And store it in this class
788                 $this->rawTemplateData = $rawTemplateData;
789         }
790
791         /**
792          * Getter for raw template data
793          *
794          * @return      $rawTemplateData        The raw data from the template
795          */
796         public final function getRawTemplateData () {
797                 return $this->rawTemplateData;
798         }
799
800         /**
801          * Private setter for compiled templates
802          *
803          * @param       $compiledData   Compiled template data
804          * @return      void
805          */
806         private final function setCompiledData (string $compiledData) {
807                 // And store it in this class
808                 $this->compiledData = $compiledData;
809         }
810
811         /**
812          * Getter for compiled templates, must be public for e.g. Mailer classes.
813          *
814          * @return      $compiledData   Compiled template data
815          */
816         public final function getCompiledData () {
817                 return $this->compiledData;
818         }
819
820         /**
821          * Private loader for all template types
822          *
823          * @param       $templateName   The template we shall load
824          * @param       $extOther       An other extension to use
825          * @return      void
826          * @throws      InvalidArgumentException        If the variable name is left empty
827          * @throws      FileNotFoundException   If the template was not found
828          */
829         protected function loadTemplate (string $templateName, string $extOther = '') {
830                 // Check parameter
831                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: templateName=%s,extOther=%s - CALLED!', $templateName, $extOther));
832                 if (empty($templateName)) {
833                         // Throw IAE
834                         throw new InvalidArgumentException('Parameter "templateName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
835                 }
836
837                 // Get extension for the template if empty
838                 if (empty($extOther)) {
839                         // None provided, so get the raw one
840                         $ext = $this->getRawTemplateExtension();
841                 } else {
842                         // Then use it!
843                         $ext = $extOther;
844                 }
845
846                 /*
847                  * Construct the FQFN for the template without language as language is
848                  * now entirely done by php_intl. These old thing with language-based
849                  * template paths comes from an older time.
850                  */
851                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: ext=%s', $ext));
852                 $fileInstance = new SplFileInfo(sprintf('%s%s%s%s%s%s',
853                         $this->getTemplateBasePath(),
854                         $this->getGenericBasePath(),
855                         $this->getTemplateType(),
856                         DIRECTORY_SEPARATOR,
857                         (string) $templateName,
858                         $ext
859                 ));
860
861                 // First try this
862                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: fileInstance=%s', $fileInstance->__toString()));
863                 try {
864                         // Load the raw template data
865                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->loadRawTemplateData(%s) ...', $fileInstance->__toString()));
866                         $this->loadRawTemplateData($fileInstance);
867                 } catch (FileNotFoundException $e) {
868                         // If we shall load a code-template we need to switch the file extension
869                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: this->templateType=%s,self::htmlTemplateType=%s,extOther=%s', $this->getTemplateType(), self::$htmlTemplateType, $extOther));
870                         if (($this->getTemplateType() != self::$htmlTemplateType) && (empty($extOther))) {
871                                 // Switch over to the code-template extension and try it again
872                                 $ext = $this->getCodeTemplateExtension();
873
874                                 // Try it again...
875                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->loadTemplate(%s,%s) ...', $templateName, $ex));
876                                 $this->loadTemplate($templateName, $ext);
877                         } else {
878                                 // Throw it again
879                                 throw new FileNotFoundException($fileInstance, self::EXCEPTION_FILE_NOT_FOUND);
880                         }
881                 }
882
883                 // Trace message
884                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
885         }
886
887         /**
888          * A private loader for raw template names
889          *
890          * @param       $fileInstance   An instance of a SplFileInfo class
891          * @return      void
892          */
893         private function loadRawTemplateData (SplFileInfo $fileInstance) {
894                 // Load the raw template
895                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: fileInstance=%s - CALLED!', $fileInstance->__toString()));
896                 $rawTemplateData = $this->getFileIoInstance()->loadFileContents($fileInstance);
897
898                 // Store the template's contents into this class
899                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->setRawTemplateData(%d) ...', strlen($rawTemplateData)));
900                 $this->setRawTemplateData($rawTemplateData);
901
902                 // Remember the template's file instance
903                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->setLastTemplate(%s) ...', $fileInstance->__toString()));
904                 $this->setLastTemplate($fileInstance);
905
906                 // Trace message
907                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
908         }
909
910         /**
911          * Try to assign an extracted template variable as a "content" or 'config'
912          * variable.
913          *
914          * @param       $variableName   The variable's name (shall be content or config)
915          *                                                      by default
916          * @param       $configKey      Possible configuration key
917          * @return      void
918          */
919         private function assignTemplateVariable (string $variableName, string $configKey = '') {
920                 // Replace all dashes to underscores to match variables with configuration entries
921                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: variableName=%s,configKey=%s - CALLED!', $variableName, $configKey));
922                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
923
924                 // Is it not a config variable?
925                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableName=%s', $variableName));
926                 if ($variableName != 'config') {
927                         // Regular template variables
928                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->assignVariable(%s,"") ...', $variableName));
929                         $this->assignVariable($variableName, '');
930                 } else {
931                         // Configuration variables
932                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->assignConfigVariable(%s,"") ...', $configKey));
933                         $this->assignConfigVariable($configKey);
934                 }
935
936                 // Trace message
937                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
938         }
939
940         /**
941          * Extract variables from a given raw data stream
942          *
943          * @param       $rawData        The raw template data we shall analyze
944          * @return      void
945          */
946         private function extractVariablesFromRawData (string $rawData) {
947                 // Search for variables
948                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: rawData(%d)=%s - CALLED!', strlen($rawData), $rawData));
949                 preg_match_all('/\$(\w+)(\[(\w+)\])?/', $rawData, $variableMatches);
950
951                 // Did we find some variables?
952                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableMatches[]=%s', gettype($variableMatches)));
953                 if ((is_array($variableMatches)) && (count($variableMatches) == 4) && (count($variableMatches[0]) > 0)) {
954                         // Initialize all missing variables
955                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableMatches()=%d', count($variableMatches)));
956                         foreach ($variableMatches[3] as $key => $var) {
957                                 // Variable name
958                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: key=%s,var=%s', $key, $var));
959                                 $variableName = $variableMatches[1][$key];
960
961                                 // Workarround: Do not assign empty variables
962                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: variableName=%s', $variableName));
963                                 if (!empty($var)) {
964                                         // Try to assign it, empty strings are being ignored
965                                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->assignTemplateVariable(%s,%s) ...', $variableName, $var));
966                                         $this->assignTemplateVariable($variableName, $var);
967                                 }
968                         }
969                 }
970
971                 // Trace message
972                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
973         }
974
975         /**
976          * Main analysis of the loaded template
977          *
978          * @param       $templateMatches        Found template place-holders, see below
979          * @return      void
980          *
981          *---------------------------------
982          * Structure of $templateMatches:
983          *---------------------------------
984          * [0] => Array - An array with all full matches
985          * [1] => Array - An array with left part (before the ':') of a match
986          * [2] => Array - An array with right part of a match including ':'
987          * [3] => Array - An array with right part of a match excluding ':'
988          */
989         private function analyzeTemplate (array $templateMatches) {
990                 // Backup raw template data
991                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: templateMatches()=%d', count($templateMatches)));
992                 $backup = $this->getRawTemplateData();
993
994                 // Initialize some arrays
995                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: backup[%s]()=%d,this->loadedRawData[]=%s', gettype($backup), strlen($backup), gettype($this->loadedRawData)));
996                 if (is_null($this->loadedRawData)) {
997                         // Initialize both
998                         $this->loadedRawData = [];
999                         $this->rawTemplates = [];
1000                 }
1001
1002                 // Load all requested templates
1003                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: templateMatches[1]()=%d', count($templateMatches[1])));
1004                 foreach ($templateMatches[1] as $template) {
1005                         // Load and compile only templates which we have not yet loaded
1006                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: template=%s', $template));
1007                         // RECURSIVE PROTECTION! BE CAREFUL HERE!
1008                         if ((!isset($this->loadedRawData[$template])) && (!in_array($template, $this->loadedTemplates))) {
1009                                 // Template not found, but maybe variable assigned?
1010                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-TEMPLATE: template=%s not loaded yet', $template));
1011                                 if ($this->getVariableIndex($template) !== false) {
1012                                         // Use that content here
1013                                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-TEMPLATE: Assigning this->loadedTemplateData[%s] from variable template=%s ...', $template, $template));
1014                                         $this->loadedRawData[$template] = $this->readVariable($template);
1015
1016                                         // Recursive protection:
1017                                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-TEMPLATE: Marking template=%s as loaded ...', $template));
1018                                         array_push($this->loadedTemplates, $template);
1019                                 } else {
1020                                         // Then try to search for code-templates
1021                                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: template=%s is maybe name of code template?', $template));
1022                                         try {
1023                                                 // Load the code template ...
1024                                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->loadCodeTemplate(%s) ...', $template));
1025                                                 $this->loadCodeTemplate($template);
1026
1027                                                 // ... and remember it's contents
1028                                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-TEMPLATE: Setting this->loadedRawData[%s] from this->rawTemplateData ...', $template));
1029                                                 $this->loadedRawData[$template] = $this->getRawTemplateData();
1030
1031                                                 // Remember this template for recursion detection
1032                                                 // RECURSIVE PROTECTION!
1033                                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-TEMPLATE: Marking template=%s as loaded ...', $template));
1034                                                 array_push($this->loadedTemplates, $template);
1035                                         } catch (FileNotFoundException $e) {
1036                                                 // Even this is not done... :/
1037                                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-TEMPLATE: Marking template=%s as loaded ...', $template));
1038                                                 array_push($this->rawTemplates, $template);
1039                                         }
1040                                 }
1041                         }
1042                 }
1043
1044                 // Restore the raw template data
1045                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->setRawTemplateData(backup()=%d) ...', strlen($backup)));
1046                 $this->setRawTemplateData($backup);
1047
1048                 // Trace message
1049                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
1050         }
1051
1052         /**
1053          * Compile a given raw template code and remember it for later usage
1054          *
1055          * @param       $code           The raw template code
1056          * @param       $template       The template's name
1057          * @return      void
1058          */
1059         private function compileCode (string $code, string $template) {
1060                 // Is this template already compiled?
1061                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: code=%s,template=%s - CALLED!', $code, $template));
1062                 if (in_array($template, $this->compiledTemplates)) {
1063                         // Abort here...
1064                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Found template=%s in this->compiledTemplates - EXIT!', $template));
1065                         return;
1066                 }
1067
1068                 // Remember this template being compiled
1069                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: Adding template=%s to this->compiledTemplates ...', $template));
1070                 array_push($this->compiledTemplates, $template);
1071
1072                 // Compile the loaded code in five steps:
1073                 //
1074                 // 1. Backup current template data
1075                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: this->compiledTemplates()=%d', count($this->compiledTemplates)));
1076                 $backup = $this->getRawTemplateData();
1077
1078                 // 2. Set the current template's raw data as the new content
1079                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->setRawTemplateData(code()=%d) ... - backup[%s]()=%d', strlen($code), gettype($backup), strlen($backup)));
1080                 $this->setRawTemplateData($code);
1081
1082                 // 3. Compile the template data
1083                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: Invoking this->compileTemplate() ...');
1084                 $this->compileTemplate();
1085
1086                 // 4. Remember it's contents
1087                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-TEMPLATE: Setting this->loadedRawData[%s] from this->rawTemplateData ...', $template));
1088                 $this->loadedRawData[$template] = $this->getRawTemplateData();
1089
1090                 // 5. Restore the previous raw content from backup variable
1091                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: Invoking this->setRawTemplateData(backup()=%d) ...', strlen($backup)));
1092                 $this->setRawTemplateData($backup);
1093
1094                 // Trace message
1095                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
1096         }
1097
1098         /**
1099          * Insert all given and loaded templates by running through all loaded
1100          * codes and searching for their place-holder in the main template
1101          *
1102          * @param       $templateMatches        See method analyzeTemplate()
1103          * @return      void
1104          */
1105         private function insertAllTemplates (array $templateMatches) {
1106                 // Run through all loaded codes
1107                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-TEMPLATE: templateMatches()=%d', count($templateMatches)));
1108                 foreach ($this->loadedRawData as $template => $code) {
1109                         // Search for the template
1110                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: template=%s,code()=%d', $template, strlen($code)));
1111                         $foundIndex = array_search($template, $templateMatches[1]);
1112
1113                         // Lookup the matching template replacement
1114                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: foundIndex[%s]=d', gettype($foundIndex), $foundIndex));
1115                         if (($foundIndex !== false) && (isset($templateMatches[0][$foundIndex]))) {
1116                                 // Get the current raw template
1117                                 $rawData = $this->getRawTemplateData();
1118
1119                                 // Replace the space holder with the template code
1120                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: rawData()=%s - BEFORE!', strlen($rawData)));
1121                                 $rawData = str_replace($templateMatches[0][$foundIndex], $code, $rawData);
1122
1123                                 // Set the new raw data
1124                                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-TEMPLATE: rawData()=%s - AFTER!', strlen($rawData)));
1125                                 $this->setRawTemplateData($rawData);
1126                         }
1127                 }
1128
1129                 // Trace message
1130                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-TEMPLATE: EXIT!');
1131         }
1132
1133         /**
1134          * Load all extra raw templates
1135          *
1136          * @return      void
1137          */
1138         private function loadExtraRawTemplates () {
1139                 // Are there some raw templates we need to load?
1140                 if (count($this->rawTemplates) > 0) {
1141                         // Try to load all raw templates
1142                         foreach ($this->rawTemplates as $key => $template) {
1143                                 try {
1144                                         // Load the template
1145                                         $this->loadHtmlTemplate($template);
1146
1147                                         // Remember it's contents
1148                                         $this->rawTemplates[$template] = $this->getRawTemplateData();
1149
1150                                         // Remove it from the loader list
1151                                         unset($this->rawTemplates[$key]);
1152
1153                                         // Remember this template for recursion detection
1154                                         // RECURSIVE PROTECTION!
1155                                         array_push($this->loadedTemplates, $template);
1156                                 } catch (FileNotFoundException $e) {
1157                                         // This template was never found. We silently ignore it
1158                                         unset($this->rawTemplates[$key]);
1159                                 }
1160                         }
1161                 }
1162         }
1163
1164         /**
1165          * Assign all found template variables
1166          *
1167          * @param       $varMatches             An array full of variable/value pairs.
1168          * @return      void
1169          * @todo        Unfinished work or don't die here.
1170          */
1171         private function assignAllVariables (array $varMatches) {
1172                 // Search for all variables
1173                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:varMatches()=' . count($varMatches));
1174                 foreach ($varMatches[1] as $key => $var) {
1175                         // Replace all dashes to underscores to match variables with configuration entries
1176                         $var = trim(StringUtils::convertDashesToUnderscores($var));
1177
1178                         // Debug message
1179                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:key=' . $key . ',var=' . $var);
1180
1181                         // Detect leading equals
1182                         if (substr($varMatches[2][$key], 0, 1) == '=') {
1183                                 // Remove and cast it
1184                                 $varMatches[2][$key] = (string) substr($varMatches[2][$key], 1);
1185                         }
1186
1187                         // Do we have some quotes left and right side? Then it is free text
1188                         if ((substr($varMatches[2][$key], 0, 1) == '"') && (substr($varMatches[2][$key], -1, 1) == '"')) {
1189                                 // Free string detected! Which we can assign directly
1190                                 $this->assignVariable($var, $varMatches[3][$key]);
1191                         } elseif (!empty($varMatches[2][$key])) {
1192                                 // @TODO Non-string found so we need some deeper analysis...
1193                                 ApplicationEntryPoint::exitApplication('Deeper analysis not yet implemented!');
1194                         }
1195                 }
1196         }
1197
1198         /**
1199          * Compiles all loaded raw templates
1200          *
1201          * @param       $templateMatches        See method analyzeTemplate() for details
1202          * @return      void
1203          */
1204         private function compileRawTemplateData (array $templateMatches) {
1205                 // Are some code-templates found which we need to compile?
1206                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:loadedRawData()= ' .count($this->loadedRawData));
1207                 if (count($this->loadedRawData) > 0) {
1208                         // Then compile all!
1209                         foreach ($this->loadedRawData as $template => $code) {
1210                                 // Debug message
1211                                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:template=' . $template . ',code(' . strlen($code) . ')=' . $code);
1212
1213                                 // Is this template already compiled?
1214                                 if (in_array($template, $this->compiledTemplates)) {
1215                                         // Then skip it
1216                                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: Template ' . $template . ' already compiled. SKIPPED!');
1217                                         continue;
1218                                 }
1219
1220                                 // Search for the template
1221                                 $foundIndex = array_search($template, $templateMatches[1]);
1222
1223                                 // Lookup the matching variable data
1224                                 if (($foundIndex !== false) && (isset($templateMatches[3][$foundIndex]))) {
1225                                         // Split it up with another reg. exp. into variable=value pairs
1226                                         preg_match_all($this->regExpVarValue, $templateMatches[3][$foundIndex], $varMatches);
1227                                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:varMatches=' . print_r($varMatches, true));
1228
1229                                         // Assign all variables
1230                                         $this->assignAllVariables($varMatches);
1231                                 }
1232
1233                                 // Compile the loaded template
1234                                 $this->compileCode($code, $template);
1235                         }
1236
1237                         // Insert all templates
1238                         $this->insertAllTemplates($templateMatches);
1239                 }
1240         }
1241
1242         /**
1243          * Inserts all raw templates into their respective variables
1244          *
1245          * @return      void
1246          */
1247         private function insertRawTemplates () {
1248                 // Load all templates
1249                 foreach ($this->rawTemplates as $template => $content) {
1250                         // Set the template as a variable with the content
1251                         $this->assignVariable($template, $content);
1252                 }
1253         }
1254
1255         /**
1256          * Finalizes the compilation of all template variables
1257          *
1258          * @return      void
1259          */
1260         private function finalizeVariableCompilation () {
1261                 // Get the content
1262                 $content = $this->getRawTemplateData();
1263                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: content before=' . strlen($content) . ' (' . md5($content) . ')');
1264
1265                 // Walk through all variables
1266                 foreach ($this->getVarStack('general') as $currEntry) {
1267                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: name=' . $currEntry['name'] . ', value=<pre>' . htmlentities($currEntry['value']) . '</pre>');
1268                         // Replace all [$var] or {?$var?} with the content
1269                         // @TODO Old behaviour, will become obsolete!
1270                         $content = str_replace('$content[' . $currEntry['name'] . ']', $currEntry['value'], $content);
1271
1272                         // @TODO Yet another old way
1273                         $content = str_replace('[' . $currEntry['name'] . ']', $currEntry['value'], $content);
1274
1275                         // The new behaviour
1276                         $content = str_replace('{?' . $currEntry['name'] . '?}', $currEntry['value'], $content);
1277                 }
1278
1279                 // Set the content back
1280                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: content after=' . strlen($content) . ' (' . md5($content) . ')');
1281                 $this->setRawTemplateData($content);
1282         }
1283
1284         /**
1285          * Load a specified HTML template into the engine
1286          *
1287          * @param       $template       The web template we shall load which is located in
1288          *                                              'html' by default
1289          * @return      void
1290          * @throws      InvalidArgumentException        If the variable name is left empty
1291          */
1292         public function loadHtmlTemplate (string $template) {
1293                 // Validate parameter
1294                 if (empty($template)) {
1295                         // Throw an exception
1296                         throw new InvalidArgumentException('Parameter "template" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1297                 }
1298
1299                 // Set template type
1300                 $this->setTemplateType(self::$htmlTemplateType);
1301
1302                 // Load the special template
1303                 $this->loadTemplate($template);
1304         }
1305
1306         /**
1307          * Assign (add) a given variable with a value
1308          *
1309          * @param       $variableName   The variable we are looking for
1310          * @param       $value                  The value we want to store in the variable
1311          * @return      void
1312          * @throws      InvalidArgumentException        If the variable name is left empty
1313          */
1314         public final function assignVariable (string $variableName, $value) {
1315                 // Validate parameter
1316                 if (empty($variableName)) {
1317                         // Throw an exception
1318                         throw new InvalidArgumentException('Parameter "variableName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1319                 }
1320
1321                 // Replace all dashes to underscores to match variables with configuration entries
1322                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
1323
1324                 // First search for the variable if it was already added
1325                 $index = $this->getVariableIndex($variableName);
1326
1327                 // Was it found?
1328                 if ($index === false) {
1329                         // Add it to the stack
1330                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:ADD: ' . $variableName . '[' . gettype($value) . ']=' . $value);
1331                         $this->addVariable($variableName, $value);
1332                 } elseif (!empty($value)) {
1333                         // Modify the stack entry
1334                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:MOD: ' . $variableName . '[' . gettype($value) . ']=' . $value);
1335                         $this->modifyVariable($variableName, $value);
1336                 }
1337         }
1338
1339         /**
1340          * Removes a given variable
1341          *
1342          * @param       $variableName   The variable we are looking for
1343          * @param       $variableGroup  Name of variable group (default: 'general')
1344          * @return      void
1345          * @throws      InvalidArgumentException        If the variable name is left empty
1346          */
1347         public final function removeVariable (string $variableName, string $variableGroup = 'general') {
1348                 // Validate parameter
1349                 if (empty($variableName)) {
1350                         // Throw an exception
1351                         throw new InvalidArgumentException('Parameter "variableName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1352                 } elseif (empty($variableGroup)) {
1353                         // Throw an exception
1354                         throw new InvalidArgumentException('Parameter "variableGroup" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1355                 }
1356
1357                 // First search for the variable if it was already added
1358                 $index = $this->getVariableIndex($variableName, $variableGroup);
1359
1360                 // Was it found?
1361                 if ($index !== false) {
1362                         // Remove this variable
1363                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:UNSET: variableGroup=' . $variableGroup . ',variableName=' . $variableName . ',index=' . $index);
1364                         $this->unsetVariableStackOffset($index, $variableGroup);
1365                 }
1366         }
1367
1368         /**
1369          * Assigns the last loaded raw template content with a given variable
1370          *
1371          * @param       $templateName   Name of the template we want to assign
1372          * @param       $variableName   Name of the variable we want to assign
1373          * @return      void
1374          * @throws      InvalidArgumentException        If the variable name is left empty
1375          */
1376         public function assignTemplateWithVariable (string $templateName, string $variableName) {
1377                 // Validate parameter
1378                 if (empty($templateName)) {
1379                         // Throw an exception
1380                         throw new InvalidArgumentException('Parameter "templateName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1381                 } elseif (empty($variableName)) {
1382                         // Throw an exception
1383                         throw new InvalidArgumentException('Parameter "variableName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1384                 }
1385
1386                 // Get the content from last loaded raw template
1387                 $content = $this->getRawTemplateData();
1388
1389                 // Assign the variable
1390                 $this->assignVariable($variableName, $content);
1391
1392                 // Purge raw content
1393                 $this->setRawTemplateData('');
1394         }
1395
1396         /**
1397          * Assign a given congfiguration variable with a value
1398          *
1399          * @param       $variableName   The configuration variable we want to assign
1400          * @return      void
1401          * @throws      InvalidArgumentException        If the variable name is left empty
1402          */
1403         public function assignConfigVariable (string $variableName) {
1404                 // Validate parameter
1405                 if (empty($variableName)) {
1406                         // Throw an exception
1407                         throw new InvalidArgumentException('Parameter "variableName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1408                 }
1409
1410                 // Replace all dashes to underscores to match variables with configuration entries
1411                 $variableName = trim(StringUtils::convertDashesToUnderscores($variableName));
1412
1413                 // Sweet and simple...
1414                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: variableName=' . $variableName . ',getConfigEntry()=' . FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($variableName));
1415                 $this->assignVariable($variableName, FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($variableName));
1416         }
1417
1418         /**
1419          * Assigns a lot variables into the stack of currently loaded template.
1420          * This method should only be used in very rare circumstances, e.g. when
1421          * you have to copy a whole set of variables into the template engine.
1422          * Before you use this method, please make sure you have considered all
1423          * other possiblities.
1424          *
1425          * @param       $variables      An array with variables to be assigned
1426          * @return      void
1427          */
1428         public function assignMultipleVariables (array $variables) {
1429                 // "Inject" all
1430                 foreach ($variables as $name => $value) {
1431                         // Set variable with name for 'config' group
1432                         $this->assignVariable($name, $value);
1433                 }
1434         }
1435
1436         /**
1437          * Assigns all the application data with template variables
1438          *
1439          * @return      void
1440          */
1441         public function assignApplicationData () {
1442                 // Get application instance
1443                 $applicationInstance = ApplicationHelper::getSelfInstance();
1444
1445                 // Get long name and assign it
1446                 $this->assignVariable('app_full_name' , $applicationInstance->getAppName());
1447
1448                 // Get short name and assign it
1449                 $this->assignVariable('app_short_name', $applicationInstance->getAppShortName());
1450
1451                 // Get version number and assign it
1452                 $this->assignVariable('app_version'   , $applicationInstance->getAppVersion());
1453
1454                 // Assign extra application-depending data
1455                 $applicationInstance->assignExtraTemplateData($this);
1456         }
1457
1458         /**
1459          * Load a specified code template into the engine
1460          *
1461          * @param       $template       The code template we shall load which is
1462          *                                              located in 'code' by default
1463          * @return      void
1464          * @throws      InvalidArgumentException        If the variable name is left empty
1465          */
1466         public function loadCodeTemplate (string $template) {
1467                 // Validate parameter
1468                 if (empty($template)) {
1469                         // Throw an exception
1470                         throw new InvalidArgumentException('Parameter "template" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1471                 }
1472
1473                 // Set template type
1474                 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('code_' . FrameworkBootstrap::getRequestTypeFromSystem() . '_template_type'));
1475
1476                 // Load the special template
1477                 $this->loadTemplate($template);
1478         }
1479
1480         /**
1481          * Load a specified email template into the engine
1482          *
1483          * @param       $template       The email template we shall load which is
1484          *                                              located in 'emails' by default
1485          * @return      void
1486          * @throws      InvalidArgumentException        If the variable name is left empty
1487          */
1488         public function loadEmailTemplate (string $template) {
1489                 // Validate parameter
1490                 if (empty($template)) {
1491                         // Throw an exception
1492                         throw new InvalidArgumentException('Parameter "template" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1493                 }
1494
1495                 // Set template type
1496                 $this->setTemplateType(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('email_template_type'));
1497
1498                 // Load the special template
1499                 $this->loadTemplate($template);
1500         }
1501
1502         /**
1503          * Compiles configuration place-holders in all variables. This 'walks'
1504          * through the variable group 'general'. It interprets all values from that
1505          * variables as configuration entries after compiling them.
1506          *
1507          * @return      void
1508          */
1509         public final function compileConfigInVariables () {
1510                 // Do we have the stack?
1511                 if (!$this->isVarStackSet('general')) {
1512                         // Abort here silently
1513                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: Aborted, variable stack general not found!');
1514                         return;
1515                 }
1516
1517                 // Iterate through all general variables
1518                 foreach ($this->getVarStack('general') as $index => $currVariable) {
1519                         // Compile the value
1520                         $value = $this->compileRawCode($this->readVariable($currVariable['name']), true);
1521
1522                         // Debug message
1523                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: name=' . $currVariable['name'] . ',value=' . $value);
1524
1525                         // Remove it from stack
1526                         $this->removeVariable($currVariable['name'], 'general');
1527                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: value='. $value . ',name=' . $currVariable['name'] . ',index=' . $index);
1528
1529                         // Is it a configuration key?
1530                         if (FrameworkBootstrap::getConfigurationInstance()->isConfigurationEntrySet($value)) {
1531                                 // The value itself is a configuration entry
1532                                 $this->assignConfigVariable($value);
1533                         } else {
1534                                 // Re-assign the value directly
1535                                 $this->assignVariable($currVariable['name'], $value);
1536                         }
1537                 }
1538         }
1539
1540         /**
1541          * Compile all variables by inserting their respective values
1542          *
1543          * @return      void
1544          * @todo        Make this code some nicer...
1545          */
1546         public final function compileVariables () {
1547                 // Initialize the $content array
1548                 $validVar = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('tpl_valid_var');
1549                 $dummy = [];
1550
1551                 // Iterate through all general variables
1552                 foreach ($this->getVarStack('general') as $currVariable) {
1553                         // Transfer it's name/value combination to the $content array
1554                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:' . $currVariable['name'] . '=<pre>' . htmlentities($currVariable['value']).'</pre>');
1555                         $dummy[$currVariable['name']] = $currVariable['value'];
1556                 }
1557
1558                 // Set the new variable (don't remove the second dollar!)
1559                 $$validVar = $dummy;
1560
1561                 // Remove some variables
1562                 unset($index);
1563                 unset($currVariable);
1564
1565                 // Run the compilation three times to get content from helper classes in
1566                 $cnt = 0;
1567                 while ($cnt < 3) {
1568                         // Finalize the compilation of template variables
1569                         $this->finalizeVariableCompilation();
1570
1571                         // Prepare the eval() command for comiling the template
1572                         $eval = sprintf('$result = "%s";',
1573                                 addslashes($this->getRawTemplateData())
1574                         );
1575
1576                         // This loop does remove the backslashes (\) in PHP parameters
1577                         while (strpos($eval, $this->codeBegin) !== false) {
1578                                 // Get left part before "<?"
1579                                 $evalLeft = substr($eval, 0, strpos($eval, $this->codeBegin));
1580
1581                                 // Get all from right of "<?"
1582                                 $evalRight = substr($eval, (strpos($eval, $this->codeBegin) + 5));
1583
1584                                 // Cut middle part out and remove escapes
1585                                 $evalMiddle = trim(substr($evalRight, 0, strpos($evalRight, $this->codeEnd)));
1586                                 $evalMiddle = stripslashes($evalMiddle);
1587
1588                                 // Remove the middle part from right one
1589                                 $evalRight = substr($evalRight, (strpos($evalRight, $this->codeEnd) + 2));
1590
1591                                 // And put all together
1592                                 $eval = sprintf('%s<%%php %s %%>%s', $evalLeft, $evalMiddle, $evalRight);
1593                         }
1594
1595                         // Prepare PHP code for eval() command
1596                         $eval = str_replace(
1597                                 '<%php', '";',
1598                                 str_replace(
1599                                         '%>',
1600                                         "\n\$result .= \"",
1601                                         $eval
1602                                 )
1603                         );
1604
1605                         // Run the constructed command. This will "compile" all variables in
1606                         eval($eval);
1607
1608                         // Goes something wrong?
1609                         if ((!isset($result)) || (empty($result))) {
1610                                 // Output eval command
1611                                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('Failed eval() code: <pre>%s</pre>', $this->markupCode($eval, true)), true);
1612
1613                                 // Output backtrace here
1614                                 $this->debugBackTrace();
1615                         }
1616
1617                         // Set raw template data
1618                         $this->setRawTemplateData($result);
1619                         $cnt++;
1620                 }
1621
1622                 // Final variable assignment
1623                 $this->finalizeVariableCompilation();
1624
1625                 // Set the new content
1626                 $this->setCompiledData($this->getRawTemplateData());
1627         }
1628
1629         /**
1630          * Compile all required templates into the current loaded one
1631          *
1632          * @return      void
1633          * @throws      UnexpectedTemplateTypeException If the template type is
1634          *                                                                                      not "code"
1635          * @throws      InvalidArrayCountException              If an unexpected array
1636          *                                                                                      count has been found
1637          */
1638         public function compileTemplate () {
1639                 // Get code type to make things shorter
1640                 $codeType = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('code_' . FrameworkBootstrap::getRequestTypeFromSystem() . '_template_type');
1641
1642                 // We will only work with template type "code" from configuration
1643                 if (substr($this->getTemplateType(), 0, strlen($codeType)) != $codeType) {
1644                         // Abort here
1645                         throw new UnexpectedTemplateTypeException(array($this, $this->getTemplateType(), FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('code_' . FrameworkBootstrap::getRequestTypeFromSystem() . '_template_type')), self::EXCEPTION_TEMPLATE_TYPE_IS_UNEXPECTED);
1646                 }
1647
1648                 // Get the raw data.
1649                 $rawData = $this->getRawTemplateData();
1650
1651                 // Remove double spaces and trim leading/trailing spaces
1652                 $rawData = trim(str_replace('  ', ' ', $rawData));
1653
1654                 // Search for raw variables
1655                 $this->extractVariablesFromRawData($rawData);
1656
1657                 // Search for code-tags which are {? ?}
1658                 preg_match_all($this->regExpCodeTags, $rawData, $templateMatches);
1659
1660                 // Debug message
1661                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:templateMatches=' . print_r($templateMatches , true));
1662
1663                 // Analyze the matches array
1664                 if ((is_array($templateMatches)) && (count($templateMatches) == 4) && (count($templateMatches[0]) > 0)) {
1665                         // Entries are found:
1666                         //
1667                         // The main analysis
1668                         $this->analyzeTemplate($templateMatches);
1669
1670                         // Compile raw template data
1671                         $this->compileRawTemplateData($templateMatches);
1672
1673                         // Are there some raw templates left for loading?
1674                         $this->loadExtraRawTemplates();
1675
1676                         // Are some raw templates found and loaded?
1677                         if (count($this->rawTemplates) > 0) {
1678                                 // Insert all raw templates
1679                                 $this->insertRawTemplates();
1680
1681                                 // Remove the raw template content as well
1682                                 $this->setRawTemplateData('');
1683                         }
1684                 }
1685         }
1686
1687         /**
1688          * Loads a given view helper (by name)
1689          *
1690          * @param       $helperName             The helper's name
1691          * @return      void
1692          * @throws      InvalidArgumentException        If the variable name is left empty
1693          */
1694         protected function loadViewHelper (string $helperName) {
1695                 // Validate parameter
1696                 if (empty($helperName)) {
1697                         // Throw an exception
1698                         throw new InvalidArgumentException('Parameter "helperName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1699                 }
1700
1701                 // Is this view helper loaded?
1702                 if (!isset($this->helpers[$helperName])) {
1703                         // Create a class name
1704                         $className = StringUtils::convertToClassName($helperName) . 'ViewHelper';
1705
1706                         // Generate new instance
1707                         $this->helpers[$helperName] = ObjectFactory::createObjectByName($className);
1708                 }
1709
1710                 // Return the requested instance
1711                 return $this->helpers[$helperName];
1712         }
1713
1714         /**
1715          * Transfers the content of this template engine to a given response instance
1716          *
1717          * @param       $responseInstance       An instance of a Responseable class
1718          * @return      void
1719          */
1720         public function transferToResponse (Responseable $responseInstance) {
1721                 // Get the content and set it in response class
1722                 $responseInstance->writeToBody($this->getCompiledData());
1723         }
1724
1725         /**
1726          * "Compiles" a variable by replacing {?var?} with it's content
1727          *
1728          * @param       $rawCode                        Raw code to compile
1729          * @param       $setMatchAsCode         Sets $match if readVariable() returns empty result
1730          * @return      $rawCode        Compile code with inserted variable value
1731          * @throws      InvalidArgumentException        If the variable name is left empty
1732          */
1733         public function compileRawCode (string $rawCode, bool $setMatchAsCode = false) {
1734                 // Validate parameter
1735                 if (empty($rawCode)) {
1736                         // Throw an exception
1737                         throw new InvalidArgumentException('Parameter "rawCode" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1738                 }
1739
1740                 // Find the variables
1741                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:rawCode=<pre>' . htmlentities($rawCode) . '</pre>');
1742                 preg_match_all($this->regExpVarValue, $rawCode, $varMatches);
1743
1744                 // Compile all variables
1745                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:<pre>' . print_r($varMatches, true) . '</pre>');
1746                 foreach ($varMatches[0] as $match) {
1747                         // Add variable tags around it
1748                         $varCode = '{?' . $match . '?}';
1749
1750                         // Debug message
1751                         //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:varCode=' . $varCode);
1752
1753                         // Is the variable found in code? (safes some calls)
1754                         if (strpos($rawCode, $varCode) !== false) {
1755                                 // Debug message
1756                                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: match=' . $match . ',rawCode[' . gettype($rawCode) . ']=' . $rawCode);
1757
1758                                 // Use $match as new value or $value from read variable?
1759                                 if ($setMatchAsCode === true) {
1760                                         // Insert match
1761                                         $rawCode = str_replace($varCode, $match, $rawCode);
1762                                 } else {
1763                                         // Read the variable
1764                                         $value = $this->readVariable($match);
1765
1766                                         // Insert value
1767                                         $rawCode = str_replace($varCode, $value, $rawCode);
1768                                 }
1769                         }
1770                 }
1771
1772                 // Return the compiled data
1773                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE:rawCode=<pre>' . htmlentities($rawCode) . '</pre>');
1774                 return $rawCode;
1775         }
1776
1777         /**
1778          * Getter for variable group array
1779          *
1780          * @return      $variableGroups All variable groups
1781          */
1782         public final function getVariableGroups () {
1783                 return $this->variableGroups;
1784         }
1785
1786         /**
1787          * Renames a variable in code and in stack
1788          *
1789          * @param       $oldName        Old name of variable
1790          * @param       $newName        New name of variable
1791          * @return      void
1792          * @throws      InvalidArgumentException        If the variable name is left empty
1793          */
1794         public function renameVariable (string $oldName, string $newName) {
1795                 // Validate parameter
1796                 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE: oldName=' . $oldName . ', newName=' . $newName);
1797                 if (empty($oldName)) {
1798                         // Throw an exception
1799                         throw new InvalidArgumentException('Parameter "oldName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1800                 } elseif (empty($newName)) {
1801                         // Throw an exception
1802                         throw new InvalidArgumentException('Parameter "newName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1803                 }
1804
1805                 // Get raw template code
1806                 $rawData = $this->getRawTemplateData();
1807
1808                 // Replace it
1809                 $rawData = str_replace($oldName, $newName, $rawData);
1810
1811                 // Set the code back
1812                 $this->setRawTemplateData($rawData);
1813         }
1814
1815         /**
1816          * Enables or disables language support
1817          *
1818          * @param       $languageSupport        New language support setting
1819          * @return      void
1820          */
1821         public final function enableLanguageSupport (bool $languageSupport = true) {
1822                 $this->languageSupport = $languageSupport;
1823         }
1824
1825         /**
1826          * Checks whether language support is enabled
1827          *
1828          * @return      $languageSupport        Whether language support is enabled or disabled
1829          */
1830         public final function isLanguageSupportEnabled () {
1831                 return $this->languageSupport;
1832         }
1833
1834         /**
1835          * Removes all commentd, tabs and new-line characters to compact the content
1836          *
1837          * @param       $uncompactedContent             The uncompacted content
1838          * @return      $compactedContent               The compacted content
1839          */
1840         public function compactContent (string $uncompactedContent) {
1841                 // Validate parameter
1842                 if (empty($uncompactedContent)) {
1843                         // Throw an exception
1844                         throw new InvalidArgumentException('Parameter "uncompactedContent" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1845                 }
1846
1847                 // First, remove all tab/new-line/revert characters
1848                 $compactedContent = str_replace(chr(9), '', str_replace(chr(10), '', str_replace(chr(13), '', $uncompactedContent)));
1849
1850                 // Then regex all comments like <!-- //--> away
1851                 preg_match_all($this->regExpComments, $compactedContent, $matches);
1852
1853                 // Do we have entries?
1854                 if (isset($matches[0][0])) {
1855                         // Remove all
1856                         foreach ($matches[0] as $match) {
1857                                 // Remove the match
1858                                 $compactedContent = str_replace($match, '', $compactedContent);
1859                         }
1860                 }
1861
1862                 // Set the content again
1863                 $this->setRawTemplateData($compactedContent);
1864
1865                 // Return compacted content
1866                 return $compactedContent;
1867         }
1868
1869 }