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