3 namespace CoreFramework\Template\Engine;
5 // Import framework stuff
6 use CoreFramework\Bootstrap\FrameworkBootstrap;
7 use CoreFramework\Factory\ObjectFactory;
8 use CoreFramework\Generic\EmptyVariableException;
9 use CoreFramework\Manager\ManageableApplication;
10 use CoreFramework\Object\BaseFrameworkSystem;
11 use CoreFramework\Response\Responseable;
14 * A generic template engine
16 * @author Roland Haeder <webmaster@shipsimu.org>
18 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team
19 * @license GNU GPL 3.0 or any newer version
20 * @link http://www.shipsimu.org
22 * This program is free software: you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation, either version 3 of the License, or
25 * (at your option) any later version.
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
32 * You should have received a copy of the GNU General Public License
33 * along with this program. If not, see <http://www.gnu.org/licenses/>.
35 class BaseTemplateEngine extends BaseFrameworkSystem {
37 * The local path name where all templates and sub folders for special
38 * templates are stored. We will internally determine the language plus
39 * "html" for web templates or "emails" for email templates
41 private $templateBasePath = '';
46 private $templateType = 'html';
49 * The extension for web and email templates (not compiled templates)
51 private $templateExtension = '.tpl';
54 * The extension for code templates (not compiled templates)
56 private $codeExtension = '.ctp';
59 * Path relative to $templateBasePath and language code for compiled code-templates
61 private $compileOutputPath = 'templates/_compiled/';
64 * The path name for all templates
66 private $genericBasePath = 'templates/';
69 * The raw (maybe uncompiled) template
71 private $rawTemplateData = '';
74 * Template data with compiled-in variables
76 private $compiledData = '';
79 * The last loaded template's FQFN for debugging the engine
81 private $lastTemplate = '';
84 * The variable stack for the templates
86 private $varStack = array();
89 * Loaded templates for recursive protection and detection
91 private $loadedTemplates = array();
94 * Compiled templates for recursive protection and detection
96 private $compiledTemplates = array();
99 * Loaded raw template data
101 private $loadedRawData = NULL;
104 * Raw templates which are linked in code templates
106 private $rawTemplates = NULL;
109 * A regular expression for variable=value pairs
111 private $regExpVarValue = '/([\w_]+)(="([^"]*)"|=([\w_]+))?/';
114 * A regular expression for filtering out code tags
116 * E.g.: {?template:variable=value;var2=value2;[...]?}
118 private $regExpCodeTags = '/\{\?([a-z_]+)(:("[^"]+"|[^?}]+)+)?\?\}/';
121 * A regular expression to find template comments like <!-- Comment here //-->
123 private $regExpComments = '/<!--[\w\W]*?(\/\/){0,1}-->/';
128 private $helpers = array();
131 * Current variable group
133 private $currGroup = 'general';
136 * All template groups except "general"
138 private $variableGroups = array();
143 private $codeBegin = '<?php';
148 private $codeEnd = '?>';
151 * Language support is enabled by default
153 private $languageSupport = true;
156 * XML compacting is disabled by default
158 private $xmlCompacting = false;
160 // Exception codes for the template engine
161 const EXCEPTION_TEMPLATE_TYPE_IS_UNEXPECTED = 0x110;
162 const EXCEPTION_TEMPLATE_CONTAINS_INVALID_VAR = 0x111;
163 const EXCEPTION_INVALID_VIEW_HELPER = 0x112;
164 const EXCEPTION_VARIABLE_IS_MISSING = 0x113;
167 * Protected constructor
169 * @param $className Name of the class
172 protected function __construct ($className) {
173 // Call parent constructor
174 parent::__construct($className);
176 // Init file I/O instance
177 $ioInstance = ObjectFactory::createObjectByConfiguredName('file_io_class');
180 $this->setFileIoInstance($ioInstance);
184 * Search for a variable in the stack
186 * @param $variableName The variable we are looking for
187 * @param $variableGroup Optional variable group to look in
188 * @return $index false means not found, >=0 means found on a specific index
190 private function getVariableIndex ($variableName, $variableGroup = NULL) {
191 // Replace all dashes to underscores to match variables with configuration entries
192 $variableName = trim(self::convertDashesToUnderscores($variableName));
194 // First everything is not found
197 // If the stack is NULL, use the current group
198 if (is_null($variableGroup)) {
200 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.' currGroup=' . $this->currGroup . ' set as stack!');
201 $variableGroup = $this->currGroup;
204 // Is the group there?
205 if ($this->isVarStackSet($variableGroup)) {
207 foreach ($this->getVarStack($variableGroup) as $index => $currEntry) {
208 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.':currGroup=' . $variableGroup . ',idx=' . $index . ',currEntry=' . $currEntry['name'] . ',variableName=' . $variableName);
209 // Is the entry found?
210 if ($currEntry['name'] == $variableName) {
212 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.':FOUND!');
219 // Return the current position
224 * Checks whether the given variable group is set
226 * @param $variableGroup Variable group to check
227 * @return $isSet Whether the given variable group is set
229 protected final function isVarStackSet ($variableGroup) {
231 $isSet = isset($this->varStack[$variableGroup]);
238 * Getter for given variable group
240 * @param $variableGroup Variable group to check
241 * @return $varStack Found variable group
243 public final function getVarStack ($variableGroup) {
244 return $this->varStack[$variableGroup];
248 * Setter for given variable group
250 * @param $variableGroup Variable group to check
251 * @param $varStack Variable stack to check
254 protected final function setVarStack ($variableGroup, array $varStack) {
255 $this->varStack[$variableGroup] = $varStack;
259 * Return a content of a variable or null if not found
261 * @param $variableName The variable we are looking for
262 * @param $variableGroup Optional variable group to look in
263 * @return $content Content of the variable or null if not found
265 protected function readVariable ($variableName, $variableGroup = NULL) {
266 // Replace all dashes to underscores to match variables with configuration entries
267 $variableName = trim(self::convertDashesToUnderscores($variableName));
269 // First everything is not found
272 // If the stack is NULL, use the current group
273 if (is_null($variableGroup)) {
275 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.' currGroup=' . $this->currGroup . ' set as stack!');
276 $variableGroup = $this->currGroup;
279 // Get variable index
280 $found = $this->getVariableIndex($variableName, $variableGroup);
282 // Is the variable found?
283 if ($found !== false) {
285 $content = $this->getVariableValue($variableGroup, $found);
288 // Return the current position
289 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.': variableGroup=' . $variableGroup . ',variableName=' . $variableName . ', content[' . gettype($content) . ']=' . $content);
294 * Add a variable to the stack
296 * @param $variableName Name of variable to add
297 * @param $value Value we want to store in the variable
300 private function addVariable ($variableName, $value) {
301 // Set general variable group
302 $this->setVariableGroup('general');
304 // Add it to the stack
305 $this->addGroupVariable($variableName, $value);
309 * Returns all variables of current group or empty array
311 * @return $result Whether array of found variables or empty array
313 private function readCurrentGroup () {
314 // Default is not found
317 // Is the group there?
318 if ($this->isVarStackSet($this->currGroup)) {
320 $result = $this->getVarStack($this->currGroup);
328 * Settter for variable group
330 * @param $groupName Name of variable group
331 * @param $add Whether add this group
334 public function setVariableGroup ($groupName, $add = true) {
336 //* DEBIG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.': currGroup=' . $groupName);
337 $this->currGroup = $groupName;
339 // Skip group 'general'
340 if (($groupName != 'general') && ($add === true)) {
341 $this->variableGroups[$groupName] = 'OK';
347 * Adds a variable to current group
349 * @param $variableName Variable to set
350 * @param $value Value to store in variable
353 public function addGroupVariable ($variableName, $value) {
354 // Replace all dashes to underscores to match variables with configuration entries
355 $variableName = trim(self::convertDashesToUnderscores($variableName));
358 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.': group=' . $this->currGroup . ', variableName=' . $variableName . ', value=' . $value);
360 // Get current variables in group
361 $currVars = $this->readCurrentGroup();
363 // Append our variable
364 array_push($currVars, $this->generateVariableArray($variableName, $value));
366 // Add it to the stack
367 $this->setVarStack($this->currGroup, $currVars);
371 * Getter for variable value, throws a NoVariableException if the variable is not found
373 * @param $variableGroup Variable group to use
374 * @param $index Index in variable array
375 * @return $value Value to set
377 private function getVariableValue ($variableGroup, $index) {
379 return $this->varStack[$variableGroup][$index]['value'];
383 * Modify an entry on the stack
385 * @param $variableName The variable we are looking for
386 * @param $value The value we want to store in the variable
388 * @throws NoVariableException If the given variable is not found
390 private function modifyVariable ($variableName, $value) {
391 // Replace all dashes to underscores to match variables with configuration entries
392 $variableName = trim(self::convertDashesToUnderscores($variableName));
394 // Get index for variable
395 $index = $this->getVariableIndex($variableName);
397 // Is the variable set?
398 if ($index === false) {
399 // Unset variables cannot be modified
400 throw new NoVariableException(array($this, $variableName, $value), self::EXCEPTION_VARIABLE_IS_MISSING);
404 $this->setVariableValue($this->currGroup, $index, $value);
408 * Sets a variable value for given variable group and index
410 * @param $variableGroup Variable group to use
411 * @param $index Index in variable array
412 * @param $value Value to set
415 private function setVariableValue ($variableGroup, $index, $value) {
416 $this->varStack[$variableGroup][$index]['value'] = $value;
420 * Sets a variable within given group. This method does detect if the
421 * variable is already set. If so, the variable got modified, otherwise
424 * @param $variableGroup Variable group to use
425 * @param $variableName Variable to set
426 * @param $value Value to set
429 protected function setVariable ($variableGroup, $variableName, $value) {
430 // Replace all dashes to underscores to match variables with configuration entries
431 $variableName = trim(self::convertDashesToUnderscores($variableName));
433 // Get index for variable
434 $index = $this->getVariableIndex($variableName);
436 // Is the variable set?
437 if ($index === false) {
438 // Is the stack there?
439 if (!isset($this->varStack[$variableGroup])) {
440 // Then initialize it here
441 $this->varStack[$variableGroup] = array();
445 array_push($this->varStack[$variableGroup], $this->generateVariableArray($variableName, $value));
448 $this->setVariableValue($this->currGroup, $index, $value);
453 * "Generates" (better returns) an array for all variables for given
454 * variable/value pay.
456 * @param $variableName Variable to set
457 * @param $value Value to set
458 * @return $varData Variable data array
460 private function generateVariableArray ($variableName, $value) {
461 // Replace all dashes to underscores to match variables with configuration entries
462 $variableName = trim(self::convertDashesToUnderscores($variableName));
464 // Generate the temporary array
466 'name' => $variableName,
475 * Setter for template type. Only 'html', 'emails' and 'compiled' should
478 * @param $templateType The current template's type
481 protected final function setTemplateType ($templateType) {
482 $this->templateType = (string) $templateType;
486 * Getter for template type
488 * @return $templateType The current template's type
490 public final function getTemplateType () {
491 return $this->templateType;
495 * Setter for the last loaded template's FQFN
497 * @param $template The last loaded template
500 private final function setLastTemplate ($template) {
501 $this->lastTemplate = (string) $template;
505 * Getter for the last loaded template's FQFN
507 * @return $template The last loaded template
509 private final function getLastTemplate () {
510 return $this->lastTemplate;
514 * Setter for base path
516 * @param $templateBasePath The relative base path for all templates
519 protected final function setTemplateBasePath ($templateBasePath) {
521 $this->templateBasePath = (string) $templateBasePath;
525 * Getter for base path
527 * @return $templateBasePath The relative base path for all templates
529 public final function getTemplateBasePath () {
531 return $this->templateBasePath;
535 * Getter for generic base path
537 * @return $templateBasePath The relative base path for all templates
539 public final function getGenericBasePath () {
541 return $this->genericBasePath;
545 * Setter for template extension
547 * @param $templateExtension The file extension for all uncompiled
551 protected final function setRawTemplateExtension ($templateExtension) {
553 $this->templateExtension = (string) $templateExtension;
557 * Setter for code template extension
559 * @param $codeExtension The file extension for all uncompiled
563 protected final function setCodeTemplateExtension ($codeExtension) {
565 $this->codeExtension = (string) $codeExtension;
569 * Getter for template extension
571 * @return $templateExtension The file extension for all uncompiled
574 public final function getRawTemplateExtension () {
576 return $this->templateExtension;
580 * Getter for code-template extension
582 * @return $codeExtension The file extension for all code-
585 public final function getCodeTemplateExtension () {
587 return $this->codeExtension;
591 * Setter for path of compiled templates
593 * @param $compileOutputPath The local base path for all compiled
597 protected final function setCompileOutputPath ($compileOutputPath) {
599 $this->compileOutputPath = (string) $compileOutputPath;
603 * Unsets the given offset in the variable group
605 * @param $index Index to unset
606 * @param $variableGroup Variable group (default: currGroup)
609 protected final function unsetVariableStackOffset ($index, $variableGroup = NULL) {
610 // Is the variable group not set?
611 if (is_null($variableGroup)) {
612 // Then set it to current
613 $variableGroup = $this->currGroup;
616 // Is the entry there?
617 if (!isset($this->varStack[$variableGroup][$index])) {
618 // Abort here, we need fixing!
619 $this->debugInstance();
623 unset($this->varStack[$variableGroup][$index]);
627 * Private setter for raw template data
629 * @param $rawTemplateData The raw data from the template
632 protected final function setRawTemplateData ($rawTemplateData) {
633 // And store it in this class
634 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.': ' . strlen($rawTemplateData) . ' Bytes set.');
635 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(__METHOD__.': ' . $this->currGroup . ' variables: ' . count($this->getVarStack($this->currGroup)) . ', groups=' . count($this->varStack));
636 $this->rawTemplateData = (string) $rawTemplateData;
640 * Getter for raw template data
642 * @return $rawTemplateData The raw data from the template
644 public final function getRawTemplateData () {
645 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: ' . strlen($this->rawTemplateData) . ' Bytes read.');
646 return $this->rawTemplateData;
650 * Private setter for compiled templates
654 private final function setCompiledData ($compiledData) {
655 // And store it in this class
656 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: ' . strlen($compiledData) . ' Bytes set.');
657 $this->compiledData = (string) $compiledData;
661 * Getter for compiled templates, must be public for e.g. Mailer classes.
663 * @return $compiledData Compiled template data
665 public final function getCompiledData () {
666 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: ' . strlen($this->compiledData) . ' Bytes read.');
667 return $this->compiledData;
671 * Private loader for all template types
673 * @param $template The template we shall load
674 * @param $extOther An other extension to use
676 * @throws FileNotFoundException If the template was not found
678 protected function loadTemplate ($template, $extOther = '') {
679 // Get extension for the template if empty
680 if (empty($extOther)) {
681 // None provided, so get the raw one
682 $ext = $this->getRawTemplateExtension();
685 $ext = (string) $extOther;
689 * Construct the FQFN for the template without language as language is
690 * now entirely done by php_intl. These old thing with language-based
691 * template paths comes from an older time.
693 $fqfn = sprintf('%s%s%s%s/%s%s',
694 $this->getConfigInstance()->getConfigEntry('application_base_path'),
695 $this->getTemplateBasePath(),
696 $this->getGenericBasePath(),
697 $this->getTemplateType(),
704 // Load the raw template data
705 $this->loadRawTemplateData($fqfn);
706 } catch (FileNotFoundException $e) {
707 // If we shall load a code-template we need to switch the file extension
708 if (($this->getTemplateType() != $this->getConfigInstance()->getConfigEntry('html_template_type')) && (empty($extOther))) {
709 // Switch over to the code-template extension and try it again
710 $ext = $this->getCodeTemplateExtension();
713 $this->loadTemplate($template, $ext);
716 throw new FileNotFoundException($fqfn, self::EXCEPTION_FILE_NOT_FOUND);
723 * A private loader for raw template names
725 * @param $fqfn The full-qualified file name for a template
728 private function loadRawTemplateData ($fqfn) {
729 // Some debug code to look on the file which is being loaded
730 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: FQFN=' . $fqfn);
732 // Load the raw template
733 $rawTemplateData = $this->getFileIoInstance()->loadFileContents($fqfn);
735 // Store the template's contents into this class
736 $this->setRawTemplateData($rawTemplateData);
738 // Remember the template's FQFN
739 $this->setLastTemplate($fqfn);
743 * Try to assign an extracted template variable as a "content" or 'config'
746 * @param $variableName The variable's name (shall be content or config)
748 * @param $variableName The variable we want to assign
751 private function assignTemplateVariable ($variableName, $var) {
752 // Replace all dashes to underscores to match variables with configuration entries
753 $variableName = trim(self::convertDashesToUnderscores($variableName));
756 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: variableName=' . $variableName . ',variableName=' . $variableName);
758 // Is it not a config variable?
759 if ($variableName != 'config') {
760 // Regular template variables
761 $this->assignVariable($variableName, '');
763 // Configuration variables
764 $this->assignConfigVariable($var);
769 * Extract variables from a given raw data stream
771 * @param $rawData The raw template data we shall analyze
774 private function extractVariablesFromRawData ($rawData) {
776 $rawData = (string) $rawData;
778 // Search for variables
779 preg_match_all('/\$(\w+)(\[(\w+)\])?/', $rawData, $variableMatches);
782 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:rawData(' . strlen($rawData) . ')=' . $rawData . ',variableMatches=' . print_r($variableMatches, true));
784 // Did we find some variables?
785 if ((is_array($variableMatches)) && (count($variableMatches) == 4) && (count($variableMatches[0]) > 0)) {
786 // Initialize all missing variables
787 foreach ($variableMatches[3] as $key => $var) {
789 $variableName = $variableMatches[1][$key];
791 // Workarround: Do not assign empty variables
793 // Try to assign it, empty strings are being ignored
794 $this->assignTemplateVariable($variableName, $var);
801 * Main analysis of the loaded template
803 * @param $templateMatches Found template place-holders, see below
806 *---------------------------------
807 * Structure of $templateMatches:
808 *---------------------------------
809 * [0] => Array - An array with all full matches
810 * [1] => Array - An array with left part (before the ':') of a match
811 * [2] => Array - An array with right part of a match including ':'
812 * [3] => Array - An array with right part of a match excluding ':'
814 private function analyzeTemplate (array $templateMatches) {
815 // Backup raw template data
816 $backup = $this->getRawTemplateData();
818 // Initialize some arrays
819 if (is_null($this->loadedRawData)) {
821 $this->loadedRawData = array();
822 $this->rawTemplates = array();
825 // Load all requested templates
826 foreach ($templateMatches[1] as $template) {
827 // Load and compile only templates which we have not yet loaded
828 // RECURSIVE PROTECTION! BE CAREFUL HERE!
829 if ((!isset($this->loadedRawData[$template])) && (!in_array($template, $this->loadedTemplates))) {
831 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:template=' . $template);
833 // Template not found, but maybe variable assigned?
834 if ($this->getVariableIndex($template) !== false) {
835 // Use that content here
836 $this->loadedRawData[$template] = $this->readVariable($template);
838 // Recursive protection:
839 array_push($this->loadedTemplates, $template);
841 // Then try to search for code-templates
843 // Load the code template and remember it's contents
844 $this->loadCodeTemplate($template);
845 $this->loadedRawData[$template] = $this->getRawTemplateData();
847 // Remember this template for recursion detection
848 // RECURSIVE PROTECTION!
849 array_push($this->loadedTemplates, $template);
850 } catch (FileNotFoundException $e) {
851 // Even this is not done... :/
852 array_push($this->rawTemplates, $template);
858 // Restore the raw template data
859 $this->setRawTemplateData($backup);
863 * Compile a given raw template code and remember it for later usage
865 * @param $code The raw template code
866 * @param $template The template's name
869 private function compileCode ($code, $template) {
870 // Is this template already compiled?
871 if (in_array($template, $this->compiledTemplates)) {
876 // Remember this template being compiled
877 array_push($this->compiledTemplates, $template);
879 // Compile the loaded code in five steps:
881 // 1. Backup current template data
882 $backup = $this->getRawTemplateData();
884 // 2. Set the current template's raw data as the new content
885 $this->setRawTemplateData($code);
887 // 3. Compile the template data
888 $this->compileTemplate();
890 // 4. Remember it's contents
891 $this->loadedRawData[$template] = $this->getRawTemplateData();
893 // 5. Restore the previous raw content from backup variable
894 $this->setRawTemplateData($backup);
898 * Insert all given and loaded templates by running through all loaded
899 * codes and searching for their place-holder in the main template
901 * @param $templateMatches See method analyzeTemplate()
904 private function insertAllTemplates (array $templateMatches) {
905 // Run through all loaded codes
906 foreach ($this->loadedRawData as $template => $code) {
908 // Search for the template
909 $foundIndex = array_search($template, $templateMatches[1]);
911 // Lookup the matching template replacement
912 if (($foundIndex !== false) && (isset($templateMatches[0][$foundIndex]))) {
914 // Get the current raw template
915 $rawData = $this->getRawTemplateData();
917 // Replace the space holder with the template code
918 $rawData = str_replace($templateMatches[0][$foundIndex], $code, $rawData);
920 // Set the new raw data
921 $this->setRawTemplateData($rawData);
927 * Load all extra raw templates
931 private function loadExtraRawTemplates () {
932 // Are there some raw templates we need to load?
933 if (count($this->rawTemplates) > 0) {
934 // Try to load all raw templates
935 foreach ($this->rawTemplates as $key => $template) {
938 $this->loadHtmlTemplate($template);
940 // Remember it's contents
941 $this->rawTemplates[$template] = $this->getRawTemplateData();
943 // Remove it from the loader list
944 unset($this->rawTemplates[$key]);
946 // Remember this template for recursion detection
947 // RECURSIVE PROTECTION!
948 array_push($this->loadedTemplates, $template);
949 } catch (FileNotFoundException $e) {
950 // This template was never found. We silently ignore it
951 unset($this->rawTemplates[$key]);
958 * Assign all found template variables
960 * @param $varMatches An array full of variable/value pairs.
962 * @todo Unfinished work or don't die here.
964 private function assignAllVariables (array $varMatches) {
966 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:varMatches()=' . count($varMatches));
968 // Search for all variables
969 foreach ($varMatches[1] as $key => $var) {
970 // Replace all dashes to underscores to match variables with configuration entries
971 $var = trim(self::convertDashesToUnderscores($var));
974 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:key=' . $key . ',var=' . $var);
976 // Detect leading equals
977 if (substr($varMatches[2][$key], 0, 1) == '=') {
978 // Remove and cast it
979 $varMatches[2][$key] = (string) substr($varMatches[2][$key], 1);
982 // Do we have some quotes left and right side? Then it is free text
983 if ((substr($varMatches[2][$key], 0, 1) == '"') && (substr($varMatches[2][$key], -1, 1) == '"')) {
984 // Free string detected! Which we can assign directly
985 $this->assignVariable($var, $varMatches[3][$key]);
986 } elseif (!empty($varMatches[2][$key])) {
987 // @TODO Non-string found so we need some deeper analysis...
988 ApplicationEntryPoint::app_exit('Deeper analysis not yet implemented!');
994 * Compiles all loaded raw templates
996 * @param $templateMatches See method analyzeTemplate() for details
999 private function compileRawTemplateData (array $templateMatches) {
1001 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:loadedRawData()= ' .count($this->loadedRawData));
1003 // Are some code-templates found which we need to compile?
1004 if (count($this->loadedRawData) > 0) {
1005 // Then compile all!
1006 foreach ($this->loadedRawData as $template => $code) {
1008 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:template=' . $template . ',code(' . strlen($code) . ')=' . $code);
1010 // Is this template already compiled?
1011 if (in_array($template, $this->compiledTemplates)) {
1013 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: Template ' . $template . ' already compiled. SKIPPED!');
1017 // Search for the template
1018 $foundIndex = array_search($template, $templateMatches[1]);
1020 // Lookup the matching variable data
1021 if (($foundIndex !== false) && (isset($templateMatches[3][$foundIndex]))) {
1022 // Split it up with another reg. exp. into variable=value pairs
1023 preg_match_all($this->regExpVarValue, $templateMatches[3][$foundIndex], $varMatches);
1024 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:varMatches=' . print_r($varMatches, true));
1026 // Assign all variables
1027 $this->assignAllVariables($varMatches);
1028 } // END - if (isset($templateMatches ...
1030 // Compile the loaded template
1031 $this->compileCode($code, $template);
1032 } // END - foreach ($this->loadedRawData ...
1034 // Insert all templates
1035 $this->insertAllTemplates($templateMatches);
1036 } // END - if (count($this->loadedRawData) ...
1040 * Inserts all raw templates into their respective variables
1044 private function insertRawTemplates () {
1045 // Load all templates
1046 foreach ($this->rawTemplates as $template => $content) {
1047 // Set the template as a variable with the content
1048 $this->assignVariable($template, $content);
1053 * Finalizes the compilation of all template variables
1057 private function finalizeVariableCompilation () {
1059 $content = $this->getRawTemplateData();
1060 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: content before=' . strlen($content) . ' (' . md5($content) . ')');
1062 // Do we have the stack?
1063 if (!$this->isVarStackSet('general')) {
1064 // Abort here silently
1065 // @TODO This silent abort should be logged, maybe.
1069 // Walk through all variables
1070 foreach ($this->getVarStack('general') as $currEntry) {
1071 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: name=' . $currEntry['name'] . ', value=<pre>' . htmlentities($currEntry['value']) . '</pre>');
1072 // Replace all [$var] or {?$var?} with the content
1073 // @TODO Old behaviour, will become obsolete!
1074 $content = str_replace('$content[' . $currEntry['name'] . ']', $currEntry['value'], $content);
1076 // @TODO Yet another old way
1077 $content = str_replace('[' . $currEntry['name'] . ']', $currEntry['value'], $content);
1079 // The new behaviour
1080 $content = str_replace('{?' . $currEntry['name'] . '?}', $currEntry['value'], $content);
1083 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: content after=' . strlen($content) . ' (' . md5($content) . ')');
1085 // Set the content back
1086 $this->setRawTemplateData($content);
1090 * Load a specified HTML template into the engine
1092 * @param $template The web template we shall load which is located in
1096 public function loadHtmlTemplate ($template) {
1097 // Set template type
1098 $this->setTemplateType($this->getConfigInstance()->getConfigEntry('html_template_type'));
1100 // Load the special template
1101 $this->loadTemplate($template);
1105 * Assign (add) a given variable with a value
1107 * @param $variableName The variable we are looking for
1108 * @param $value The value we want to store in the variable
1110 * @throws EmptyVariableException If the variable name is left empty
1112 public final function assignVariable ($variableName, $value) {
1113 // Replace all dashes to underscores to match variables with configuration entries
1114 $variableName = trim(self::convertDashesToUnderscores($variableName));
1116 // Empty variable found?
1117 if (empty($variableName)) {
1118 // Throw an exception
1119 throw new EmptyVariableException(array($this, 'variableName'), self::EXCEPTION_UNEXPECTED_EMPTY_STRING);
1122 // First search for the variable if it was already added
1123 $index = $this->getVariableIndex($variableName);
1126 if ($index === false) {
1127 // Add it to the stack
1128 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:ADD: ' . $variableName . '[' . gettype($value) . ']=' . $value);
1129 $this->addVariable($variableName, $value);
1130 } elseif (!empty($value)) {
1131 // Modify the stack entry
1132 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:MOD: ' . $variableName . '[' . gettype($value) . ']=' . $value);
1133 $this->modifyVariable($variableName, $value);
1138 * Removes a given variable
1140 * @param $variableName The variable we are looking for
1141 * @param $variableGroup Name of variable group (default: 'general')
1144 public final function removeVariable ($variableName, $variableGroup = 'general') {
1145 // First search for the variable if it was already added
1146 $index = $this->getVariableIndex($variableName, $variableGroup);
1149 if ($index !== false) {
1150 // Remove this variable
1151 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:UNSET: variableGroup=' . $variableGroup . ',variableName=' . $variableName . ',index=' . $index);
1152 $this->unsetVariableStackOffset($index, $variableGroup);
1157 * Assigns the last loaded raw template content with a given variable
1159 * @param $templateName Name of the template we want to assign
1160 * @param $variableName Name of the variable we want to assign
1163 public function assignTemplateWithVariable ($templateName, $variableName) {
1164 // Get the content from last loaded raw template
1165 $content = $this->getRawTemplateData();
1167 // Assign the variable
1168 $this->assignVariable($variableName, $content);
1170 // Purge raw content
1171 $this->setRawTemplateData('');
1175 * Assign a given congfiguration variable with a value
1177 * @param $variableName The configuration variable we want to assign
1180 public function assignConfigVariable ($variableName) {
1181 // Replace all dashes to underscores to match variables with configuration entries
1182 $variableName = trim(self::convertDashesToUnderscores($variableName));
1184 // Sweet and simple...
1185 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: variableName=' . $variableName . ',getConfigEntry()=' . $this->getConfigInstance()->getConfigEntry($variableName));
1186 $this->assignVariable($variableName, $this->getConfigInstance()->getConfigEntry($variableName));
1190 * Assigns a lot variables into the stack of currently loaded template.
1191 * This method should only be used in very rare circumstances, e.g. when
1192 * you have to copy a whole set of variables into the template engine.
1193 * Before you use this method, please make sure you have considered all
1194 * other possiblities.
1196 * @param $variables An array with variables to be assigned
1199 public function assignMultipleVariables (array $variables) {
1201 foreach ($variables as $name => $value) {
1202 // Set variable with name for 'config' group
1203 $this->assignVariable($name, $value);
1208 * Assigns all the application data with template variables
1210 * @param $applicationInstance A manageable application instance
1213 public function assignApplicationData (ManageableApplication $applicationInstance) {
1214 // Get long name and assign it
1215 $this->assignVariable('app_full_name' , $applicationInstance->getAppName());
1217 // Get short name and assign it
1218 $this->assignVariable('app_short_name', $applicationInstance->getAppShortName());
1220 // Get version number and assign it
1221 $this->assignVariable('app_version' , $applicationInstance->getAppVersion());
1223 // Assign extra application-depending data
1224 $applicationInstance->assignExtraTemplateData($this);
1228 * Load a specified code template into the engine
1230 * @param $template The code template we shall load which is
1231 * located in 'code' by default
1234 public function loadCodeTemplate ($template) {
1235 // Set template type
1236 $this->setTemplateType($this->getConfigInstance()->getConfigEntry('code_' . FrameworkBootstrap::getRequestTypeFromSystem() . '_template_type'));
1238 // Load the special template
1239 $this->loadTemplate($template);
1243 * Load a specified email template into the engine
1245 * @param $template The email template we shall load which is
1246 * located in 'emails' by default
1249 public function loadEmailTemplate ($template) {
1250 // Set template type
1251 $this->setTemplateType($this->getConfigInstance()->getConfigEntry('email_template_type'));
1253 // Load the special template
1254 $this->loadTemplate($template);
1258 * Compiles configuration place-holders in all variables. This 'walks'
1259 * through the variable group 'general'. It interprets all values from that
1260 * variables as configuration entries after compiling them.
1264 public final function compileConfigInVariables () {
1265 // Do we have the stack?
1266 if (!$this->isVarStackSet('general')) {
1267 // Abort here silently
1268 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: Aborted, variable stack general not found!');
1272 // Iterate through all general variables
1273 foreach ($this->getVarStack('general') as $index => $currVariable) {
1274 // Compile the value
1275 $value = $this->compileRawCode($this->readVariable($currVariable['name']), true);
1278 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: name=' . $currVariable['name'] . ',value=' . $value);
1280 // Remove it from stack
1281 $this->removeVariable($currVariable['name'], 'general');
1282 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: value='. $value . ',name=' . $currVariable['name'] . ',index=' . $index);
1284 // Is it a configuration key?
1285 if ($this->getConfigInstance()->isConfigurationEntrySet($value)) {
1286 // The value itself is a configuration entry
1287 $this->assignConfigVariable($value);
1289 // Re-assign the value directly
1290 $this->assignVariable($currVariable['name'], $value);
1296 * Compile all variables by inserting their respective values
1299 * @todo Make this code some nicer...
1301 public final function compileVariables () {
1302 // Initialize the $content array
1303 $validVar = $this->getConfigInstance()->getConfigEntry('tpl_valid_var');
1306 // Iterate through all general variables
1307 foreach ($this->getVarStack('general') as $currVariable) {
1308 // Transfer it's name/value combination to the $content array
1309 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:' . $currVariable['name'] . '=<pre>' . htmlentities($currVariable['value']).'</pre>');
1310 $dummy[$currVariable['name']] = $currVariable['value'];
1313 // Set the new variable (don't remove the second dollar!)
1314 $$validVar = $dummy;
1316 // Remove some variables
1318 unset($currVariable);
1320 // Run the compilation three times to get content from helper classes in
1323 // Finalize the compilation of template variables
1324 $this->finalizeVariableCompilation();
1326 // Prepare the eval() command for comiling the template
1327 $eval = sprintf('$result = "%s";',
1328 addslashes($this->getRawTemplateData())
1331 // This loop does remove the backslashes (\) in PHP parameters
1332 while (strpos($eval, $this->codeBegin) !== false) {
1333 // Get left part before "<?"
1334 $evalLeft = substr($eval, 0, strpos($eval, $this->codeBegin));
1336 // Get all from right of "<?"
1337 $evalRight = substr($eval, (strpos($eval, $this->codeBegin) + 5));
1339 // Cut middle part out and remove escapes
1340 $evalMiddle = trim(substr($evalRight, 0, strpos($evalRight, $this->codeEnd)));
1341 $evalMiddle = stripslashes($evalMiddle);
1343 // Remove the middle part from right one
1344 $evalRight = substr($evalRight, (strpos($evalRight, $this->codeEnd) + 2));
1346 // And put all together
1347 $eval = sprintf('%s<%%php %s %%>%s', $evalLeft, $evalMiddle, $evalRight);
1350 // Prepare PHP code for eval() command
1351 $eval = str_replace(
1360 // Run the constructed command. This will "compile" all variables in
1363 // Goes something wrong?
1364 if ((!isset($result)) || (empty($result))) {
1365 // Output eval command
1366 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('Failed eval() code: <pre>%s</pre>', $this->markupCode($eval, true)), true);
1368 // Output backtrace here
1369 $this->debugBackTrace();
1372 // Set raw template data
1373 $this->setRawTemplateData($result);
1377 // Final variable assignment
1378 $this->finalizeVariableCompilation();
1380 // Set the new content
1381 $this->setCompiledData($this->getRawTemplateData());
1385 * Compile all required templates into the current loaded one
1388 * @throws UnexpectedTemplateTypeException If the template type is
1390 * @throws InvalidArrayCountException If an unexpected array
1391 * count has been found
1393 public function compileTemplate () {
1394 // Get code type to make things shorter
1395 $codeType = $this->getConfigInstance()->getConfigEntry('code_' . FrameworkBootstrap::getRequestTypeFromSystem() . '_template_type');
1397 // We will only work with template type "code" from configuration
1398 if (substr($this->getTemplateType(), 0, strlen($codeType)) != $codeType) {
1400 throw new UnexpectedTemplateTypeException(array($this, $this->getTemplateType(), $this->getConfigInstance()->getConfigEntry('code_' . FrameworkBootstrap::getRequestTypeFromSystem() . '_template_type')), self::EXCEPTION_TEMPLATE_TYPE_IS_UNEXPECTED);
1403 // Get the raw data.
1404 $rawData = $this->getRawTemplateData();
1406 // Remove double spaces and trim leading/trailing spaces
1407 $rawData = trim(str_replace(' ', ' ', $rawData));
1409 // Search for raw variables
1410 $this->extractVariablesFromRawData($rawData);
1412 // Search for code-tags which are {? ?}
1413 preg_match_all($this->regExpCodeTags, $rawData, $templateMatches);
1416 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:templateMatches=' . print_r($templateMatches , true));
1418 // Analyze the matches array
1419 if ((is_array($templateMatches)) && (count($templateMatches) == 4) && (count($templateMatches[0]) > 0)) {
1420 // Entries are found:
1422 // The main analysis
1423 $this->analyzeTemplate($templateMatches);
1425 // Compile raw template data
1426 $this->compileRawTemplateData($templateMatches);
1428 // Are there some raw templates left for loading?
1429 $this->loadExtraRawTemplates();
1431 // Are some raw templates found and loaded?
1432 if (count($this->rawTemplates) > 0) {
1433 // Insert all raw templates
1434 $this->insertRawTemplates();
1436 // Remove the raw template content as well
1437 $this->setRawTemplateData('');
1439 } // END - if($templateMatches ...
1443 * Loads a given view helper (by name)
1445 * @param $helperName The helper's name
1448 protected function loadViewHelper ($helperName) {
1449 // Is this view helper loaded?
1450 if (!isset($this->helpers[$helperName])) {
1451 // Create a class name
1452 $className = self::convertToClassName($helperName) . 'ViewHelper';
1454 // Generate new instance
1455 $this->helpers[$helperName] = ObjectFactory::createObjectByName($className);
1458 // Return the requested instance
1459 return $this->helpers[$helperName];
1463 * Transfers the content of this template engine to a given response instance
1465 * @param $responseInstance An instance of a Responseable class
1468 public function transferToResponse (Responseable $responseInstance) {
1469 // Get the content and set it in response class
1470 $responseInstance->writeToBody($this->getCompiledData());
1474 * "Compiles" a variable by replacing {?var?} with it's content
1476 * @param $rawCode Raw code to compile
1477 * @param $setMatchAsCode Sets $match if readVariable() returns empty result
1478 * @return $rawCode Compile code with inserted variable value
1480 public function compileRawCode ($rawCode, $setMatchAsCode=false) {
1481 // Find the variables
1482 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:rawCode=<pre>' . htmlentities($rawCode) . '</pre>');
1483 preg_match_all($this->regExpVarValue, $rawCode, $varMatches);
1485 // Compile all variables
1486 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:<pre>' . print_r($varMatches, true) . '</pre>');
1487 foreach ($varMatches[0] as $match) {
1488 // Add variable tags around it
1489 $varCode = '{?' . $match . '?}';
1492 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:varCode=' . $varCode);
1494 // Is the variable found in code? (safes some calls)
1495 if (strpos($rawCode, $varCode) !== false) {
1497 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: match=' . $match . ',rawCode[' . gettype($rawCode) . ']=' . $rawCode);
1499 // Use $match as new value or $value from read variable?
1500 if ($setMatchAsCode === true) {
1502 $rawCode = str_replace($varCode, $match, $rawCode);
1504 // Read the variable
1505 $value = $this->readVariable($match);
1508 $rawCode = str_replace($varCode, $value, $rawCode);
1513 // Return the compiled data
1514 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']:rawCode=<pre>' . htmlentities($rawCode) . '</pre>');
1519 * Getter for variable group array
1521 * @return $variableGroups All variable groups
1523 public final function getVariableGroups () {
1524 return $this->variableGroups;
1528 * Renames a variable in code and in stack
1530 * @param $oldName Old name of variable
1531 * @param $newName New name of variable
1534 public function renameVariable ($oldName, $newName) {
1535 //* DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-TEMPLATE[' . __METHOD__ . ':' . __LINE__ . ']: oldName=' . $oldName . ', newName=' . $newName);
1536 // Get raw template code
1537 $rawData = $this->getRawTemplateData();
1540 $rawData = str_replace($oldName, $newName, $rawData);
1542 // Set the code back
1543 $this->setRawTemplateData($rawData);
1547 * Renders the given XML content
1549 * @param $content Valid XML content or if not set the current loaded raw content
1551 * @throws XmlParserException If an XML error was found
1553 public function renderXmlContent ($content = NULL) {
1554 // Is the content set?
1555 if (is_null($content)) {
1556 // Get current content
1557 $content = $this->getRawTemplateData();
1560 // Get a XmlParser instance
1561 $parserInstance = ObjectFactory::createObjectByConfiguredName('xml_parser_class', array($this));
1563 // Check if XML compacting is enabled
1564 if ($this->isXmlCompactingEnabled()) {
1565 // Yes, so get a decorator class for transparent compacting
1566 $parserInstance = ObjectFactory::createObjectByConfiguredName('deco_compacting_xml_parser_class', array($parserInstance));
1569 // Parse the XML document
1570 $parserInstance->parseXmlContent($content);
1574 * Enables or disables language support
1576 * @param $languageSupport New language support setting
1579 public final function enableLanguageSupport ($languageSupport = true) {
1580 $this->languageSupport = (bool) $languageSupport;
1584 * Checks whether language support is enabled
1586 * @return $languageSupport Whether language support is enabled or disabled
1588 public final function isLanguageSupportEnabled () {
1589 return $this->languageSupport;
1593 * Enables or disables XML compacting
1595 * @param $xmlCompacting New XML compacting setting
1598 public final function enableXmlCompacting ($xmlCompacting = true) {
1599 $this->xmlCompacting = (bool) $xmlCompacting;
1603 * Checks whether XML compacting is enabled
1605 * @return $xmlCompacting Whether XML compacting is enabled or disabled
1607 public final function isXmlCompactingEnabled () {
1608 return $this->xmlCompacting;
1612 * Removes all commentd, tabs and new-line characters to compact the content
1614 * @param $uncompactedContent The uncompacted content
1615 * @return $compactedContent The compacted content
1617 public function compactContent ($uncompactedContent) {
1618 // First, remove all tab/new-line/revert characters
1619 $compactedContent = str_replace(chr(9), '', str_replace(chr(10), '', str_replace(chr(13), '', $uncompactedContent)));
1621 // Then regex all comments like <!-- //--> away
1622 preg_match_all($this->regExpComments, $compactedContent, $matches);
1624 // Do we have entries?
1625 if (isset($matches[0][0])) {
1627 foreach ($matches[0] as $match) {
1629 $compactedContent = str_replace($match, '', $compactedContent);
1633 // Set the content again
1634 $this->setRawTemplateData($compactedContent);
1636 // Return compacted content
1637 return $compactedContent;