thw-theorie-web/class.template.inc.php
Kai Blaschke 7b28fea35c
Code-Modernisierungen für PHP-8-Kompatibilität.
Zusätzlich Charsets, Linebreaks und Kommentar-Blöcke korrigiert.
2023-01-24 15:12:37 +01:00

662 lines
22 KiB
PHP

<?php
/*
* Copyright (C) 2001 Kai Blaschke <webmaster@thw-theorie.de>
*
* 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 "
. "'<b>" . $path . "</b>' is invalid!<br />";
$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!<br />";
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 . "'!<br />";
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 . "'!<br />";
if ($this->dieOnError) {
die();
}
return false;
}
}
// Load template file
$contents = implode("", (@file($filename)));
if (empty($contents)) {
echo "[TEMPLATE ENGINE] Can't load template '" . $tplFilename . "'!<br />";
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("&#123;", "&#125;"),
$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("&#123;", "&#125;"),
$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;
}
}