Source for file Sigma.php
Documentation is available at Sigma.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Ulf Wendel <ulf.wendel@phpdoc.de> |
// | Alexey Borzov <avb@php.net> |
// +----------------------------------------------------------------------+
// $Id: Sigma.php,v 1.12 2004/10/20 10:52:14 avb Exp $
define('SIGMA_TPL_NOT_FOUND', -2 );
define('SIGMA_BLOCK_NOT_FOUND', -3 );
define('SIGMA_BLOCK_DUPLICATE', -4 );
define('SIGMA_CACHE_ERROR', -5 );
define('SIGMA_UNKNOWN_OPTION', -6 );
define('SIGMA_PLACEHOLDER_NOT_FOUND', -10 );
define('SIGMA_PLACEHOLDER_DUPLICATE', -11 );
define('SIGMA_BLOCK_EXISTS', -12 );
define('SIGMA_INVALID_CALLBACK', -13 );
define('SIGMA_CALLBACK_SYNTAX_ERROR', -14 );
* HTML_Template_Sigma: implementation of Integrated Templates API with
* template 'compilation' added.
* The main new feature in Sigma is the template 'compilation'. Consider the
* following: when loading a template file the engine has to parse it using
* regular expressions to find all the blocks and variable placeholders. This
* is a very "expensive" operation and is definitely an overkill to do on
* every page request: templates seldom change on production websites. This is
* where the cache kicks in: it saves an internal representation of the
* template structure into a file and this file gets loaded instead of the
* source one on subsequent requests (unless the source changes, of course).
* While HTML_Template_Sigma inherits PHPLib Template's template syntax, it has
* an API which is easier to understand. When using HTML_Template_PHPLIB, you
* have to explicitly name a source and a target the block gets parsed into.
* This gives maximum flexibility but requires full knowledge of template
* structure from the programmer.
* Integrated Template on the other hands manages block nesting and parsing
* itself. The engine knows that inner1 is a child of block2, there's
* no need to tell it about this:
* + __global__ (hidden and automatically added)
* To add content to block1 you simply type:
* <code>$tpl->setCurrentBlock("block1");</code>
* and repeat this as often as needed:
* $tpl->setVariable(...);
* $tpl->parseCurrentBlock();
* To add content to block2 you would type something like:
* $tpl->setCurrentBlock("inner1");
* $tpl->setVariable(...);
* $tpl->parseCurrentBlock();
* $tpl->setVariable(...);
* $tpl->parseCurrentBlock();
* This will result in one repetition of block2 which contains two repetitions
* of inner1. inner2 will be removed if $removeEmptyBlock is set to true (which
* $tpl = new HTML_Template_Sigma( [string filerootdir], [string cacherootdir] );
* // load a template or set it with setTemplate()
* $tpl->loadTemplatefile( string filename [, boolean removeUnknownVariables, boolean removeEmptyBlocks] )
* // set "global" Variables meaning variables not beeing within a (inner) block
* $tpl->setVariable( string variablename, mixed value );
* // like with the HTML_Template_PHPLIB there's a second way to use setVariable()
* $tpl->setVariable( array ( string varname => mixed value ) );
* // Let's use any block, even a deeply nested one
* $tpl->setCurrentBlock( string blockname );
* // repeat this as often as you need it.
* $tpl->setVariable( array ( string varname => mixed value ) );
* $tpl->parseCurrentBlock();
* // get the parsed template or print it: $tpl->show()
* @author Ulf Wendel <ulf.wendel@phpdoc.de>
* @author Alexey Borzov <avb@php.net>
* @version $Revision: 1.12 $
* @package HTML_Template_Sigma
* First character of a variable placeholder ( _{_VARIABLE} ).
* @see $closingDelimiter, $blocknameRegExp, $variablenameRegExp
* Last character of a variable placeholder ( {VARIABLE_}_ )
* @see $openingDelimiter, $blocknameRegExp, $variablenameRegExp
* RegExp for matching the block names in the template.
* Per default "sm" is used as the regexp modifier, "i" is missing.
* That means a case sensitive search is done.
* @see $variablenameRegExp, $openingDelimiter, $closingDelimiter
* RegExp matching a variable placeholder in the template.
* Per default "sm" is used as the regexp modifier, "i" is missing.
* That means a case sensitive search is done.
* @see $blocknameRegExp, $openingDelimiter, $closingDelimiter
* RegExp used to find variable placeholder, filled by the constructor
* @var string Looks somewhat like @(delimiter varname delimiter)@
* @see HTML_Template_Sigma()
* RegExp used to strip unused variable placeholders
* @see $variablesRegExp, HTML_Template_Sigma()
* RegExp used to find blocks and their content, filled by the constructor
* @see HTML_Template_Sigma()
* Controls the handling of unknown variables, default is remove
* Controls the handling of empty blocks, default is remove
* Name of the current block
* Template blocks and their content
* Content of parsed blocks
var $_parsedBlocks = array ();
* Variable names that appear in the block
* @see _buildBlockVariables()
var $_blockVariables = array ();
* Inner blocks inside the block
var $_children = array ();
* List of blocks to preserve even if they are "empty"
* @see touchBlock(), $removeEmptyBlocks
var $_touchedBlocks = array ();
* List of blocks which should not be shown even if not "empty"
* @see hideBlock(), $removeEmptyBlocks
var $_hiddenBlocks = array ();
* Variables for substitution.
* Variables are kept in this array before the replacements are done.
* This allows automatic removal of empty blocks.
var $_variables = array ();
* Global variables for substitution
* These are substituted into all blocks, are not cleared on
* block parsing and do not trigger "non-empty" logic. I.e. if
* only global variables are substituted into the block, it is
* still considered "empty".
* @see setVariable(), setGlobalVariable()
var $_globalVariables = array ();
* Root directory for "source" templates
* @see HTML_Template_Sigma(), setRoot()
* Directory to store the "prepared" templates in
* @see HTML_Template_Sigma(), setCacheRoot()
* Flag indicating that the global block was parsed
* Options to control some finer aspects of Sigma's work.
* $_options['preserve_data'] If false, then substitute variables and remove empty
* placeholders in data passed through setVariable (see also bugs #20199, #21951)
* $_options['trim_on_save'] Whether to trim extra whitespace from template on cache save.
* Generally safe to have this on, unless you have <pre></pre> in templates or want to
* preserve HTML indentantion
'preserve_data' => false ,
* Function name prefix used when searching for function calls in the template
* RegExp used to grep function calls in the template (set by the constructor)
* @see _buildFunctionlist(), HTML_Template_Sigma()
* List of functions found in the template.
var $_functions = array ();
* List of callback functions specified by the user
var $_callback = array ();
* RegExp used to find file inclusion calls in the template (should have 'e' modifier)
* Files queued for inclusion
var $_triggers = array ();
* Constructor: builds some complex regular expressions and optionally
* sets the root directories.
* Make sure that you call this constructor if you derive your template
* @param string root directory for templates
* @param string directory to cache "prepared" templates in
* @see setRoot(), setCacheRoot()
// the class is inherited from PEAR to be able to use $this->setErrorHandling()
* Sets the file root for templates. The file root gets prefixed to all
* filenames passed to the object.
* @param string directory name
* @see HTML_Template_Sigma()
if (('' != $root) && ('/' != substr($root, -1 ))) {
* Sets the directory to cache "prepared" templates in, the directory should be writable for PHP.
* The "prepared" template contains an internal representation of template
* structure: essentially a serialized array of $_blocks, $_blockVariables,
* $_children and $_functions, may also contain $_triggers. This allows
* to bypass expensive calls to _buildBlockVariables() and especially
* _buildBlocks() when reading the "prepared" template instead of
* The files in this cache do not have any TTL and are regenerated when the
* source templates change.
* @param string directory name
* @see HTML_Template_Sigma(), _getCached(), _writeCache()
} elseif (('' != $root) && ('/' != substr($root, -1 ))) {
$this->_cacheRoot = $root;
* Sets the option for the template class
* @param string option name
* @param mixed option value
* @return mixed SIGMA_OK on success, error object on failure
if (isset ($this->_options[$option])) {
$this->_options[$option] = $value;
* Returns a textual error message for an error code
* @param integer error code
* @param string additional data to insert into message
* @return string error message
if (!isset ($errorMessages)) {
SIGMA_BLOCK_DUPLICATE => 'The name of a block must be unique within a template. Block \'%s\' found twice.',
if (PEAR ::isError ($code)) {
$code = $code->getCode ();
if (!isset ($errorMessages[$code])) {
return (null === $data)? $errorMessages[$code]: sprintf($errorMessages[$code], $data);
* Prints a block with all replacements done.
* @param string block name
function show($block = '__global__')
print $this->get($block);
* Returns a block with all replacements done.
* @param string block name
* @param bool whether to clear parsed block contents
* @return string block with all replacements done
function get($block = '__global__', $clear = false )
if (!isset ($this->_blocks[$block])) {
$this->parse('__global__');
// return the parsed block, removing the unknown placeholders if needed
if (!isset ($this->_parsedBlocks[$block])) {
$ret = $this->_parsedBlocks[$block];
unset ($this->_parsedBlocks[$block]);
if ($this->_options['preserve_data']) {
* Parses the given block.
* @param string block name
* @param boolean true if the function is called recursively (do not set this to true yourself!)
* @param boolean true if parsing a "hidden" block (do not set this to true yourself!)
* @see parseCurrentBlock()
function parse($block = '__global__', $flagRecursion = false , $fakeParse = false )
if (!isset ($this->_blocks[$block])) {
if ('__global__' == $block) {
if (!isset ($this->_parsedBlocks[$block])) {
$this->_parsedBlocks[$block] = '';
$outer = $this->_blocks[$block];
// block is not empty if its local var is substituted
foreach ($this->_blockVariables[$block] as $allowedvar => $v) {
if (isset ($this->_variables[$allowedvar])) {
// vital for checking "empty/nonempty" status
unset ($this->_variables[$allowedvar]);
// processing of the inner blocks
if (isset ($this->_children[$block])) {
foreach ($this->_children[$block] as $innerblock => $v) {
if (isset ($this->_hiddenBlocks[$innerblock])) {
// don't bother actually parsing this inner block; but we _have_
// to go through its local vars to prevent problems on next iteration
$this->parse($innerblock, true , true );
unset ($this->_hiddenBlocks[$innerblock]);
$this->parse($innerblock, true , $fakeParse);
// block is not empty if its inner block is not empty
if ('' != $this->_parsedBlocks[$innerblock]) {
$outer = str_replace($placeholder, $this->_parsedBlocks[$innerblock], $outer);
$this->_parsedBlocks[$innerblock] = '';
// add "global" variables to the static array
foreach ($this->_globalVariables as $allowedvar => $value) {
if (isset ($this->_blockVariables[$block][$allowedvar])) {
// if we are inside a hidden block, don't bother
if (0 != count($vars) && (!$flagRecursion || !empty ($this->_functions[$block]))) {
// check whether the block is considered "empty" and append parsed content if not
if (!$empty || ('__global__' == $block) || !$this->removeEmptyBlocks || isset ($this->_touchedBlocks[$block])) {
if (!empty ($this->_functions[$block])) {
foreach ($this->_functions[$block] as $id => $data) {
// do not waste time calling function more than once
if (!isset ($vars[$placeholder])) {
$preserveArgs = isset ($this->_callback[$data['name']]['preserveArgs']) && $this->_callback[$data['name']]['preserveArgs'];
foreach ($data['args'] as $arg) {
$args[] = (empty ($varKeys) || $preserveArgs)? $arg: str_replace($varKeys, $varValues, $arg);
if (isset ($this->_callback[$data['name']]['data'])) {
$res = isset ($args[0 ])? $args[0 ]: '';
// save the result to variable cache, it can be requested somewhere else
$vars[$placeholder] = $res;
// substitute variables only on non-recursive call, thus all
// variables from all inner blocks get substituted
if (!$flagRecursion && !empty ($varKeys)) {
$this->_parsedBlocks[$block] .= $outer;
if (isset ($this->_touchedBlocks[$block])) {
unset ($this->_touchedBlocks[$block]);
* The function can be used either like setVariable("varname", "value")
* or with one array $variables["varname"] = "value" given setVariable($variables)
* @param mixed variable name or array ('varname'=>'value')
* @param string variable value if $variable is not an array
$this->_variables = array_merge($this->_variables, $variable);
$this->_variables[$variable] = $value;
* Sets a global variable value.
* @param mixed variable name or array ('varname'=>'value')
* @param string variable value if $variable is not an array
$this->_globalVariables = array_merge($this->_globalVariables, $variable);
$this->_globalVariables[$variable] = $value;
* Sets the name of the current block: the block where variables are added
* @param string block name
* @return mixed SIGMA_OK on success, error object on failure
if (!isset ($this->_blocks[$block])) {
* Parses the current block
* @see parse(), setCurrentBlock()
* Returns the current block name
* @return string block name
* Preserves the block even if empty blocks should be removed.
* Sometimes you have blocks that should be preserved although they are
* empty (no placeholder replaced). Think of a shopping basket. If it's
* empty you have to show a message to the user. If it's filled you have
* to show the contents of the shopping basket. Now where to place the
* message that the basket is empty? It's not a good idea to place it
* in you application as customers tend to like unecessary minor text
* changes. Having another template file for an empty basket means that
* one fine day the filled and empty basket templates will have different
* So blocks that do not contain any placeholders but only messages like
* "Your shopping basked is empty" are intoduced. Now if there is no
* replacement done in such a block the block will be recognized as "empty"
* and by default ($removeEmptyBlocks = true) be stripped off. To avoid this
* you can call touchBlock()
* @param string block name
* @return mixed SIGMA_OK on success, error object on failure
* @see $removeEmptyBlocks, $_touchedBlocks
if (!isset ($this->_blocks[$block])) {
if (isset ($this->_hiddenBlocks[$block])) {
unset ($this->_hiddenBlocks[$block]);
$this->_touchedBlocks[$block] = true;
* Hides the block even if it is not "empty".
* Is somewhat an opposite to touchBlock().
* Consider a block (a 'edit' link for example) that should be visible to
* registered/"special" users only, but its visibility is triggered by
* some little 'id' field passed in a large array into setVariable(). You
* can either carefully juggle your variables to prevent the block from
* appearing (a fragile solution) or simply call hideBlock()
* @param string block name
* @return mixed SIGMA_OK on success, error object on failure
if (!isset ($this->_blocks[$block])) {
if (isset ($this->_touchedBlocks[$block])) {
unset ($this->_touchedBlocks[$block]);
$this->_hiddenBlocks[$block] = true;
* You can either load a template file from disk with LoadTemplatefile() or set the
* template manually using this function.
* @param string template content
* @param boolean remove unknown/unused variables?
* @param boolean remove empty blocks?
* @return mixed SIGMA_OK on success, error object on failure
* @see loadTemplatefile()
function setTemplate($template, $removeUnknownVariables = true , $removeEmptyBlocks = true )
$this->_resetTemplate ($removeUnknownVariables, $removeEmptyBlocks);
$list = $this->_buildBlocks ('<!-- BEGIN __global__ -->'. $template. '<!-- END __global__ -->');
if (PEAR ::isError ($list)) {
return $this->_buildBlockVariables ();
* If caching is on, then it checks whether a "prepared" template exists.
* If it does, it gets loaded instead of the original, if it does not, then
* the original gets loaded and prepared and then the prepared version is saved.
* addBlockfile() and replaceBlockfile() implement quite the same logic.
* @param boolean remove unknown/unused variables?
* @param boolean remove empty blocks?
* @return mixed SIGMA_OK on success, error object on failure
* @see setTemplate(), $removeUnknownVariables, $removeEmptyBlocks
function loadTemplateFile($filename, $removeUnknownVariables = true , $removeEmptyBlocks = true )
if ($this->_isCached ($filename)) {
$this->_resetTemplate ($removeUnknownVariables, $removeEmptyBlocks);
return $this->_getCached ($filename);
$template = $this->_getFile ($this->_sourceName ($filename));
if (PEAR ::isError ($template)) {
$this->_triggers = array ();
if (SIGMA_OK !== ($res = $this->setTemplate($template, $removeUnknownVariables, $removeEmptyBlocks))) {
return $this->_writeCache ($filename, '__global__');
* Adds a block to the template changing a variable placeholder to a block placeholder.
* This means that a new block will be integrated into the template in
* place of a variable placeholder. The variable placeholder will be
* removed and the new block will behave in the same way as if it was
* inside the original template.
* The block content must not start with <!-- BEGIN blockname --> and end with
* <!-- END blockname -->, if it does the error will be thrown.
* @param string name of the variable placeholder, the name must be unique within the template.
* @param string name of the block to be added
* @param string content of the block
* @return mixed SIGMA_OK on success, error object on failure
function addBlock($placeholder, $block, $template)
if (isset ($this->_blocks[$block])) {
$parents = $this->_findParentBlocks ($placeholder);
if (0 == count($parents)) {
} elseif (count($parents) > 1 ) {
$template = " <!-- BEGIN $block -->" . $template . " <!-- END $block -->";
$list = $this->_buildBlocks ($template);
if (PEAR ::isError ($list)) {
$this->_replacePlaceholder ($parents[0 ], $placeholder, $block);
return $this->_buildBlockVariables ($block);
* Adds a block taken from a file to the template, changing a variable placeholder
* to a block placeholder.
* @param string name of the variable placeholder
* @param string name of the block to be added
* @param string template file that contains the block
* @return mixed SIGMA_OK on success, error object on failure
if ($this->_isCached ($filename)) {
return $this->_getCached ($filename, $block, $placeholder);
$template = $this->_getFile ($this->_sourceName ($filename));
if (PEAR ::isError ($template)) {
return $this->_writeCache ($filename, $block);
* Replaces an existing block with new content.
* This function will replace a block of the template and all blocks
* contained in it and add a new block instead. This means you can
* dynamically change your template.
* Sigma analyses the way you've nested blocks and knows which block
* belongs into another block. This nesting information helps to make the
* API short and simple. Replacing blocks does not only mean that Sigma
* has to update the nesting information (relatively time consuming task)
* but you have to make sure that you do not get confused due to the
* template change yourself.
* @param string name of a block to replace
* @param string new content
* @param boolean true if the parsed contents of the block should be kept
* @see replaceBlockfile(), addBlock()
* @return mixed SIGMA_OK on success, error object on failure
function replaceBlock($block, $template, $keepContent = false )
if (!isset ($this->_blocks[$block])) {
// should not throw a error as we already checked for block existance
$this->_removeBlockData ($block, $keepContent);
$template = " <!-- BEGIN $block -->" . $template . " <!-- END $block -->";
$list = $this->_buildBlocks ($template);
if (PEAR ::isError ($list)) {
// renew the variables list
return $this->_buildBlockVariables ($block);
* Replaces an existing block with new content from a file.
* @param string name of a block to replace
* @param string template file that contains the block
* @param boolean true if the parsed contents of the block should be kept
* @return mixed SIGMA_OK on success, error object on failure
* @see replaceBlock(), addBlockfile()
if ($this->_isCached ($filename)) {
if (PEAR ::isError ($res = $this->_removeBlockData ($block, $keepContent))) {
return $this->_getCached ($filename, $block);
$template = $this->_getFile ($this->_sourceName ($filename));
if (PEAR ::isError ($template)) {
return $this->_writeCache ($filename, $block);
* Checks if the block exists in the template
* @param string block name
return isset ($this->_blocks[$block]);
* Returns the name of the (first) block that contains the specified placeholder.
* @param string Name of the placeholder you're searching
* @param string Name of the block to scan. If left out (default) all blocks are scanned.
* @return string Name of the (first) block that contains the specified placeholder.
* If the placeholder was not found an empty string is returned.
if ('' != $block && !isset ($this->_blocks[$block])) {
// if we search in the specific block, we should just check the array
return isset ($this->_blockVariables[$block][$placeholder])? $block: '';
// _findParentBlocks returns an array, we need only the first element
$parents = $this->_findParentBlocks ($placeholder);
return empty ($parents)? '': $parents[0 ];
} // end func placeholderExists
* Sets a callback function.
* Sigma templates can contain simple function calls. This means that the
* author of the template can add a special placeholder to it:
* func_h1("embedded in h1")
* Sigma will parse the template for these placeholders and will allow
* you to define a callback function for them. Callback will be called
* automatically when the block containing such function call is parse()'d.
* Please note that arguments to these template functions can contain
* variable placeholders: func_translate('Hello, {username}'), but not
* blocks or other function calls.
* This should NOT be used to add logic (except some presentation one) to
* the template. If you use a lot of such callbacks and implement business
* logic through them, then you're reinventing the wheel. Consider using
* XML/XSLT, native PHP or some other template engine.
* return '<h1>' . $arg . '</h1>';
* $tpl = new HTML_Template_Sigma( ... );
* $tpl->setCallbackFunction('h1', 'h_one');
* func_h1('H1 Headline');
* @param string Function name in the template
* @param mixed A callback: anything that can be passed to call_user_func_array()
* @param bool If true, then no variable substitution in arguments will take place before function call
* @return mixed SIGMA_OK on success, error object on failure
$this->_callback[$tplFunction] = array (
'preserveArgs' => $preserveArgs
} // end func setCallbackFunction
* Returns a list of blocks within a template.
* If $recursive is false, it returns just a 'flat' array of $parent's
* direct subblocks. If $recursive is true, it builds a tree of template
* blocks using $parent as root. Tree structure is compatible with
* PEAR::Tree's Memory_Array driver.
* @param string parent block name
* @param bool whether to return a tree of child blocks (true) or a 'flat' array (false)
* @return array a list of child blocks
function getBlockList($parent = '__global__', $recursive = false )
if (!isset ($this->_blocks[$parent])) {
return isset ($this->_children[$parent])? array_keys($this->_children[$parent]): array ();
$ret = array ('name' => $parent);
if (!empty ($this->_children[$parent])) {
$ret['children'] = array ();
foreach (array_keys($this->_children[$parent]) as $child) {
* Returns a list of placeholders within a block.
* Only 'normal' placeholders are returned, not auto-created ones.
* @param string block name
* @return array a list of placeholders
if (!isset ($this->_blocks[$block])) {
foreach ($this->_blockVariables[$block] as $var => $v) {
if ('__' != substr($var, 0 , 2 ) || '__' != substr($var, -2 )) {
* Global variables are not affected. The method is useful when you add
* a lot of variables via setVariable() and are not sure whether all of
* them appear in the block you parse(). If you clear the variables after
* parse(), you don't risk them suddenly showing up in other blocks.
$this->_variables = array ();
//------------------------------------------------------------
// Private methods follow
//------------------------------------------------------------
* Reads the file and returns its content
* @return string file content (or error object)
function _getFile ($filename)
if (!($fh = @fopen($filename, 'r'))) {
* Recursively builds a list of all variables within a block.
* Also calls _buildFunctionlist() for each block it visits
* @param string block name
* @see _buildFunctionlist()
function _buildBlockVariables ($block = '__global__')
$this->_blockVariables[$block] = array ();
$this->_functions[$block] = array ();
foreach ($regs as $match) {
$this->_blockVariables[$block][$match[1 ]] = true;
$this->_blocks[$block] = str_replace($match[0 ], '{__function_' . $funcId . '__}', $this->_blocks[$block]);
$this->_blockVariables[$block]['__function_' . $funcId . '__'] = true;
$this->_functions[$block][$funcId] = $funcData;
if (SIGMA_OK != ($res = $this->_buildFunctionlist ($block))) {
if (isset ($this->_children[$block]) && is_array($this->_children[$block])) {
foreach ($this->_children[$block] as $child => $v) {
if (SIGMA_OK != ($res = $this->_buildBlockVariables ($child))) {
* Recusively builds a list of all blocks within the template.
* @param string template to be scanned
* @return mixed array of block names on success or error object on failure
function _buildBlocks ($string)
foreach ($regs as $k => $match) {
$blockcontent = $match[2 ];
if (isset ($this->_blocks[$blockname]) || isset ($blocks[$blockname])) {
$this->_blocks[$blockname] = $blockcontent;
$blocks[$blockname] = true;
$inner = $this->_buildBlocks ($blockcontent);
if (PEAR ::isError ($inner)) {
foreach ($inner as $name => $v) {
$pattern = sprintf('@<!--\s+BEGIN\s+%s\s+-->(.*)<!--\s+END\s+%s\s+-->@sm', $name, $name);
$this->_blocks[$blockname] = preg_replace($pattern, $replacement, $this->_blocks[$blockname]);
$this->_children[$blockname][$name] = true;
* Resets the object's properties, used before processing a new template
* @param boolean remove unknown/unused variables?
* @param boolean remove empty blocks?
* @see setTemplate(), loadTemplateFile()
function _resetTemplate ($removeUnknownVariables = true , $removeEmptyBlocks = true )
$this->_variables = array ();
$this->_blocks = array ();
$this->_children = array ();
$this->_parsedBlocks = array ();
$this->_touchedBlocks = array ();
$this->_functions = array ();
* Checks whether we have a "prepared" template cached.
* If we do not do caching, always returns false
* @param string source filename
* @see loadTemplatefile(), addBlockfile(), replaceBlockfile()
function _isCached ($filename)
if (null === $this->_cacheRoot) {
$cachedName = $this->_cachedName ($filename);
$sourceName = $this->_sourceName ($filename);
// if $sourceName does not exist, error will be thrown later
if ((false !== $sourceTime) && @file_exists($cachedName) && (filemtime($cachedName) > $sourceTime)) {
* Loads a "prepared" template file
* @param string block name
* @param string variable placeholder to replace by a block
* @return mixed SIGMA_OK on success, error object on failure
* @see loadTemplatefile(), addBlockfile(), replaceBlockfile()
function _getCached ($filename, $block = '__global__', $placeholder = '')
// the same checks are done in addBlock()
if (!empty ($placeholder)) {
if (isset ($this->_blocks[$block])) {
$parents = $this->_findParentBlocks ($placeholder);
if (0 == count($parents)) {
} elseif (count($parents) > 1 ) {
$content = $this->_getFile ($this->_cachedName ($filename));
if (PEAR ::isError ($content)) {
if ('__global__' != $block) {
$this->_blocks[$block] = $cache['blocks']['__global__'];
$this->_blockVariables[$block] = $cache['variables']['__global__'];
$this->_children[$block] = $cache['children']['__global__'];
$this->_functions[$block] = $cache['functions']['__global__'];
unset ($cache['blocks']['__global__'], $cache['variables']['__global__'], $cache['children']['__global__'], $cache['functions']['__global__']);
$this->_blocks = array_merge($this->_blocks, $cache['blocks']);
$this->_blockVariables = array_merge($this->_blockVariables, $cache['variables']);
$this->_children = array_merge($this->_children, $cache['children']);
$this->_functions = array_merge($this->_functions, $cache['functions']);
// the same thing gets done in addBlockfile()
if (!empty ($placeholder)) {
$this->_replacePlaceholder ($parents[0 ], $placeholder, $block);
// pull the triggers, if any
if (isset ($cache['triggers'])) {
return $this->_pullTriggers ($cache['triggers']);
* Returns a full name of a "prepared" template file
* @param string source filename, relative to root directory
* @return string filename
function _cachedName ($filename)
if ('/' == $filename{0 } && '/' == substr($this->_cacheRoot, -1 )) {
$filename = substr($filename, 1 );
return $this->_cacheRoot. $filename. '.it';
* Returns a full name of a "source" template file
* @param string source filename, relative to root directory
function _sourceName ($filename)
$filename = substr($filename, 1 );
* Writes a prepared template file.
* Even if NO caching is going on, this method has a side effect: it calls
* the _pullTriggers() method and thus loads all files added via <!-- INCLUDE -->
* @param string source filename, relative to root directory
* @param string name of the block to save into file
* @return mixed SIGMA_OK on success, error object on failure
function _writeCache ($filename, $block)
// do not save anything if no cache dir, but do pull triggers
if (null !== $this->_cacheRoot) {
$cachedName = $this->_cachedName ($filename);
$this->_buildCache ($cache, $block);
if ('__global__' != $block) {
$cache[$k]['__global__'] = $cache[$k][$block];
unset ($cache[$k][$block]);
if (isset ($this->_triggers[$block])) {
$cache['triggers'] = $this->_triggers[$block];
if (!($fh = @fopen($cachedName, 'w'))) {
if (isset ($this->_triggers[$block])) {
if (SIGMA_OK !== ($res = $this->_pullTriggers ($this->_triggers[$block]))) {
unset ($this->_triggers[$block]);
* Builds an array of template data to be saved in prepared template file
* @param array template data
* @param string block to add to the array
function _buildCache (&$cache, $block)
if (!$this->_options['trim_on_save']) {
$cache['blocks'][$block] = $this->_blocks[$block];
array ('/^\\s+/m', '/\\s+$/m', '/(\\r?\\n)+/'),
$cache['variables'][$block] = $this->_blockVariables[$block];
$cache['functions'][$block] = isset ($this->_functions[$block])? $this->_functions[$block]: array ();
if (!isset ($this->_children[$block])) {
$cache['children'][$block] = array ();
$cache['children'][$block] = $this->_children[$block];
foreach (array_keys($this->_children[$block]) as $child) {
$this->_buildCache ($cache, $child);
* Recursively removes all data belonging to a block
* @param string block name
* @param boolean true if the parsed contents of the block should be kept
* @return mixed SIGMA_OK on success, error object on failure
* @see replaceBlock(), replaceBlockfile()
function _removeBlockData ($block, $keepContent = false )
if (!isset ($this->_blocks[$block])) {
if (!empty ($this->_children[$block])) {
foreach (array_keys($this->_children[$block]) as $child) {
$this->_removeBlockData ($child, false );
unset ($this->_children[$block]);
unset ($this->_blocks[$block]);
unset ($this->_blockVariables[$block]);
unset ($this->_hiddenBlocks[$block]);
unset ($this->_touchedBlocks[$block]);
unset ($this->_functions[$block]);
unset ($this->_parsedBlocks[$block]);
* Returns the names of the blocks where the variable placeholder appears
* @param string variable name
* @return array block names
* @see addBlock(), addBlockfile(), placeholderExists()
function _findParentBlocks ($variable)
foreach ($this->_blockVariables as $blockname => $varnames) {
if (!empty ($varnames[$variable])) {
* Replaces a variable placeholder by a block placeholder.
* Of course, it also updates the necessary arrays
* @param string name of the block containing the placeholder
* @param string variable name
* @param string block name
function _replacePlaceholder ($parent, $placeholder, $block)
$this->_children[$parent][$block] = true;
$this->_blockVariables[$parent]['__'. $block. '__'] = true;
$this->_blocks[$parent] );
unset ($this->_blockVariables[$parent][$placeholder]);
* Generates a placeholder to replace an <!-- INCLUDE filename --> statement
* @param string current block name
* @return string a placeholder
function _makeTrigger ($filename, $block)
$this->_triggers[$block][$name] = $filename;
* Replaces the "trigger" placeholders by the matching file contents.
* @see _makeTrigger(), addBlockfile()
* @param array array ('trigger placeholder' => 'filename')
* @return mixed SIGMA_OK on success, error object on failure
function _pullTriggers ($triggers)
foreach ($triggers as $placeholder => $filename) {
// we actually do not need the resultant block...
$parents = $this->_findParentBlocks ('__' . $placeholder . '__');
// merge current block's children and variables with the parent's ones
if (isset ($this->_children[$placeholder])) {
$this->_children[$parents[0 ]] = array_merge($this->_children[$parents[0 ]], $this->_children[$placeholder]);
$this->_blockVariables[$parents[0 ]] = array_merge($this->_blockVariables[$parents[0 ]], $this->_blockVariables[$placeholder]);
if (isset ($this->_functions[$placeholder])) {
$this->_functions[$parents[0 ]] = array_merge($this->_functions[$parents[0 ]], $this->_functions[$placeholder]);
// substitute the block's contents into parent's
$this->_blocks[$placeholder],
$this->_blocks[$parents[0 ]]
// remove the stuff that is no more needed
unset ($this->_blocks[$placeholder], $this->_blockVariables[$placeholder], $this->_children[$placeholder], $this->_functions[$placeholder]);
unset ($this->_children[$parents[0 ]][$placeholder], $this->_blockVariables[$parents[0 ]]['__' . $placeholder . '__']);
* Builds a list of functions in a block.
* @param string Block name
* @see _buildBlockVariables()
function _buildFunctionlist ($block)
$template = $this->_blocks[$block];
$this->_blocks[$block] = '';
$this->_blocks[$block] .= substr($template, 0 , strpos($template, $regs[0 ]));
for ($i = 0 , $len = strlen($template); $i < $len; $i++ ) {
} elseif (',' == $char) {
$error = 'Unexpected \',\'';
} elseif ('\'' == $char || '"' == $char) {
if (',' == $char || ')' == $char) {
$error = 'Unexpected \'' . $char . '\'';
} elseif ('\'' == $char || '"' == $char) {
$funcData['args'][] = rtrim($arg);
} elseif (',' == $char) {
$funcData['args'][] = rtrim($arg);
} elseif ('\'' == $char || '"' == $char) {
} elseif ($quote == $char) {
$funcData['args'][] = $arg;
} elseif (',' == $char) {
$funcData['args'][] = $arg;
$error = 'Unexpected \'' . $char . '\' (expected: \')\' or \',\')';
$template = substr($template, $i);
$this->_blocks[$block] .= '{__function_' . $funcId . '__}';
$this->_blockVariables[$block]['__function_' . $funcId . '__'] = true;
$this->_functions[$block][$funcId] = $funcData;
$this->_blocks[$block] .= $template;
} // end func _buildFunctionlist
* Replaces an opening delimiter by a special string.
* Used to implement $_options['preserve_data'] logic
function _preserveOpeningDelimiter ($str)
* Quotes the string so that it can be used in Javascript string constants
function _jsEscape ($value)
return strtr($value, array (
"\r" => '\r', "'" => "\\'", "\n" => '\n',
'"' => '\"', "\t" => '\t', '\\' => '\\\\'
Documentation generated on Mon, 11 Mar 2019 13:57:22 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|