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