* * The included "THW Thema" templates, logos and the Q&A catalog are protected * by copyright laws, and must not be used without the written permission * of the * * Bundesanstalt Technisches Hilfswerk * Provinzialstraße 93 * D-53127 Bonn * Germany * E-Mail: redaktion@thw.de * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /************************************************************ * Template class * * This class is capable of replacing variables with text, * including other templates, and it can parse dynamic blocks * which can be nested inside other blocks. * * Predefined template variables: * * - {templatePath} Contains the template path * - {templateLang} Contains the country code, if set * * If you don't need them, remove them immediately after * creating the class instance or setting a new template path. * **************************************************************/ class Template { /** * @var array Contains all template filenames */ private array $templateFiles = array(); /** * @var array Contains all template data */ private array $templateData = array(); /** * @var array Already loaded templates. */ private array $loadedTemplates = array(); /** * @var array Array of variable/value pairs stored as $LOADED[HANDLE]=>true */ private array $parserVariables = array(); /** * @var array Handles which contain parsed templates to be replaced in templates */ private array $templateHandles = array(); /** * @var array Array with dynamic block data */ private array $templateBlocks = array(); /** * @var array Parsed block data */ private array $parsedBlocks = array(); /** * @var string Path to template files. Filename: $templateDirectory/filename[.$languageCode].tpl */ private string $templateDirectory; /** * @var string Country code (en, de, fr, ...) */ private string $languageCode; /** * @var string Filename extension (default is .tpl) */ private string $templateExtension; /** * @var bool Safely replaces curly braces with HTML entities in variable values if set to true. */ public bool $replaceBraces = true; /** * @var bool Set to "true" on Windows systems, if you encounter problems with paths. */ public bool $useWindowsPathSeparator = false; /** * @var bool If set to true, die() is called in error */ public bool $dieOnError = true; /** * constructor. * @param string $tpldir The template base directory. * @param string $lang The browser language code. * @param string $tplext Optional. The template file extension. */ function __construct(string $tpldir, string $lang = "", string $tplext = "tpl") { $this->setPath($tpldir); $this->languageCode = strtolower($lang); $this->templateExtension = $tplext; $this->addVars("templateLang", $this->languageCode); } /** * Sets or changes the template search path * @param string $path New template base directory. * @return bool true if the path was set correctly, false if an error occurred. */ function setPath(string $path): bool { if ($this->useWindowsPathSeparator) { if (ord(substr($path, -1)) != 92) { $path .= chr(92); } } else { if (ord(substr($path, -1)) != 47) { $path .= chr(47); } } if (is_dir($path)) { $this->templateDirectory = $path; // Add a path variable for use in templates $this->addVars("templatePath", $path); return true; } else { echo "[TEMPLATE ENGINE] The specified template path " . "'" . $path . "' is invalid!
"; $this->templateDirectory = ""; if ($this->dieOnError) { die(); } return false; } } /** * Get dynamic blocks recursively to enable nested blocks. * @param string $tplFilename Template base filename. * @param string $contents Content to search for dynamic blocks. * @return void */ function getDynamicBlocks(string $tplFilename, string $contents): void { preg_match_all("/(\{\|([a-zA-Z0-9]+)})(.*?)\\1/s", $contents, $blocks); if (empty($blocks[0])) { return; } // Go through all blocks and save them in $this->BLOCKS for ($I = 0; $I < count($blocks[0]); $I++) { $blockparts = array(); preg_match_all("/(\{\|" . $blocks[2][$I] . "\*([a-zA-Z0-9]+)})(.*?)\\1/s", $blocks[3][$I], $blockparts); for ($J = 0; $J < count($blockparts[0]); $J++) { // Get nested blocks $this->getDynamicBlocks($tplFilename, $blockparts[3][$J]); // Replace block data with placeholders $blockparts[3][$J] = preg_replace("/(\{\|([a-zA-Z0-9]+)})(.*?)\\1/s", "\\1", $blockparts[3][$J]); // Save block data $this->templateBlocks[$tplFilename][$blocks[2][$I]][$blockparts[2][$J]] = $blockparts[3][$J]; } } } /** * Loads a template, runs some checks and extracts dynamic blocks. * @param string $tplFilename Template base filename. * @return bool true if the template is loaded, false if an error occurred. */ function loadTemplate(string $tplFilename): bool { // Template already loaded? if (isset($this->loadedTemplates[$tplFilename])) { return true; } // Has the path been set? if (empty($this->templateDirectory)) { echo "[TEMPLATE ENGINE] Template path not set or invalid!
"; if ($this->dieOnError) { die(); } return false; } // Is a user-defined county code set? if (!empty($this->languageCode)) { // Yes. Try to find template with the specified CC if (file_exists($this->templateDirectory . $this->templateFiles[$tplFilename] . "." . $this->languageCode . "." . $this->templateExtension)) { $filename = $this->templateDirectory . $this->templateFiles[$tplFilename] . "." . $this->languageCode . "." . $this->templateExtension; } else { // Otherwise, use template filename without CC if (file_exists($this->templateDirectory . $this->templateFiles[$tplFilename] . "." . $this->templateExtension)) { $filename = $this->templateDirectory . $this->templateFiles[$tplFilename] . "." . $this->templateExtension; } else { echo "[TEMPLATE ENGINE] Can't find template " . "'" . $tplFilename . "'!
"; if ($this->dieOnError) { die(); } return false; } } } else { // No. Use template filename without CC if (file_exists($this->templateDirectory . $this->templateFiles[$tplFilename] . "." . $this->templateExtension)) { $filename = $this->templateDirectory . $this->templateFiles[$tplFilename] . "." . $this->templateExtension; } else { echo "[TEMPLATE ENGINE] Can't find template " . "'" . $tplFilename . "'!
"; if ($this->dieOnError) { die(); } return false; } } // Load template file $contents = implode("", (@file($filename))); if (empty($contents)) { echo "[TEMPLATE ENGINE] Can't load template '" . $tplFilename . "'!
"; if ($this->dieOnError) { die(); } return false; } // Parse dynamic blocks recursively $this->getDynamicBlocks($tplFilename, $contents); // Replace all block data with placeholders $contents = preg_replace("/(\{\|([a-zA-Z0-9]+)})(.*?)\\1/s", "\\1", $contents); $this->templateData[$tplFilename] = $contents; $this->loadedTemplates[$tplFilename] = 1; return true; } /** * Parses a template and loads it if necessary. * The result is assigned or concatenated to the specified handle. * @param string $handle Result handle to store the parsed template in. * @param string $file Template file to parse. * @param bool $append true to append the parsed template to the given handle, false replaces the contents. * @param bool $delunused true to delete unused variable placeholders, false to keep them for later processing. * @return bool true if the file was parsed correctly, false if an error occurred. */ function parse(string $handle = "", string $file = "", bool $append = false, bool $delunused = false): bool { // Check if all prerequisites are met if (empty($handle) || empty($file)) { return false; } if (!isset($this->templateFiles[$file])) { return false; } if (!isset($this->loadedTemplates[$file]) && !$this->loadTemplate($file)) { return false; } $templateCopy = $this->templateData[$file]; // Reset array pointers reset($this->templateHandles); reset($this->parserVariables); // Replace blocks if (isset($this->parsedBlocks[$file])) { reset($this->parsedBlocks[$file]); foreach ($this->parsedBlocks[$file] as $varname => $value) { $templateCopy = preg_replace("/\{\|" . $varname . "}/i", $value, $templateCopy); } } // Replace variables foreach ($this->parserVariables as $varname => $value) { $templateCopy = preg_replace("/\{" . $varname . "}/i", $value, $templateCopy); } // Replace {~name} placeholders with already parsed handle of // the same name foreach ($this->templateHandles as $varname => $value) { $templateCopy = preg_replace("/\{~" . $varname . "}/i", $value, $templateCopy); } // Delete unused variables and placeholders if ($delunused) { $templateCopy = preg_replace("/\{[~|]?(\w*?)}/", "", $templateCopy); } // Assign to handle if ($append && isset($this->templateHandles[$handle])) { $this->templateHandles[$handle] .= $templateCopy; } else { $this->templateHandles[$handle] = $templateCopy; } return true; } /** * Parses multiple templates. * * $list is an associative array of the type "handle"=>"template". * Note that concatenation is not possible. Use the parse() method instead. * @param array $list Associative array with handles as key and files as values. * @return bool true if all templates were parsed correctly, false if an error occurred. */ function multiParse(array $list): bool { foreach ($list as $handle => $file) { if (!$this->parse($handle, $file)) return false; } return true; } /** * Parses a template and prints the result. * @param string $handle Result handle to store the parsed template in. * @param string $file Template base filename to parse. * @param bool $append true to append the parsed template to the given handle, false replaces the contents. * @return bool true if the template was parsed correctly, false if an error occurred. */ function printParse(string $handle = "", string $file = "", bool $append = false): bool { if ($this->parse($handle, $file, $append)) { $this->printHandle($handle); return true; } return false; } /** * Parses a block and replaces or appends the result to the block handle. * * Note: First argument is the template file name containing the blocks, * not a handle name! * * Note: This function has no effect on any template handle. * Parsed block data is inserted into the template handle in the * parse() method. * @param string $file Template base filename to parse. * @param string $block Block name to parse. * @param string $blockpart Block part to parse. * @param bool $append true appends the parsed data to the block contents, false replaces it. * @param bool $delunused true removes unused variable placeholders, false keeps them for later processing. * @return bool true if the bock was parsed successfully, false if an error occurred. */ function parseBlock(string $file = "", string $block = "", string $blockpart = "", bool $append = false, bool $delunused = false): bool { if (empty($file) || empty($block) || empty($blockpart)) return false; if (!isset($this->templateFiles[$file])) return false; if (!isset($this->loadedTemplates[$file]) && !$this->loadTemplate($file)) return false; $blockCopy = $this->templateBlocks[$file][$block][$blockpart]; // Reset array pointers reset($this->parsedBlocks); reset($this->parserVariables); // Replace blocks if (isset($this->parsedBlocks[$file])) { reset($this->parsedBlocks[$file]); foreach ($this->parsedBlocks[$file] as $varname => $value) { $blockCopy = preg_replace("/\{\|" . $varname . "}/i", $value, $blockCopy); } } // Replace variables foreach ($this->parserVariables as $varname => $value) { $blockCopy = preg_replace("/\{" . $varname . "}/i", $value, $blockCopy); } // Replace {~name} placeholders with already parsed handle of // the same name foreach ($this->templateHandles as $varname => $value) { $blockCopy = preg_replace("/\{~" . $varname . "}/i", $value, $blockCopy); } // Delete unused variables and placeholders if ($delunused) { $blockCopy = preg_replace("/\{[~|]?(\w*?)}/", "", $blockCopy); } // Assign to handle if ($append && isset($this->parsedBlocks[$file][$block])) { $this->parsedBlocks[$file][$block] .= $blockCopy; } else { $this->parsedBlocks[$file][$block] = $blockCopy; } return true; } /** * Deletes all block handles. * @return void */ function clearBlockHandles(): void { if (!empty($this->parsedBlocks)) { reset($this->parsedBlocks); foreach ($this->parsedBlocks as $ref => $val) { unset($this->parsedBlocks[$ref]); } } } /** * Deletes the specified block handle * @param string $file The template base file the block is found ion. * @param string $block The block name to delete. * @return void */ function delBlockHandle(string $file, string $block): void { if (!empty($file) && !empty($block)) { if (isset($this->parsedBlocks[$file][$block])) { unset($this->parsedBlocks[$file][$block]); } } } /** * Adds one or more templates * You can pass one associative array with $handle=>$filename pairs, * or two strings ($handle, $filename) to this function. * @param mixed $tplList A template handle or an associative array with template handles as keys and filenames as values. * @param string $tplFilename A template base filename. Only used if $tplList is a single string. * @return void */ function addTemplates(mixed $tplList, string $tplFilename = ""): void { if (is_array($tplList)) { reset($tplList); foreach ($tplList as $handle => $filename) { // Add handle to list $this->templateFiles[$handle] = $filename; // Delete loaded flag if set unset($this->loadedTemplates[$handle]); } } else { $this->templateFiles[$tplList] = $tplFilename; unset($this->loadedTemplates[$tplList]); } } /** * Deletes all template handles * @return void */ function clearHandles(): void { if (!empty($this->templateHandles)) { reset($this->templateHandles); foreach ($this->templateHandles as $ref => $val) { unset($this->templateHandles[$ref]); } } } /** * Deletes the specified template handle * @param string $handleName Template handle to delete. * @return void */ function delHandle(string $handleName = ""): void { if (!empty($handleName)) { if (isset($this->templateHandles[$handleName])) { unset($this->templateHandles[$handleName]); } } } /** * Returns the contents of the specified template handle. * @param string $handleName The handle to return the contents for. * @return bool|string false if the handle doesn't exist, or a string with the contents. */ function getHandle(string $handleName = ""): bool|string { if (empty($handleName)) return false; if (isset($this->templateHandles[$handleName])) return $this->templateHandles[$handleName]; return false; } /** * Prints a parsed template handle to the output stream. * @param string $handleName The handle to print. * @return bool false if the handle doesn't exist, true if the handle was printed. */ function printHandle(string $handleName = ""): bool { if (empty($handleName)) return false; // Remove all remaining placeholders $this->templateHandles[$handleName] = preg_replace("/\{[~|]?(\w*?)}/", "", $this->templateHandles[$handleName]); if (isset($this->templateHandles[$handleName])) { echo $this->templateHandles[$handleName]; return true; } return false; } /** * Deletes all variables set with the addVars() method. * @return void */ function clearVars(): void { if (!empty($this->parserVariables)) { reset($this->parserVariables); foreach ($this->parserVariables as $ref => $val) { unset($this->parserVariables[$ref]); } } } /** * Adds one or more variables. * You can pass one associative array with $varname=>$value pairs * or two strings ($varname, $value) to this function. * @param mixed $varList Either an associative array with variable names as keys and values as values, or a string with the variable name. * @param string $varValue the value to insert for this variable. Only used if $varList is a string with the name of the variable. * @return void */ function addVars(mixed $varList, string $varValue = ""): void { if (is_array($varList)) { reset($varList); foreach ($varList as $varname => $value) { // Replace curly braces if ($this->replaceBraces == true) { $value = preg_replace(array("/(\{)/", "/(})/"), array("{", "}"), $value); } // Add/replace variable if (!preg_match("/[^0-9a-z\-_]/i", $varname)) $this->parserVariables[$varname] = $value; } } else { // Replace curly braces if ($this->replaceBraces == true) { $varValue = preg_replace(array("/(\{)/", "/(})/"), array("{", "}"), $varValue); } // Add/replace variable if (!preg_match("/[^0-9a-z\-_]/i", $varList)) $this->parserVariables[$varList] = $varValue; } } /** * Returns a value set by the addVars() method. * @param string $varName The variable to return the value of. * @return bool|string false, if the variable didn't exist, or a string with the contents. */ function getVar(string $varName = ""): bool|string { if (empty($varName)) { return false; } if (isset($this->parserVariables[$varName])) { return $this->parserVariables[$varName]; } return false; } }