2fff655b7c4b7a98362a024775e769953925f320
[mailer.git] / inc / classes / main / template / class_TemplateEngine.php
1 <?php
2 /**
3  * The own template engine for loading caching and sending out the web pages
4  * and emails.
5  *
6  * @author              Roland Haeder <webmaster@mxchange.org>
7  * @version             0.3.0
8  * @copyright   Copyright(c) 2007, 2008 Roland Haeder, this is free software
9  * @license             GNU GPL 3.0 or any newer version
10  * @link                http://www.mxchange.org
11  *
12  * This program is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25 class TemplateEngine extends BaseFrameworkSystem implements CompileableTemplate {
26         /**
27          * The local path name where all templates and sub folders for special
28          * templates are stored. We will internally determine the language plus
29          * "html" for web templates or "emails" for email templates
30          */
31         private $basePath = "";
32
33         /**
34          * The extension for web and email templates (not compiled templates)
35          */
36         private $templateExtension = ".tpl";
37
38         /**
39          * The extension for code templates (not compiled templates)
40          */
41         private $codeExtension = ".ctp";
42
43         /**
44          * Path relative to $basePath and language code for compiled code-templates
45          */
46         private $compileOutputPath = "templates/_compiled";
47
48         /**
49          * The raw (maybe uncompiled) template
50          */
51         private $rawTemplateData = "";
52
53         /**
54          * Template data with compiled-in variables
55          */
56         private $compiledData = "";
57
58         /**
59          * The last loaded template's FQFN for debugging the engine
60          */
61         private $lastTemplate = "";
62
63         /**
64          * The variable stack for the templates. This must be initialized and
65          * shall become an instance of FrameworkArrayObject.
66          */
67         private $varStack = null;
68
69         /**
70          * Configuration variables in a simple array
71          */
72         private $configVariables = array();
73
74         /**
75          * The language instance which should link to an object of LanguageSystem
76          */
77         private $langInstance = null;
78
79         /**
80          * Loaded templates for recursive protection and detection
81          */
82         private $loadedTemplates = array();
83
84         /**
85          * Compiled templates for recursive protection and detection
86          */
87         private $compiledTemplates = array();
88
89         /**
90          * Loaded raw template data
91          */
92         private $loadedRawData = null;
93
94         /**
95          * Raw templates which are linked in code templates
96          */
97         private $rawTemplates = null;
98
99         /**
100          * A regular expression for variable=value pairs
101          */
102         private $regExpVarValue = '/([\w_]+)(="([^"]*)"|=([\w_]+))?/';
103
104         /**
105          * A regular expression for filtering out code tags
106          *
107          * E.g.: {?template:variable=value;var2=value2;[...]?}
108          */
109         private $regExpCodeTags = '/\{\?([a-z_]+)(:("[^"]+"|[^?}]+)+)?\?\}/';
110
111         /**
112          * Loaded helpers
113          */
114         private $helpers = array();
115
116         // Exception codes for the template engine
117         const EXCEPTION_TEMPLATE_TYPE_IS_UNEXPECTED   = 0xa00;
118         const EXCEPTION_TEMPLATE_CONTAINS_INVALID_VAR = 0xa01;
119         const EXCEPTION_INVALID_VIEW_HELPER           = 0xa02;
120
121         /**
122          * Private constructor
123          *
124          * @return      void
125          */
126         private final function __construct () {
127                 // Call parent constructor
128                 parent::constructor(__CLASS__);
129
130                 // Set part description
131                 $this->setPartDescr("Template-Engine");
132
133                 // Create unique ID number
134                 $this->createUniqueID();
135
136                 // Clean up a little
137                 $this->removeNumberFormaters();
138                 $this->removeSystemArray();
139         }
140
141         /**
142          * Creates an instance of the class TemplateEngine and prepares it for usage
143          *
144          * @param               $basePath               The local base path for all templates
145          * @param               $langInstance   An instance of LanguageSystem (default)
146          * @param               $ioInstance     An instance of FileIOHandler (default, middleware!)
147          * @return      $tplInstance    An instance of TemplateEngine
148          * @throws      BasePathIsEmptyException                If the provided $basePath is empty
149          * @throws      InvalidBasePathStringException  If $basePath is no string
150          * @throws      BasePathIsNoDirectoryException  If $basePath is no
151          *                                                                              directory or not found
152          * @throws      BasePathReadProtectedException  If $basePath is
153          *                                                                              read-protected
154          */
155         public final static function createTemplateEngine ($basePath, $langInstance, $ioInstance) {
156                 // Get a new instance
157                 $tplInstance = new TemplateEngine();
158
159                 // Is the base path valid?
160                 if (empty($basePath)) {
161                         // Base path is empty
162                         throw new BasePathIsEmptyException($tplInstance, self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
163                 } elseif (!is_string($basePath)) {
164                         // Is not a string
165                         throw new InvalidBasePathStringException(array($tplInstance, $basePath), self::EXCEPTION_INVALID_STRING);
166                 } elseif (!is_dir($basePath)) {
167                         // Is not a path
168                         throw new BasePathIsNoDirectoryException(array($tplInstance, $basePath), self::EXCEPTION_INVALID_PATH_NAME);
169                 } elseif (!is_readable($basePath)) {
170                         // Is not readable
171                         throw new BasePathReadProtectedException(array($tplInstance, $basePath), self::EXCEPTION_READ_PROTECED_PATH);
172                 }
173
174                 // Get configuration instance
175                 $cfgInstance = $tplInstance->getConfigInstance();
176
177                 // Set the base path
178                 $tplInstance->setBasePath($basePath);
179
180                 // Initialize the variable stack
181                 $tplInstance->initVariableStack();
182
183                 // Set the language and IO instances
184                 $tplInstance->setLanguageInstance($langInstance);
185                 $tplInstance->setIOInstance($ioInstance);
186
187                 // Set template extensions
188                 $tplInstance->setRawTemplateExtension($cfgInstance->readConfig("raw_template_extension"));
189                 $tplInstance->setCodeTemplateExtension($cfgInstance->readConfig("code_template_extension"));
190
191                 // Absolute output path for compiled templates
192                 $tplInstance->setCompileOutputPath(PATH . $cfgInstance->readConfig("compile_output_path"));
193
194                 // Return the prepared instance
195                 return $tplInstance;
196         }
197
198         /**
199          * Search for a variable in the stack
200          *
201          * @param               $var            The variable we are looking for
202          * @return      $idx            FALSE means not found, > 0 means found on a specific index
203          */
204         private function isVariableAlreadySet ($var) {
205                 // First everything is not found
206                 $found = false;
207
208                 // Now search for it
209                 for ($idx = $this->varStack->getIterator(); $idx->valid(); $idx->next()) {
210                         // Get current item
211                         $currEntry = $idx->current();
212
213                         // Is the entry found?
214                         if ($currEntry['name'] == $var) {
215                                 // Found!
216                                 $found = $idx->key();
217                                 break;
218                         }
219                 }
220
221                 // Return the current position
222                 return $found;
223         }
224
225         /**
226          * Add a variable to the stack
227          *
228          * @param               $var            The variable we are looking for
229          * @param               $value  The value we want to store in the variable
230          * @return      void
231          */
232         private function addVariable ($var, $value) {
233                 // Add it to the stack
234                 $this->varStack->append(array(
235                         'name'  => $var,
236                         'value' => $value
237                 ));
238         }
239
240         /**
241          * Modify an entry on the stack
242          *
243          * @param               $var            The variable we are looking for
244          * @param               $value  The value we want to store in the variable
245          * @return      void
246          */
247         private function modifyVariable ($var, $value) {
248                 // It should be there so let's look again...
249                 for ($idx = $this->varStack->getIterator(); $idx->valid(); $idx->next()) {
250                         // Get current entry
251                         $currEntry = $idx->current();
252
253                         // Is this the requested variable?
254                         if ($currEntry['name'] == $var) {
255                                 // Change it to the other value
256                                 $this->varStack->offsetSet($idx->key(), array(
257                                         'name'  => $var,
258                                         'value' => $value
259                                 ));
260                         }
261                 }
262         }
263
264         /**
265          * Initialize the variable stack. This holds all variables for later
266          * compilation.
267          *
268          * @return      void
269          */
270         public final function initVariableStack () {
271                 $this->varStack = new FrameworkArrayObject();
272         }
273
274         /**
275          * Setter for language instance which should be LanguageSystem
276          *
277          * @param               $langInstance           The language instance
278          * @return      void
279          */
280         public final function setLanguageInstance (ManageableLanguage $langInstance) {
281                 $this->langInstance = $langInstance;
282         }
283
284         /**
285          * Setter for file I/O instance which should be FileIOHandler
286          *
287          * @param               $ioInstance             The file I/O instance
288          * @return      void
289          */
290         public final function setIOInstance (FileIOHandler $ioInstance) {
291                 $this->ioInstance = $ioInstance;
292         }
293
294         /**
295          * Getter for file I/O instance which should be FileIOHandler
296          *
297          * @return      $ioInstance             The file I/O instance
298          */
299         public final function getIOInstance () {
300                 return $this->ioInstance;
301         }
302
303         /**
304          * Setter for base path
305          *
306          * @param               $basePath               The local base path for all templates
307          * @return      void
308          */
309         public final function setBasePath ($basePath) {
310                 // Cast it
311                 $basePath = (string) $basePath;
312
313                 // And set it
314                 $this->basePath = $basePath;
315         }
316
317         /**
318          * Getter for base path
319          *
320          * @return      $basePath               The local base path for all templates
321          */
322         public final function getBasePath () {
323                 // And set it
324                 return $this->basePath;
325         }
326
327         /**
328          * Setter for template extension
329          *
330          * @param               $templateExtension      The file extension for all uncompiled
331          *                                                      templates
332          * @return      void
333          */
334         public final function setRawTemplateExtension ($templateExtension) {
335                 // Cast it
336                 $templateExtension = (string) $templateExtension;
337
338                 // And set it
339                 $this->templateExtension = $templateExtension;
340         }
341
342         /**
343          * Setter for code template extension
344          *
345          * @param               $codeExtension          The file extension for all uncompiled
346          *                                                      templates
347          * @return      void
348          */
349         public final function setCodeTemplateExtension ($codeExtension) {
350                 // Cast it
351                 $codeExtension = (string) $codeExtension;
352
353                 // And set it
354                 $this->codeExtension = $codeExtension;
355         }
356
357         /**
358          * Getter for template extension
359          *
360          * @return      $templateExtension      The file extension for all uncompiled
361          *                                                      templates
362          */
363         public final function getRawTemplateExtension () {
364                 // And set it
365                 return $this->templateExtension;
366         }
367
368         /**
369          * Getter for code-template extension
370          *
371          * @return      $codeExtension          The file extension for all code-
372          *                                                      templates
373          */
374         public final function getCodeTemplateExtension () {
375                 // And set it
376                 return $this->codeExtension;
377         }
378
379         /**
380          * Setter for path of compiled templates
381          *
382          * @param               $compileOutputPath              The local base path for all
383          *                                                              compiled templates
384          * @return      void
385          */
386         public final function setCompileOutputPath ($compileOutputPath) {
387                 // Cast it
388                 $compileOutputPath = (string) $compileOutputPath;
389
390                 // And set it
391                 $this->compileOutputPath = $compileOutputPath;
392         }
393
394         /**
395          * Setter for template type. Only "html", "emails" and "compiled" should
396          * be sent here
397          *
398          * @param               $templateType   The current template's type
399          * @return      void
400          */
401         private final function setTemplateType ($templateType) {
402                 // Cast it
403                 $templateType = (string) $templateType;
404
405                 // And set it (only 2 letters)
406                 $this->templateType = $templateType;
407         }
408
409         /**
410          * Getter for template type
411          *
412          * @return      $templateType   The current template's type
413          */
414         public final function getTemplateType () {
415                 return $this->templateType;
416         }
417
418         /**
419          * Setter for the last loaded template's FQFN
420          *
421          * @param               $template               The last loaded template
422          * @return      void
423          */
424         private final function setLastTemplate ($template) {
425                 // Cast it to string
426                 $template = (string) $template;
427                 $this->lastTemplate = $template;
428         }
429
430         /**
431          * Getter for the last loaded template's FQFN
432          *
433          * @return      $template               The last loaded template
434          */
435         private final function getLastTemplate () {
436                 return $this->lastTemplate;
437         }
438
439         /**
440          * Assign (add) a given variable with a value
441          *
442          * @param               $var            The variable we are looking for
443          * @param               $value  The value we want to store in the variable
444          * @return      void
445          */
446         public final function assignVariable ($var, $value) {
447                 // First search for the variable if it was already added
448                 $idx = $this->isVariableAlreadySet($var);
449
450                 // Was it found?
451                 if ($idx === false) {
452                         // Add it to the stack
453                         $this->addVariable($var, $value);
454                 } elseif (!empty($value)) {
455                         // Modify the stack entry
456                         $this->modifyVariable($var, $value);
457                 }
458         }
459
460         /**
461          * Assign a given congfiguration variable with a value
462          *
463          * @param               $var            The configuration variable we are looking for
464          * @param               $value  The value we want to store in the variable
465          * @return      void
466          */
467         public final function assignConfigVariable ($var, $value) {
468                 // Sweet and simple...
469                 $this->configVariables[$var] = $value;
470         }
471
472         /**
473          * Removes a given variable
474          *
475          * @param               $var            The variable we are looking for
476          * @return      void
477          */
478         public final function removeVariable ($var) {
479                 // First search for the variable if it was already added
480                 $idx = $this->isVariableAlreadySet($var);
481
482                 // Was it found?
483                 if ($idx !== false) {
484                         // Remove this variable
485                         $this->varStack->offsetUnset($idx);
486                 }
487         }
488
489         /**
490          * Private setter for raw template data
491          *
492          * @param               $rawTemplateData        The raw data from the template
493          * @return      void
494          */
495         private final function setRawTemplateData ($rawTemplateData) {
496                 // Cast it to string
497                 $rawTemplateData = (string) $rawTemplateData;
498
499                 // And store it in this class
500                 $this->rawTemplateData = $rawTemplateData;
501         }
502
503         /**
504          * Private setter for compiled templates
505          */
506         private final function setCompiledData ($compiledData) {
507                 // Cast it to string
508                 $compiledData = (string) $compiledData;
509
510                 // And store it in this class
511                 $this->compiledData = $compiledData;
512         }
513
514         /**
515          * Private loader for all template types
516          *
517          * @param               $template               The template we shall load
518          * @return      void
519          */
520         private final function loadTemplate ($template) {
521                 // Cast it to string
522                 $template = (string) $template;
523
524                 // Get extension for the template
525                 $ext = $this->getRawTemplateExtension();
526
527                 // If we shall load a code-template we need to switch the file extension
528                 if ($this->getTemplateType() == $this->getConfigInstance()->readConfig("code_template_type")) {
529                         // Switch over to the code-template extension
530                         $ext = $this->getCodeTemplateExtension();
531                 }
532
533                 // Construct the FQFN for the template by honoring the current language
534                 $fqfn = sprintf("%s%s/%s/%s%s",
535                         $this->getBasePath(),
536                         $this->langInstance->getLanguageCode(),
537                         $this->getTemplateType(),
538                         $template,
539                         $ext
540                 );
541
542                 // Load the raw template data
543                 $this->loadRawTemplateData($fqfn);
544         }
545
546         /**
547          * A private loader for raw template names
548          *
549          * @param               $fqfn   The full-qualified file name for a template
550          * @return      void
551          * @throws      NullPointerException    If $inputInstance is null
552          * @throws      NoObjectException               If $inputInstance is not an object
553          * @throws      MissingMethodException  If $inputInstance is missing a
554          *                                                              required method
555          */
556         private function loadRawTemplateData ($fqfn) {
557                 // Debug message
558                 if ((defined('DEBUG_TEMPLATE')) && (is_object($this->getDebugInstance()))) $this->getDebugInstance()->output(sprintf("[%s:] Template <strong>%s</strong> vom Typ <strong>%s</strong> wird geladen.<br />\n",
559                         $this->__toString(),
560                         $template,
561                         $this->getTemplateType()
562                 ));
563
564                 // Get a input/output instance from the middleware
565                 $ioInstance = $this->getIOInstance();
566
567                 // Validate the instance
568                 if (is_null($ioInstance)) {
569                         // Throw exception
570                         throw new NullPointerException($this, self::EXCEPTION_IS_NULL_POINTER);
571                 } elseif (!is_object($ioInstance)) {
572                         // Throw another exception
573                         throw new NoObjectException($ioInstance, self::EXCEPTION_IS_NO_OBJECT);
574                 } elseif (!method_exists($ioInstance, 'loadFileContents')) {
575                         // Throw yet another exception
576                         throw new MissingMethodException(array($ioInstance, 'loadFileContents'), self::EXCEPTION_MISSING_METHOD);
577                 }
578
579                 // Load the raw template
580                 $rawTemplateData = $ioInstance->loadFileContents($fqfn);
581
582                 // Debug message
583                 if ((defined('DEBUG_TEMPLATE')) && (is_object($this->getDebugInstance()))) $this->getDebugInstance()->output(sprintf("[%s:] <strong>%s</strong> Byte Rohdaten geladen.<br />\n",
584                         $this->__toString(),
585                         strlen($rawTemplateData)
586                 ));
587
588                 // Store the template's contents into this class
589                 $this->setRawTemplateData($rawTemplateData);
590
591                 // Remember the template's FQFN
592                 $this->setLastTemplate($fqfn);
593         }
594
595         /**
596          * Try to assign an extracted template variable as a "content" or "config"
597          * variable.
598          *
599          * @param               $varName                The variable's name (shall be content or
600          *                                              config) by default
601          * @param               $var                    The variable we want to assign
602          */
603         private function assignTemplateVariable ($varName, $var) {
604                 // Is it not a config variable?
605                 if ($varName != "config") {
606                         // Regular template variables
607                         $this->assignVariable($var, "");
608                 } else {
609                         // Configuration variables
610                         $this->assignConfigVariable($var, $this->getConfigInstance()->readConfig($var));
611                 }
612         }
613
614         /**
615          * Extract variables from a given raw data stream
616          *
617          * @param               $rawData                The raw template data we shall analyze
618          * @return      void
619          * @throws      InvalidTemplateVariableNameException    If a variable name
620          *                                                                                      in a template is
621          *                                                                                      invalid
622          */
623         private function extractVariablesFromRawData ($rawData) {
624                 // Cast to string
625                 $rawData = (string) $rawData;
626
627                 // Search for variables
628                 @preg_match_all('/\$(\w+)(\[(\w+)\])?/', $rawData, $variableMatches);
629
630                 // Did we find some variables?
631                 if ((is_array($variableMatches)) && (count($variableMatches) == 4) && (count($variableMatches[0]) > 0)) {
632                         // Initialize all missing variables
633                         foreach ($variableMatches[3] as $key=>$var) {
634                                 // Is the variable name valid?
635                                 if (($variableMatches[1][$key] != $this->getConfigInstance()->readConfig("tpl_valid_var")) && ($variableMatches[1][$key] != "config")) {
636                                         // Invalid variable name
637                                         throw new InvalidTemplateVariableNameException(array($this, $this->getLastTemplate(), $variableMatches[1][$key], $this->getConfigInstance()), self::EXCEPTION_TEMPLATE_CONTAINS_INVALID_VAR);
638                                 }
639
640                                 // Try to assign it, empty strings are being ignored
641                                 $this->assignTemplateVariable($variableMatches[1][$key], $var);
642                         }
643                 }
644         }
645
646         /**
647          * Main analysis of the loaded template
648          *
649          * @param               $templateMatches        Found template place-holders, see below
650          * @return      void
651          *
652          *---------------------------------
653          * Structure of $templateMatches:
654          *---------------------------------
655          * [0] => Array - An array with all full matches
656          * [1] => Array - An array with left part (before the ":") of a match
657          * [2] => Array - An array with right part of a match including ":"
658          * [3] => Array - An array with right part of a match excluding ":"
659          */
660         private function analyzeTemplate ($templateMatches) {
661                 // Backup raw template data
662                 $backup = $this->getRawTemplateData();
663
664                 // Initialize some arrays
665                 if (is_null($this->loadedRawData)) { $this->loadedRawData = array(); $this->rawTemplates = array(); }
666
667                 // Load all requested templates
668                 foreach ($templateMatches[1] as $template) {
669
670                         // Load and compile only templates which we have not yet loaded
671                         // RECURSIVE PROTECTION! BE CAREFUL HERE!
672                         if ((!isset($this->loadedRawData[$template])) && (!in_array($template, $this->loadedTemplates))) {
673
674                                 // Then try to search for code-templates first
675                                 try {
676                                         // Load the code template and remember it's contents
677                                         $this->loadCodeTemplate($template);
678                                         $this->loadedRawData[$template] = $this->getRawTemplateData();
679
680                                         // Remember this template for recursion detection
681                                         // RECURSIVE PROTECTION!
682                                         $this->loadedTemplates[] = $template;
683                                 } catch (FilePointerNotOpenedException $e) {
684                                         // Template not found!
685                                         $this->rawTemplates[] = $template;
686                                 }
687
688                         } // if ((!isset( ...
689
690                 } // for ($templateMatches ...
691
692                 // Restore the raw template data
693                 $this->setRawTemplateData($backup);
694         }
695
696         /**
697          * Compile a given raw template code and remember it for later usage
698          *
699          * @param               $code           The raw template code
700          * @param               $template               The template's name
701          * @return      void
702          */
703         private function compileCode ($code, $template) {
704                 // Is this template already compiled?
705                 if (in_array($template, $this->compiledTemplates)) {
706                         // Abort here...
707                         return;
708                 }
709
710                 // Remember this template being compiled
711                 $this->compiledTemplates[] = $template;
712
713                 // Compile the loaded code in five steps:
714                 //
715                 // 1. Backup current template data
716                 $backup = $this->getRawTemplateData();
717
718                 // 2. Set the current template's raw data as the new content
719                 $this->setRawTemplateData($code);
720
721                 // 3. Compile the template data
722                 $this->compileTemplate();
723
724                 // 4. Remember it's contents
725                 $this->loadedRawData[$template] = $this->getRawTemplateData();
726
727                 // 5. Restore the previous raw content from backup variable
728                 $this->setRawTemplateData($backup);
729         }
730
731         /**
732          * Insert all given and loaded templates by running through all loaded
733          * codes and searching for their place-holder in the main template
734          *
735          * @param               $templateMatches        See method analyzeTemplate()
736          * @return      void
737          */
738         private function insertAllTemplates ($templateMatches) {
739                 // Run through all loaded codes
740                 foreach ($this->loadedRawData as $template => $code) {
741
742                         // Search for the template
743                         $foundIndex = array_search($template, $templateMatches[1]);
744
745                         // Lookup the matching template replacement
746                         if (isset($templateMatches[0][$foundIndex])) {
747
748                                 // Get the current raw template
749                                 $rawData = $this->getRawTemplateData();
750
751                                 // Replace the space holder with the template code
752                                 $rawData = str_replace($templateMatches[0][$foundIndex], $code, $rawData);
753
754                                 // Set the new raw data
755                                 $this->setRawTemplateData($rawData);
756                         }
757                 }
758         }
759
760         /**
761          * Load all extra raw templates
762          *
763          * @return      void
764          */
765         private function loadExtraRawTemplates () {
766                 // Are there some raw templates we need to load?
767                 if (count($this->rawTemplates) > 0) {
768                         // Try to load all raw templates
769                         foreach ($this->rawTemplates as $key => $template) {
770                                 try {
771                                         // Load the template
772                                         $this->loadWebTemplate($template);
773
774                                         // Remember it's contents
775                                         $this->rawTemplates[$template] = $this->getRawTemplateData();
776
777                                         // Remove it from the loader list
778                                         unset($this->rawTemplates[$key]);
779
780                                         // Remember this template for recursion detection
781                                         // RECURSIVE PROTECTION!
782                                         $this->loadedTemplates[] = $template;
783                                 } catch (FilePointerNotOpenedException $e) {
784                                         // This template was never found. We silently ignore it
785                                         unset($this->rawTemplates[$key]);
786                                 }
787                         }
788                 }
789         }
790
791         /**
792          * Assign all found template variables
793          *
794          * @param               $varMatches     An array full of variable/value pairs.
795          * @return      void
796          */
797         private function assignAllVariables ($varMatches) {
798                 // Search for all variables
799                 foreach ($varMatches[1] as $key=>$var) {
800
801                         // Detect leading equals
802                         if (substr($varMatches[2][$key], 0, 1) == "=") {
803                                 // Remove and cast it
804                                 $varMatches[2][$key] = (string) substr($varMatches[2][$key], 1);
805                         }
806
807                         // Do we have some quotes left and right side? Then it is free text
808                         if ((substr($varMatches[2][$key], 0, 1) == "\"") && (substr($varMatches[2][$key], -1, 1) == "\"")) {
809                                 // Free string detected! Which we can assign directly
810                                 $this->assignVariable($var, $varMatches[3][$key]);
811                         } else {
812                                 // Non-string found so we need some deeper analysis...
813                                 die("Deeper analysis not yet implemented!");
814                         }
815
816                 } // for ($varMatches ...
817         }
818         /**
819          * Compiles all loaded raw templates
820          *
821          * @param               $templateMatches        See method analyzeTemplate() for details
822          * @return      void
823          */
824         private function compileRawTemplateData ($templateMatches) {
825                 // Are some code-templates found which we need to compile?
826                 if (count($this->loadedRawData) > 0) {
827
828                         // Then compile all!
829                         foreach ($this->loadedRawData as $template => $code) {
830
831                                 // Search for the template
832                                 $foundIndex = array_search($template, $templateMatches[1]);
833
834                                 // Lookup the matching variable data
835                                 if (isset($templateMatches[3][$foundIndex])) {
836
837                                         // Split it up with another reg. exp. into variable=value pairs
838                                         @preg_match_all($this->regExpVarValue, $templateMatches[3][$foundIndex], $varMatches);
839
840                                         // Assign all variables
841                                         $this->assignAllVariables($varMatches);
842
843                                 } // END - if (isset($templateMatches ...
844
845                                 // Compile the loaded template
846                                 $this->compileCode($code, $template);
847
848                         } // END - foreach ($this->loadedRawData ...
849
850                         // Insert all templates
851                         $this->insertAllTemplates($templateMatches);
852
853                 } // END - if (count($this->loadedRawData) ...
854         }
855
856         /**
857          * Getter for raw template data
858          *
859          * @return      $rawTemplateData        The raw data from the template
860          */
861         public final function getRawTemplateData () {
862                 return $this->rawTemplateData;
863         }
864
865         /**
866          * Getter for compiled templates
867          */
868         public final function getCompiledData () {
869                 return $this->compiledData;
870         }
871
872         /**
873          * Load a specified web template into the engine
874          *
875          * @param               $template               The web template we shall load which is
876          *                                              located in "html" by default
877          * @return      void
878          */
879         public final function loadWebTemplate ($template) {
880                 // Set template type
881                 $this->setTemplateType($this->getConfigInstance()->readConfig("web_template_type"));
882
883                 // Load the special template
884                 $this->loadTemplate($template);
885         }
886
887         /**
888          * Load a specified email template into the engine
889          *
890          * @param               $template               The email template we shall load which is
891          *                                              located in "emails" by default
892          * @return      void
893          */
894         public final function loadEmailTemplate ($template) {
895                 // Set template type
896                 $this->setTemplateType($this->getConfigInstance()->readConfig("email_template_type"));
897
898                 // Load the special template
899                 $this->loadTemplate($template);
900         }
901
902         /**
903          * Load a specified code template into the engine
904          *
905          * @param               $template               The code template we shall load which is
906          *                                              located in "code" by default
907          * @return      void
908          */
909         public final function loadCodeTemplate ($template) {
910                 // Set template type
911                 $this->setTemplateType($this->getConfigInstance()->readConfig("code_template_type"));
912
913                 // Load the special template
914                 $this->loadTemplate($template);
915         }
916
917         /**
918          * Compile all variables by inserting their respective values
919          *
920          * @return      void
921          */
922         public final function compileVariables () {
923                 // Initialize the $content array
924                 $validVar = $this->getConfigInstance()->readConfig("tpl_valid_var");
925                 $dummy = array();
926
927                 // Iterate through all variables
928                 for ($idx = $this->varStack->getIterator(); $idx->valid(); $idx->next()) {
929                         // Get current variable from the stack
930                         $currVariable = $idx->current();
931
932                         // Transfer it's name/value combination to the $content array
933                         $dummy[$currVariable['name']] = $currVariable['value'];
934                 }
935                 $$validVar = $dummy;
936
937                 // Prepare all configuration variables
938                 $config = $this->configVariables;
939
940                 // Remove some variables
941                 unset($idx);
942                 unset($currVariable);
943
944                 // Prepare the eval() command for comiling the template
945                 $eval = sprintf("\$this->setCompiledData(\"%s\");",
946                         addslashes($this->getRawTemplateData())
947                 );
948
949                 // Debug message
950                 if (((defined('DEBUG_EVAL')) || (defined('DEBUG_ALL'))) && (is_object($this->getDebugInstance()))) $this->getDebugInstance()->output(sprintf("[%s:] Konstruierte PHP-Anweisung: <pre><em>%s</em></pre><br />\n",
951                         $this->__toString(),
952                         htmlentities($eval)
953                 ));
954
955                 // Run the constructed command. This will "compile" all variables in
956                 eval($eval);
957         }
958
959         /**
960          * Compile all required templates into the current loaded one
961          *
962          * @return      void
963          * @throws      UnexpectedTemplateTypeException If the template type is
964          *                                                                              not "code"
965          * @throws      InvalidArrayCountException              If an unexpected array
966          *                                                                              count has been found
967          */
968         public final function compileTemplate () {
969                 // We will only work with template type "code" from configuration
970                 if ($this->getTemplateType() != $this->getConfigInstance()->readConfig("code_template_type")) {
971                         // Abort here
972                         throw new UnexpectedTemplateTypeException(array($this, $this->getTemplateType(), $this->getConfigInstance()->readConfig("code_template_type")), self::EXCEPTION_TEMPLATE_TYPE_IS_UNEXPECTED);
973                 }
974
975                 // Get the raw data. Thanks to Flobee(R) for given me a hint using the
976                 // modifier "m" in regular expressions. I had implemented a regex here
977                 // like this: (\n|\r)
978                 $rawData = $this->getRawTemplateData();
979
980                 // Remove double spaces and trim leading/trailing spaces
981                 $rawData = trim(str_replace("  ", " ", $rawData));
982
983                 // Search for raw variables
984                 $this->extractVariablesFromRawData($rawData);
985
986                 // Search for code-tags which are {? ?}
987                 @preg_match_all($this->regExpCodeTags, $rawData, $templateMatches);
988
989                 // Analyze the matches array
990                 if ((is_array($templateMatches)) && (count($templateMatches) == 4) && (count($templateMatches[0]) > 0)) {
991                         // Entries are found:
992                         //
993                         // The main analysis
994                         $this->analyzeTemplate($templateMatches);
995
996                         // Compile raw template data
997                         $this->compileRawTemplateData($templateMatches);
998
999                         // Are there some raw templates left for loading?
1000                         $this->loadExtraRawTemplates();
1001
1002                         // Are some raw templates found and loaded?
1003                         if (count($this->rawTemplates) > 0) {
1004                                 die("NOT YET IMPLEMENTED");
1005                         }
1006                 } // END - if($templateMatches ...
1007         }
1008
1009         /**
1010          * Output the compiled page to the outside world. In case of web templates
1011          * this would be vaild (X)HTML code. And in case of email templates this
1012          * would store a prepared email body inside the template engine.
1013          *
1014          * @return      void
1015          */
1016         public final function output () {
1017                 // Check which type of template we have
1018                 switch ($this->getTemplateType()) {
1019                 case "html": // Raw HTML templates can be send to the output buffer
1020                         // Quick-N-Dirty:
1021                         $this->getWebOutputInstance()->output($this->getCompiledData());
1022                         break;
1023
1024                 default: // Unknown type found
1025                         if ((is_object($this->getDebugInstance())) && (method_exists($this->getDebugInstance(), 'output'))) {
1026                                 // Use debug output handler
1027                                 $this->getDebugInstance()->output(sprintf("[%s:] Unbekannter Template-Typ <strong>%s</strong> erkannt.",
1028                                         $this->__toString(),
1029                                         $this->getTemplateType()
1030                                 ));
1031                                 die();
1032                         } else {
1033                                 // Put directly out
1034                                 // DO NOT REWRITE THIS TO app_die() !!!
1035                                 die(sprintf("[%s:] Unbekannter Template-Typ <strong>%s</strong> erkannt.",
1036                                         $this->__toString(),
1037                                         $this->getTemplateType()
1038                                 ));
1039                         }
1040                         break;
1041                 }
1042         }
1043
1044         /**
1045          * Loads a given view helper (by name)
1046          *
1047          * @param       $helperName     The helper's name
1048          * @return      void
1049          * @throws      ViewHelperNotFoundException     If the given view helper was not found
1050          */
1051         protected function loadViewHelper ($helperName) {
1052                 // Make first character upper case, rest low
1053                 $helperName = ucfirst($helperName);
1054
1055                 // Is this view helper loaded?
1056                 if (!isset($this->helpers[$helperName])) {
1057                         // Create a class name
1058                         $className = "{$helperName}ViewHelper";
1059
1060                         // Does this class exists?
1061                         if (!class_exists($className)) {
1062                                 // Abort here!
1063                                 throw new ViewHelperNotFoundException(array($this, $helperName), self::EXCEPTION_INVALID_VIEW_HELPER);
1064                         }
1065
1066                         // Generate new instance
1067                         $eval = sprintf("\$this->helpers[%s] = %s::create%s();",
1068                                 $helperName,
1069                                 $className,
1070                                 $className
1071                         );
1072
1073                         // Run the code
1074                         @eval($eval);
1075                 }
1076
1077                 // Return the requested instance
1078                 return $this->helpers[$helperName];
1079         }
1080 }
1081
1082 // [EOF]
1083 ?>