HTML_Template_Sigma
[ class tree: HTML_Template_Sigma ] [ index: HTML_Template_Sigma ] [ all elements ]

Source for file Sigma.php

Documentation is available at Sigma.php

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Ulf Wendel <ulf.wendel@phpdoc.de>                           |
  17. // |          Alexey Borzov <avb@php.net>                                 |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: Sigma.php,v 1.16 2006/05/31 07:56:07 avb Exp $
  21. //
  22.  
  23. require_once 'PEAR.php';
  24.  
  25. define('SIGMA_OK',                         1);
  26. define('SIGMA_ERROR',                     -1);
  27. define('SIGMA_TPL_NOT_FOUND',             -2);
  28. define('SIGMA_BLOCK_NOT_FOUND',           -3);
  29. define('SIGMA_BLOCK_DUPLICATE',           -4);
  30. define('SIGMA_CACHE_ERROR',               -5);
  31. define('SIGMA_UNKNOWN_OPTION',            -6);
  32. define('SIGMA_PLACEHOLDER_NOT_FOUND',     -10);
  33. define('SIGMA_PLACEHOLDER_DUPLICATE',     -11);
  34. define('SIGMA_BLOCK_EXISTS',              -12);
  35. define('SIGMA_INVALID_CALLBACK',          -13);
  36. define('SIGMA_CALLBACK_SYNTAX_ERROR',     -14);
  37.  
  38. /**
  39. * HTML_Template_Sigma: implementation of Integrated Templates API with
  40. * template 'compilation' added.
  41. *
  42. * The main new feature in Sigma is the template 'compilation'. Consider the
  43. * following: when loading a template file the engine has to parse it using
  44. * regular expressions to find all the blocks and variable placeholders. This
  45. * is a very "expensive" operation and is definitely an overkill to do on
  46. * every page request: templates seldom change on production websites. This is
  47. * where the cache kicks in: it saves an internal representation of the
  48. * template structure into a file and this file gets loaded instead of the
  49. * source one on subsequent requests (unless the source changes, of course).
  50. *
  51. * While HTML_Template_Sigma inherits PHPLib Template's template syntax, it has
  52. * an API which is easier to understand. When using HTML_Template_PHPLIB, you
  53. * have to explicitly name a source and a target the block gets parsed into.
  54. * This gives maximum flexibility but requires full knowledge of template
  55. * structure from the programmer.
  56. *
  57. * Integrated Template on the other hands manages block nesting and parsing
  58. * itself. The engine knows that inner1 is a child of block2, there's
  59. * no need to tell it about this:
  60. *
  61. * + __global__ (hidden and automatically added)
  62. *     + block1
  63. *     + block2
  64. *         + inner1
  65. *         + inner2
  66. *
  67. * To add content to block1 you simply type:
  68. * <code>$tpl->setCurrentBlock("block1");</code>
  69. * and repeat this as often as needed:
  70. * <code>
  71. *   $tpl->setVariable(...);
  72. *   $tpl->parseCurrentBlock();
  73. * </code>
  74. *
  75. * To add content to block2 you would type something like:
  76. * <code>
  77. * $tpl->setCurrentBlock("inner1");
  78. * $tpl->setVariable(...);
  79. * $tpl->parseCurrentBlock();
  80. *
  81. * $tpl->setVariable(...);
  82. * $tpl->parseCurrentBlock();
  83. *
  84. * $tpl->parse("block2");
  85. * </code>
  86. *
  87. * This will result in one repetition of block2 which contains two repetitions
  88. * of inner1. inner2 will be removed if $removeEmptyBlock is set to true (which
  89. * is the default).
  90. *
  91. * Usage:
  92. * <code>
  93. * $tpl = new HTML_Template_Sigma( [string filerootdir], [string cacherootdir] );
  94. *
  95. * // load a template or set it with setTemplate()
  96. * $tpl->loadTemplatefile( string filename [, boolean removeUnknownVariables, boolean removeEmptyBlocks] )
  97. *
  98. * // set "global" Variables meaning variables not beeing within a (inner) block
  99. * $tpl->setVariable( string variablename, mixed value );
  100. *
  101. * // like with the HTML_Template_PHPLIB there's a second way to use setVariable()
  102. * $tpl->setVariable( array ( string varname => mixed value ) );
  103. *
  104. * // Let's use any block, even a deeply nested one
  105. * $tpl->setCurrentBlock( string blockname );
  106. *
  107. * // repeat this as often as you need it.
  108. * $tpl->setVariable( array ( string varname => mixed value ) );
  109. * $tpl->parseCurrentBlock();
  110. *
  111. * // get the parsed template or print it: $tpl->show()
  112. * $html = $tpl->get();
  113. * </code>
  114. *
  115. @author   Ulf Wendel <ulf.wendel@phpdoc.de>
  116. @author   Alexey Borzov <avb@php.net>
  117. @version  $Revision: 1.16 $
  118. @access   public
  119. @package  HTML_Template_Sigma
  120. */
  121. class HTML_Template_Sigma extends PEAR
  122. {
  123.    /**
  124.     * First character of a variable placeholder ( _{_VARIABLE} ).
  125.     * @var      string 
  126.     * @access   public
  127.     * @see      $closingDelimiter, $blocknameRegExp, $variablenameRegExp
  128.     */
  129.     var $openingDelimiter = '{';
  130.  
  131.    /**
  132.     * Last character of a variable placeholder ( {VARIABLE_}_ )
  133.     * @var      string 
  134.     * @access   public
  135.     * @see      $openingDelimiter, $blocknameRegExp, $variablenameRegExp
  136.     */
  137.     var $closingDelimiter = '}';
  138.  
  139.    /**
  140.     * RegExp for matching the block names in the template.
  141.     * Per default "sm" is used as the regexp modifier, "i" is missing.
  142.     * That means a case sensitive search is done.
  143.     * @var      string 
  144.     * @access   public
  145.     * @see      $variablenameRegExp, $openingDelimiter, $closingDelimiter
  146.     */
  147.     var $blocknameRegExp = '[0-9A-Za-z_-]+';
  148.  
  149.    /**
  150.     * RegExp matching a variable placeholder in the template.
  151.     * Per default "sm" is used as the regexp modifier, "i" is missing.
  152.     * That means a case sensitive search is done.
  153.     * @var      string 
  154.     * @access   public
  155.     * @see      $blocknameRegExp, $openingDelimiter, $closingDelimiter
  156.     */
  157.     var $variablenameRegExp = '[0-9A-Za-z._-]+';
  158.  
  159.    /**
  160.     * RegExp used to find variable placeholder, filled by the constructor
  161.     * @var      string    Looks somewhat like @(delimiter varname delimiter)@
  162.     * @see      HTML_Template_Sigma()
  163.     */
  164.     var $variablesRegExp = '';
  165.  
  166.    /**
  167.     * RegExp used to strip unused variable placeholders
  168.     * @see      $variablesRegExp, HTML_Template_Sigma()
  169.     */
  170.     var $removeVariablesRegExp = '';
  171.  
  172.    /**
  173.     * RegExp used to find blocks and their content, filled by the constructor
  174.     * @var      string 
  175.     * @see      HTML_Template_Sigma()
  176.     */
  177.     var $blockRegExp = '';
  178.  
  179.    /**
  180.     * Controls the handling of unknown variables, default is remove
  181.     * @var      boolean 
  182.     * @access   public
  183.     */
  184.     var $removeUnknownVariables = true;
  185.  
  186.    /**
  187.     * Controls the handling of empty blocks, default is remove
  188.     * @var      boolean 
  189.     * @access   public
  190.     */
  191.     var $removeEmptyBlocks = true;
  192.  
  193.    /**
  194.     * Name of the current block
  195.     * @var      string 
  196.     */
  197.     var $currentBlock = '__global__';
  198.  
  199.    /**
  200.     * Template blocks and their content
  201.     * @var      array 
  202.     * @see      _buildBlocks()
  203.     * @access   private
  204.     */
  205.     var $_blocks = array();
  206.  
  207.    /**
  208.     * Content of parsed blocks
  209.     * @var      array 
  210.     * @see      get(), parse()
  211.     * @access   private
  212.     */
  213.     var $_parsedBlocks = array();
  214.  
  215.    /**
  216.     * Variable names that appear in the block
  217.     * @var      array 
  218.     * @see      _buildBlockVariables()
  219.     * @access   private
  220.     */
  221.     var $_blockVariables = array();
  222.  
  223.    /**
  224.     * Inner blocks inside the block
  225.     * @var      array 
  226.     * @see      _buildBlocks()
  227.     * @access   private
  228.     */
  229.     var $_children = array();
  230.  
  231.    /**
  232.     * List of blocks to preserve even if they are "empty"
  233.     * @var      array 
  234.     * @see      touchBlock(), $removeEmptyBlocks
  235.     * @access   private
  236.     */
  237.     var $_touchedBlocks = array();
  238.  
  239.    /**
  240.     * List of blocks which should not be shown even if not "empty"
  241.     * @var      array 
  242.     * @see      hideBlock(), $removeEmptyBlocks
  243.     * @access   private
  244.     */
  245.     var $_hiddenBlocks = array();
  246.  
  247.    /**
  248.     * Variables for substitution.
  249.     *
  250.     * Variables are kept in this array before the replacements are done.
  251.     * This allows automatic removal of empty blocks.
  252.     *
  253.     * @var      array 
  254.     * @see      setVariable()
  255.     * @access   private
  256.     */
  257.     var $_variables = array();
  258.  
  259.    /**
  260.     * Global variables for substitution
  261.     *
  262.     * These are substituted into all blocks, are not cleared on
  263.     * block parsing and do not trigger "non-empty" logic. I.e. if
  264.     * only global variables are substituted into the block, it is
  265.     * still considered "empty".
  266.     *
  267.     * @var      array 
  268.     * @see      setVariable(), setGlobalVariable()
  269.     * @access   private
  270.     */
  271.     var $_globalVariables = array();
  272.  
  273.    /**
  274.     * Root directory for "source" templates
  275.     * @var    string 
  276.     * @see    HTML_Template_Sigma(), setRoot()
  277.     */
  278.     var $fileRoot = '';
  279.  
  280.    /**
  281.     * Directory to store the "prepared" templates in
  282.     * @var      string 
  283.     * @see      HTML_Template_Sigma(), setCacheRoot()
  284.     * @access   private
  285.     */
  286.     var $_cacheRoot = null;
  287.  
  288.    /**
  289.     * Flag indicating that the global block was parsed
  290.     * @var    boolean 
  291.     */
  292.     var $flagGlobalParsed = false;
  293.  
  294.    /**
  295.     * Options to control some finer aspects of Sigma's work.
  296.     *
  297.     * $_options['preserve_data'] If false, then substitute variables and remove empty
  298.     * placeholders in data passed through setVariable (see also bugs #20199, #21951)
  299.     * $_options['trim_on_save'] Whether to trim extra whitespace from template on cache save.
  300.     * Generally safe to have this on, unless you have <pre></pre> in templates or want to
  301.     * preserve HTML indentantion
  302.     */
  303.     var $_options = array(
  304.         'preserve_data' => false,
  305.         'trim_on_save'  => true
  306.     );
  307.  
  308.    /**
  309.     * Function name prefix used when searching for function calls in the template
  310.     * @var    string 
  311.     */
  312.     var $functionPrefix = 'func_';
  313.  
  314.    /**
  315.     * Function name RegExp
  316.     * @var    string 
  317.     */
  318.     var $functionnameRegExp = '[_a-zA-Z]+[A-Za-z_0-9]*';
  319.  
  320.    /**
  321.     * RegExp used to grep function calls in the template (set by the constructor)
  322.     * @var    string 
  323.     * @see    _buildFunctionlist(), HTML_Template_Sigma()
  324.     */
  325.     var $functionRegExp = '';
  326.  
  327.    /**
  328.     * List of functions found in the template.
  329.     * @var    array 
  330.     * @access private
  331.     */
  332.     var $_functions = array();
  333.  
  334.    /**
  335.     * List of callback functions specified by the user
  336.     * @var    array 
  337.     * @access private
  338.     */
  339.     var $_callback = array();
  340.  
  341.    /**
  342.     * RegExp used to find file inclusion calls in the template (should have 'e' modifier)
  343.     * @var  string 
  344.     */
  345.     var $includeRegExp = '#<!--\s+INCLUDE\s+(\S+)\s+-->#ime';
  346.  
  347.    /**
  348.     * Files queued for inclusion
  349.     * @var    array 
  350.     * @access private
  351.     */
  352.     var $_triggers = array();
  353.  
  354.  
  355.    /**
  356.     * Constructor: builds some complex regular expressions and optionally
  357.     * sets the root directories.
  358.     *
  359.     * Make sure that you call this constructor if you derive your template
  360.     * class from this one.
  361.     *
  362.     * @param string  root directory for templates
  363.     * @param string  directory to cache "prepared" templates in
  364.     * @see   setRoot(), setCacheRoot()
  365.     */
  366.     function HTML_Template_Sigma($root ''$cacheRoot '')
  367.     {
  368.         // the class is inherited from PEAR to be able to use $this->setErrorHandling()
  369.         $this->PEAR();
  370.         $this->variablesRegExp       = '@' $this->openingDelimiter . '(' $this->variablenameRegExp . ')' .
  371.                                        '(:(' $this->functionnameRegExp . '))?' $this->closingDelimiter . '@sm';
  372.         $this->removeVariablesRegExp = '@'.$this->openingDelimiter.'\s*('.$this->variablenameRegExp.')\s*'.$this->closingDelimiter.'@sm';
  373.         $this->blockRegExp           = '@<!--\s+BEGIN\s+('.$this->blocknameRegExp.')\s+-->(.*)<!--\s+END\s+\1\s+-->@sm';
  374.         $this->functionRegExp        = '@' $this->functionPrefix . '(' $this->functionnameRegExp . ')\s*\(@sm';
  375.         $this->setRoot($root);
  376.         $this->setCacheRoot($cacheRoot);
  377.  
  378.         $this->setCallbackFunction('h''htmlspecialchars');
  379.         $this->setCallbackFunction('u''urlencode');
  380.         $this->setCallbackFunction('j'array(&$this'_jsEscape'));
  381.     }
  382.  
  383.  
  384.    /**
  385.     * Sets the file root for templates. The file root gets prefixed to all
  386.     * filenames passed to the object.
  387.     *
  388.     * @param    string  directory name
  389.     * @see      HTML_Template_Sigma()
  390.     * @access   public
  391.     */
  392.     function setRoot($root)
  393.     {
  394.         if (('' != $root&& (DIRECTORY_SEPARATOR != substr($root-1))) {
  395.             $root .= DIRECTORY_SEPARATOR;
  396.         }
  397.         $this->fileRoot = $root;
  398.     }
  399.  
  400.  
  401.    /**
  402.     * Sets the directory to cache "prepared" templates in, the directory should be writable for PHP.
  403.     *
  404.     * The "prepared" template contains an internal representation of template
  405.     * structure: essentially a serialized array of $_blocks, $_blockVariables,
  406.     * $_children and $_functions, may also contain $_triggers. This allows
  407.     * to bypass expensive calls to _buildBlockVariables() and especially
  408.     * _buildBlocks() when reading the "prepared" template instead of
  409.     * the "source" one.
  410.     *
  411.     * The files in this cache do not have any TTL and are regenerated when the
  412.     * source templates change.
  413.     *
  414.     * @param    string  directory name
  415.     * @see      HTML_Template_Sigma(), _getCached(), _writeCache()
  416.     * @access   public
  417.     */
  418.     function setCacheRoot($root)
  419.     {
  420.         if (empty($root)) {
  421.             $root = null;
  422.         elseif (DIRECTORY_SEPARATOR != substr($root-1)) {
  423.             $root .= DIRECTORY_SEPARATOR;
  424.         }
  425.         $this->_cacheRoot $root;
  426.     }
  427.  
  428.  
  429.    /**
  430.     * Sets the option for the template class
  431.     *
  432.     * @access public
  433.     * @param  string  option name
  434.     * @param  mixed   option value
  435.     * @return mixed   SIGMA_OK on success, error object on failure
  436.     */
  437.     function setOption($option$value)
  438.     {
  439.         if (isset($this->_options[$option])) {
  440.             $this->_options[$option$value;
  441.             return SIGMA_OK;
  442.         }
  443.         return $this->raiseError($this->errorMessage(SIGMA_UNKNOWN_OPTION$option)SIGMA_UNKNOWN_OPTION);
  444.     }
  445.  
  446.  
  447.    /**
  448.     * Returns a textual error message for an error code
  449.     *
  450.     * @access public
  451.     * @param  integer  error code
  452.     * @param  string   additional data to insert into message
  453.     * @return string   error message
  454.     */
  455.     function errorMessage($code$data = null)
  456.     {
  457.         static $errorMessages;
  458.         if (!isset($errorMessages)) {
  459.             $errorMessages = array(
  460.                 SIGMA_ERROR                 => 'unknown error',
  461.                 SIGMA_OK                    => '',
  462.                 SIGMA_TPL_NOT_FOUND         => 'Cannot read the template file \'%s\'',
  463.                 SIGMA_BLOCK_NOT_FOUND       => 'Cannot find block \'%s\'',
  464.                 SIGMA_BLOCK_DUPLICATE       => 'The name of a block must be unique within a template. Block \'%s\' found twice.',
  465.                 SIGMA_CACHE_ERROR           => 'Cannot save template file \'%s\'',
  466.                 SIGMA_UNKNOWN_OPTION        => 'Unknown option \'%s\'',
  467.                 SIGMA_PLACEHOLDER_NOT_FOUND => 'Variable placeholder \'%s\' not found',
  468.                 SIGMA_PLACEHOLDER_DUPLICATE => 'Placeholder \'%s\' should be unique, found in multiple blocks',
  469.                 SIGMA_BLOCK_EXISTS          => 'Block \'%s\' already exists',
  470.                 SIGMA_INVALID_CALLBACK      => 'Callback does not exist',
  471.                 SIGMA_CALLBACK_SYNTAX_ERROR => 'Cannot parse template function: %s'
  472.             );
  473.         }
  474.  
  475.         if (PEAR::isError($code)) {
  476.             $code $code->getCode();
  477.         }
  478.         if (!isset($errorMessages[$code])) {
  479.             return $errorMessages[SIGMA_ERROR];
  480.         else {
  481.             return (null === $data)$errorMessages[$code]sprintf($errorMessages[$code]$data);
  482.         }
  483.     }
  484.  
  485.  
  486.    /**
  487.     * Prints a block with all replacements done.
  488.     *
  489.     * @access  public
  490.     * @param   string  block name
  491.     * @see     get()
  492.     */
  493.     function show($block '__global__')
  494.     {
  495.         print $this->get($block);
  496.     }
  497.  
  498.  
  499.    /**
  500.     * Returns a block with all replacements done.
  501.     *
  502.     * @param    string     block name
  503.     * @param    bool       whether to clear parsed block contents
  504.     * @return   string     block with all replacements done
  505.     * @throws   PEAR_Error
  506.     * @access   public
  507.     * @see      show()
  508.     */
  509.     function get($block '__global__'$clear = false)
  510.     {
  511.         if (!isset($this->_blocks[$block])) {
  512.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  513.         }
  514.         if ('__global__' == $block && !$this->flagGlobalParsed{
  515.             $this->parse('__global__');
  516.         }
  517.         // return the parsed block, removing the unknown placeholders if needed
  518.         if (!isset($this->_parsedBlocks[$block])) {
  519.             return '';
  520.  
  521.         else {
  522.             $ret $this->_parsedBlocks[$block];
  523.             if ($clear{
  524.                 unset($this->_parsedBlocks[$block]);
  525.             }
  526.             if ($this->removeUnknownVariables{
  527.                 $ret preg_replace($this->removeVariablesRegExp''$ret);
  528.             }
  529.             if ($this->_options['preserve_data']{
  530.                 $ret str_replace($this->openingDelimiter . '%preserved%' $this->closingDelimiter$this->openingDelimiter$ret);
  531.             }
  532.             return $ret;
  533.         }
  534.     }
  535.  
  536.  
  537.    /**
  538.     * Parses the given block.
  539.     *
  540.     * @param    string    block name
  541.     * @param    boolean   true if the function is called recursively (do not set this to true yourself!)
  542.     * @param    boolean   true if parsing a "hidden" block (do not set this to true yourself!)
  543.     * @access   public
  544.     * @see      parseCurrentBlock()
  545.     * @throws   PEAR_Error
  546.     */
  547.     function parse($block '__global__'$flagRecursion = false$fakeParse = false)
  548.     {
  549.         static $vars;
  550.  
  551.         if (!isset($this->_blocks[$block])) {
  552.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  553.         }
  554.         if ('__global__' == $block{
  555.             $this->flagGlobalParsed = true;
  556.         }
  557.         if (!isset($this->_parsedBlocks[$block])) {
  558.             $this->_parsedBlocks[$block'';
  559.         }
  560.         $outer $this->_blocks[$block];
  561.  
  562.         if (!$flagRecursion{
  563.             $vars = array();
  564.         }
  565.         // block is not empty if its local var is substituted
  566.         $empty = true;
  567.         foreach ($this->_blockVariables[$blockas $allowedvar => $v{
  568.             if (isset($this->_variables[$allowedvar])) {
  569.                 $vars[$this->openingDelimiter . $allowedvar $this->closingDelimiter$this->_variables[$allowedvar];
  570.                 $empty = false;
  571.                 // vital for checking "empty/nonempty" status
  572.                 unset($this->_variables[$allowedvar]);
  573.             }
  574.         }
  575.  
  576.         // processing of the inner blocks
  577.         if (isset($this->_children[$block])) {
  578.             foreach ($this->_children[$blockas $innerblock => $v{
  579.                 $placeholder $this->openingDelimiter.'__'.$innerblock.'__'.$this->closingDelimiter;
  580.  
  581.                 if (isset($this->_hiddenBlocks[$innerblock])) {
  582.                     // don't bother actually parsing this inner block; but we _have_
  583.                     // to go through its local vars to prevent problems on next iteration
  584.                     $this->parse($innerblocktruetrue);
  585.                     unset($this->_hiddenBlocks[$innerblock]);
  586.                     $outer str_replace($placeholder''$outer);
  587.  
  588.                 else {
  589.                     $this->parse($innerblocktrue$fakeParse);
  590.                     // block is not empty if its inner block is not empty
  591.                     if ('' != $this->_parsedBlocks[$innerblock]{
  592.                         $empty = false;
  593.                     }
  594.  
  595.                     $outer str_replace($placeholder$this->_parsedBlocks[$innerblock]$outer);
  596.                     $this->_parsedBlocks[$innerblock'';
  597.                 }
  598.             }
  599.         }
  600.  
  601.         // add "global" variables to the static array
  602.         foreach ($this->_globalVariables as $allowedvar => $value{
  603.             if (isset($this->_blockVariables[$block][$allowedvar])) {
  604.                 $vars[$this->openingDelimiter . $allowedvar $this->closingDelimiter$value;
  605.             }
  606.         }
  607.         // if we are inside a hidden block, don't bother
  608.         if (!$fakeParse{
  609.             if (0 != count($vars&& (!$flagRecursion || !empty($this->_functions[$block]))) {
  610.                 $varKeys     array_keys($vars);
  611.                 $varValues   $this->_options['preserve_data']array_map(array(&$this'_preserveOpeningDelimiter')array_values($vars))array_values($vars);
  612.             }
  613.  
  614.             // check whether the block is considered "empty" and append parsed content if not
  615.             if (!$empty || ('__global__' == $block|| !$this->removeEmptyBlocks || isset($this->_touchedBlocks[$block])) {
  616.                 // perform callbacks
  617.                 if (!empty($this->_functions[$block])) {
  618.                     foreach ($this->_functions[$blockas $id => $data{
  619.                         $placeholder $this->openingDelimiter . '__function_' $id '__' $this->closingDelimiter;
  620.                         // do not waste time calling function more than once
  621.                         if (!isset($vars[$placeholder])) {
  622.                             $args         = array();
  623.                             $preserveArgs = isset($this->_callback[$data['name']]['preserveArgs']&& $this->_callback[$data['name']]['preserveArgs'];
  624.                             foreach ($data['args'as $arg{
  625.                                 $args[(empty($varKeys|| $preserveArgs)$argstr_replace($varKeys$varValues$arg);
  626.                             }
  627.                             if (isset($this->_callback[$data['name']]['data'])) {
  628.                                 $res call_user_func_array($this->_callback[$data['name']]['data']$args);
  629.                             else {
  630.                                 $res = isset($args[0])$args[0]'';
  631.                             }
  632.                             $outer str_replace($placeholder$res$outer);
  633.                             // save the result to variable cache, it can be requested somewhere else
  634.                             $vars[$placeholder$res;
  635.                         }
  636.                     }
  637.                 }
  638.                 // substitute variables only on non-recursive call, thus all
  639.                 // variables from all inner blocks get substituted
  640.                 if (!$flagRecursion && !empty($varKeys)) {
  641.                     $outer str_replace($varKeys$varValues$outer);
  642.                 }
  643.  
  644.                 $this->_parsedBlocks[$block.= $outer;
  645.                 if (isset($this->_touchedBlocks[$block])) {
  646.                     unset($this->_touchedBlocks[$block]);
  647.                 }
  648.             }
  649.         }
  650.         return $empty;
  651.     }
  652.  
  653.  
  654.    /**
  655.     * Sets a variable value.
  656.     *
  657.     * The function can be used either like setVariable("varname", "value")
  658.     * or with one array $variables["varname"] = "value" given setVariable($variables)
  659.     *
  660.     * @access public
  661.     * @param  mixed     variable name or array ('varname'=>'value')
  662.     * @param  string    variable value if $variable is not an array
  663.     */
  664.     function setVariable($variable$value '')
  665.     {
  666.         if (is_array($variable)) {
  667.             $this->_variables array_merge($this->_variables$variable);
  668.         elseif (!is_array($variable&& is_array($value)) {
  669.             $this->_variables array_merge($this->_variables$this->_flattenVariables($variable$value));
  670.         else {
  671.             $this->_variables[$variable$value;
  672.         }
  673.     }
  674.  
  675.  
  676.    /**
  677.     * Sets a global variable value.
  678.     *
  679.     * @access public
  680.     * @param  mixed     variable name or array ('varname'=>'value')
  681.     * @param  string    variable value if $variable is not an array
  682.     * @see    setVariable()
  683.     */
  684.     function setGlobalVariable($variable$value '')
  685.     {
  686.         if (is_array($variable)) {
  687.             $this->_globalVariables array_merge($this->_globalVariables$variable);
  688.         else if (!is_array($variable&& is_array($value)) {
  689.             $this->_globalVariables array_merge($this->_globalVariables$this->_flattenVariables($variable$value));
  690.         else {
  691.             $this->_globalVariables[$variable$value;
  692.         }
  693.     }
  694.  
  695.  
  696.    /**
  697.     * Sets the name of the current block: the block where variables are added
  698.     *
  699.     * @param    string      block name
  700.     * @return   mixed       SIGMA_OK on success, error object on failure
  701.     * @throws   PEAR_Error
  702.     * @access   public
  703.     */
  704.     function setCurrentBlock($block '__global__')
  705.     {
  706.         if (!isset($this->_blocks[$block])) {
  707.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  708.         }
  709.         $this->currentBlock = $block;
  710.         return SIGMA_OK;
  711.     }
  712.  
  713.  
  714.    /**
  715.     * Parses the current block
  716.     *
  717.     * @see      parse(), setCurrentBlock()
  718.     * @access   public
  719.     */
  720.     function parseCurrentBlock()
  721.     {
  722.         return $this->parse($this->currentBlock);
  723.     }
  724.  
  725.  
  726.    /**
  727.     * Returns the current block name
  728.     *
  729.     * @return string    block name
  730.     * @access public
  731.     */
  732.     function getCurrentBlock()
  733.     {
  734.         return $this->currentBlock;
  735.     }
  736.  
  737.  
  738.    /**
  739.     * Preserves the block even if empty blocks should be removed.
  740.     *
  741.     * Sometimes you have blocks that should be preserved although they are
  742.     * empty (no placeholder replaced). Think of a shopping basket. If it's
  743.     * empty you have to show a message to the user. If it's filled you have
  744.     * to show the contents of the shopping basket. Now where to place the
  745.     * message that the basket is empty? It's not a good idea to place it
  746.     * in you application as customers tend to like unecessary minor text
  747.     * changes. Having another template file for an empty basket means that
  748.     * one fine day the filled and empty basket templates will have different
  749.     * layouts.
  750.     *
  751.     * So blocks that do not contain any placeholders but only messages like
  752.     * "Your shopping basked is empty" are intoduced. Now if there is no
  753.     * replacement done in such a block the block will be recognized as "empty"
  754.     * and by default ($removeEmptyBlocks = true) be stripped off. To avoid this
  755.     * you can call touchBlock()
  756.     *
  757.     * @param    string      block name
  758.     * @return   mixed       SIGMA_OK on success, error object on failure
  759.     * @throws   PEAR_Error
  760.     * @access   public
  761.     * @see      $removeEmptyBlocks, $_touchedBlocks
  762.     */
  763.     function touchBlock($block)
  764.     {
  765.         if (!isset($this->_blocks[$block])) {
  766.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  767.         }
  768.         if (isset($this->_hiddenBlocks[$block])) {
  769.             unset($this->_hiddenBlocks[$block]);
  770.         }
  771.         $this->_touchedBlocks[$block= true;
  772.         return SIGMA_OK;
  773.     }
  774.  
  775.  
  776.    /**
  777.     * Hides the block even if it is not "empty".
  778.     *
  779.     * Is somewhat an opposite to touchBlock().
  780.     *
  781.     * Consider a block (a 'edit' link for example) that should be visible to
  782.     * registered/"special" users only, but its visibility is triggered by
  783.     * some little 'id' field passed in a large array into setVariable(). You
  784.     * can either carefully juggle your variables to prevent the block from
  785.     * appearing (a fragile solution) or simply call hideBlock()
  786.     *
  787.     * @param    string      block name
  788.     * @return   mixed       SIGMA_OK on success, error object on failure
  789.     * @throws   PEAR_Error
  790.     * @access   public
  791.     */
  792.     function hideBlock($block)
  793.     {
  794.         if (!isset($this->_blocks[$block])) {
  795.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  796.         }
  797.         if (isset($this->_touchedBlocks[$block])) {
  798.             unset($this->_touchedBlocks[$block]);
  799.         }
  800.         $this->_hiddenBlocks[$block= true;
  801.         return SIGMA_OK;
  802.     }
  803.  
  804.  
  805.    /**
  806.     * Sets the template.
  807.     *
  808.     * You can either load a template file from disk with LoadTemplatefile() or set the
  809.     * template manually using this function.
  810.     *
  811.     * @access public
  812.     * @param  string      template content
  813.     * @param  boolean     remove unknown/unused variables?
  814.     * @param  boolean     remove empty blocks?
  815.     * @return mixed       SIGMA_OK on success, error object on failure
  816.     * @see    loadTemplatefile()
  817.     */
  818.     function setTemplate($template$removeUnknownVariables = true$removeEmptyBlocks = true)
  819.     {
  820.         $this->_resetTemplate($removeUnknownVariables$removeEmptyBlocks);
  821.         $list $this->_buildBlocks('<!-- BEGIN __global__ -->'.$template.'<!-- END __global__ -->');
  822.         if (PEAR::isError($list)) {
  823.             return $list;
  824.         }
  825.         return $this->_buildBlockVariables();
  826.     }
  827.  
  828.  
  829.    /**
  830.     * Loads a template file.
  831.     *
  832.     * If caching is on, then it checks whether a "prepared" template exists.
  833.     * If it does, it gets loaded instead of the original, if it does not, then
  834.     * the original gets loaded and prepared and then the prepared version is saved.
  835.     * addBlockfile() and replaceBlockfile() implement quite the same logic.
  836.     *
  837.     * @param    string      filename
  838.     * @param    boolean     remove unknown/unused variables?
  839.     * @param    boolean     remove empty blocks?
  840.     * @access   public
  841.     * @return   mixed       SIGMA_OK on success, error object on failure
  842.     * @see      setTemplate(), $removeUnknownVariables, $removeEmptyBlocks
  843.     */
  844.     function loadTemplateFile($filename$removeUnknownVariables = true$removeEmptyBlocks = true)
  845.     {
  846.         if ($this->_isCached($filename)) {
  847.             $this->_resetTemplate($removeUnknownVariables$removeEmptyBlocks);
  848.             return $this->_getCached($filename);
  849.         }
  850.         $template $this->_getFile($this->fileRoot . $filename);
  851.         if (PEAR::isError($template)) {
  852.             return $template;
  853.         }
  854.         $this->_triggers = array();
  855.         $template preg_replace($this->includeRegExp"\$this->_makeTrigger('\\1', '__global__')"$template);
  856.         if (SIGMA_OK !== ($res $this->setTemplate($template$removeUnknownVariables$removeEmptyBlocks))) {
  857.             return $res;
  858.         else {
  859.             return $this->_writeCache($filename'__global__');
  860.         }
  861.     }
  862.  
  863.  
  864.    /**
  865.     * Adds a block to the template changing a variable placeholder to a block placeholder.
  866.     *
  867.     * This means that a new block will be integrated into the template in
  868.     * place of a variable placeholder. The variable placeholder will be
  869.     * removed and the new block will behave in the same way as if it was
  870.     * inside the original template.
  871.     *
  872.     * The block content must not start with <!-- BEGIN blockname --> and end with
  873.     * <!-- END blockname -->, if it does the error will be thrown.
  874.     *
  875.     * @param    string    name of the variable placeholder, the name must be unique within the template.
  876.     * @param    string    name of the block to be added
  877.     * @param    string    content of the block
  878.     * @return   mixed     SIGMA_OK on success, error object on failure
  879.     * @throws   PEAR_Error
  880.     * @see      addBlockfile()
  881.     * @access   public
  882.     */
  883.     function addBlock($placeholder$block$template)
  884.     {
  885.         if (isset($this->_blocks[$block])) {
  886.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_EXISTS$block)SIGMA_BLOCK_EXISTS);
  887.         }
  888.         $parents $this->_findParentBlocks($placeholder);
  889.         if (0 == count($parents)) {
  890.             return $this->raiseError($this->errorMessage(SIGMA_PLACEHOLDER_NOT_FOUND$placeholder)SIGMA_PLACEHOLDER_NOT_FOUND);
  891.         elseif (count($parents> 1{
  892.             return $this->raiseError($this->errorMessage(SIGMA_PLACEHOLDER_DUPLICATE$placeholder)SIGMA_PLACEHOLDER_DUPLICATE);
  893.         }
  894.  
  895.         $template = "<!-- BEGIN $block -->" . $template . "<!-- END $block -->";
  896.         $list     $this->_buildBlocks($template);
  897.         if (PEAR::isError($list)) {
  898.             return $list;
  899.         }
  900.         $this->_replacePlaceholder($parents[0]$placeholder$block);
  901.         return $this->_buildBlockVariables($block);
  902.     }
  903.  
  904.  
  905.    /**
  906.     * Adds a block taken from a file to the template, changing a variable placeholder
  907.     * to a block placeholder.
  908.     *
  909.     * @param      string    name of the variable placeholder
  910.     * @param      string    name of the block to be added
  911.     * @param      string    template file that contains the block
  912.     * @return     mixed     SIGMA_OK on success, error object on failure
  913.     * @throws     PEAR_Error
  914.     * @see        addBlock()
  915.     * @access     public
  916.     */
  917.     function addBlockfile($placeholder$block$filename)
  918.     {
  919.         if ($this->_isCached($filename)) {
  920.             return $this->_getCached($filename$block$placeholder);
  921.         }
  922.         $template $this->_getFile($this->fileRoot . $filename);
  923.         if (PEAR::isError($template)) {
  924.             return $template;
  925.         }
  926.         $template preg_replace($this->includeRegExp"\$this->_makeTrigger('\\1', '{$block}')"$template);
  927.         if (SIGMA_OK !== ($res $this->addBlock($placeholder$block$template))) {
  928.             return $res;
  929.         else {
  930.             return $this->_writeCache($filename$block);
  931.         }
  932.     }
  933.  
  934.  
  935.    /**
  936.     * Replaces an existing block with new content.
  937.     *
  938.     * This function will replace a block of the template and all blocks
  939.     * contained in it and add a new block instead. This means you can
  940.     * dynamically change your template.
  941.     *
  942.     * Sigma analyses the way you've nested blocks and knows which block
  943.     * belongs into another block. This nesting information helps to make the
  944.     * API short and simple. Replacing blocks does not only mean that Sigma
  945.     * has to update the nesting information (relatively time consuming task)
  946.     * but you have to make sure that you do not get confused due to the
  947.     * template change yourself.
  948.     *
  949.     * @param   string    name of a block to replace
  950.     * @param   string    new content
  951.     * @param   boolean   true if the parsed contents of the block should be kept
  952.     * @access  public
  953.     * @see     replaceBlockfile(), addBlock()
  954.     * @return  mixed     SIGMA_OK on success, error object on failure
  955.     * @throws  PEAR_Error
  956.     */
  957.     function replaceBlock($block$template$keepContent = false)
  958.     {
  959.         if (!isset($this->_blocks[$block])) {
  960.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  961.         }
  962.         // should not throw a error as we already checked for block existance
  963.         $this->_removeBlockData($block$keepContent);
  964.         $template = "<!-- BEGIN $block -->" . $template . "<!-- END $block -->";
  965.  
  966.         $list $this->_buildBlocks($template);
  967.         if (PEAR::isError($list)) {
  968.             return $list;
  969.         }
  970.         // renew the variables list
  971.         return $this->_buildBlockVariables($block);
  972.     }
  973.  
  974.  
  975.    /**
  976.     * Replaces an existing block with new content from a file.
  977.     *
  978.     * @access     public
  979.     * @param      string    name of a block to replace
  980.     * @param      string    template file that contains the block
  981.     * @param      boolean   true if the parsed contents of the block should be kept
  982.     * @return     mixed     SIGMA_OK on success, error object on failure
  983.     * @throws     PEAR_Error
  984.     * @see        replaceBlock(), addBlockfile()
  985.     */
  986.     function replaceBlockfile($block$filename$keepContent = false)
  987.     {
  988.         if ($this->_isCached($filename)) {
  989.             if (PEAR::isError($res $this->_removeBlockData($block$keepContent))) {
  990.                 return $res;
  991.             else {
  992.                 return $this->_getCached($filename$block);
  993.             }
  994.         }
  995.         $template $this->_getFile($this->fileRoot . $filename);
  996.         if (PEAR::isError($template)) {
  997.             return $template;
  998.         }
  999.         $template preg_replace($this->includeRegExp"\$this->_makeTrigger('\\1', '{$block}')"$template);
  1000.         if (SIGMA_OK !== ($res $this->replaceBlock($block$template$keepContent))) {
  1001.             return $res;
  1002.         else {
  1003.             return $this->_writeCache($filename$block);
  1004.         }
  1005.     }
  1006.  
  1007.  
  1008.    /**
  1009.     * Checks if the block exists in the template
  1010.     *
  1011.     * @param  string  block name
  1012.     * @return bool 
  1013.     * @access public
  1014.     */
  1015.     function blockExists($block)
  1016.     {
  1017.         return isset($this->_blocks[$block]);
  1018.     }
  1019.  
  1020.  
  1021.    /**
  1022.     * Returns the name of the (first) block that contains the specified placeholder.
  1023.     *
  1024.     * @param    string  Name of the placeholder you're searching
  1025.     * @param    string  Name of the block to scan. If left out (default) all blocks are scanned.
  1026.     * @return   string  Name of the (first) block that contains the specified placeholder.
  1027.     *                    If the placeholder was not found an empty string is returned.
  1028.     * @access   public
  1029.     * @throws   PEAR_Error
  1030.     */
  1031.     function placeholderExists($placeholder$block '')
  1032.     {
  1033.         if ('' != $block && !isset($this->_blocks[$block])) {
  1034.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  1035.         }
  1036.         if ('' != $block{
  1037.             // if we search in the specific block, we should just check the array
  1038.             return isset($this->_blockVariables[$block][$placeholder])$block'';
  1039.         else {
  1040.             // _findParentBlocks returns an array, we need only the first element
  1041.             $parents $this->_findParentBlocks($placeholder);
  1042.             return empty($parents)''$parents[0];
  1043.         }
  1044.     // end func placeholderExists
  1045.  
  1046.  
  1047.    /**
  1048.     * Sets a callback function.
  1049.     *
  1050.     * Sigma templates can contain simple function calls. This means that the
  1051.     * author of the template can add a special placeholder to it:
  1052.     * func_h1("embedded in h1")
  1053.     * Sigma will parse the template for these placeholders and will allow
  1054.     * you to define a callback function for them. Callback will be called
  1055.     * automatically when the block containing such function call is parse()'d.
  1056.     *
  1057.     * Please note that arguments to these template functions can contain
  1058.     * variable placeholders: func_translate('Hello, {username}'), but not
  1059.     * blocks or other function calls.
  1060.     *
  1061.     * This should NOT be used to add logic (except some presentation one) to
  1062.     * the template. If you use a lot of such callbacks and implement business
  1063.     * logic through them, then you're reinventing the wheel. Consider using
  1064.     * XML/XSLT, native PHP or some other template engine.
  1065.     *
  1066.     * <?php
  1067.     * function h_one($arg) {
  1068.     *    return '<h1>' . $arg . '</h1>';
  1069.     * }
  1070.     * ...
  1071.     * $tpl = new HTML_Template_Sigma( ... );
  1072.     * ...
  1073.     * $tpl->setCallbackFunction('h1', 'h_one');
  1074.     * ?>
  1075.     *
  1076.     * template:
  1077.     * func_h1('H1 Headline');
  1078.     *
  1079.     * @param    string    Function name in the template
  1080.     * @param    mixed     A callback: anything that can be passed to call_user_func_array()
  1081.     * @param    bool      If true, then no variable substitution in arguments will take place before function call
  1082.     * @return   mixed     SIGMA_OK on success, error object on failure
  1083.     * @throws   PEAR_Error
  1084.     * @access   public
  1085.     */
  1086.     function setCallbackFunction($tplFunction$callback$preserveArgs = false)
  1087.     {
  1088.         if (!is_callable($callback)) {
  1089.             return $this->raiseError($this->errorMessage(SIGMA_INVALID_CALLBACK)SIGMA_INVALID_CALLBACK);
  1090.         }
  1091.         $this->_callback[$tplFunction= array(
  1092.             'data'         => $callback,
  1093.             'preserveArgs' => $preserveArgs
  1094.         );
  1095.         return SIGMA_OK;
  1096.     // end func setCallbackFunction
  1097.  
  1098.  
  1099.    /**
  1100.     * Returns a list of blocks within a template.
  1101.     *
  1102.     * If $recursive is false, it returns just a 'flat' array of $parent's
  1103.     * direct subblocks. If $recursive is true, it builds a tree of template
  1104.     * blocks using $parent as root. Tree structure is compatible with
  1105.     * PEAR::Tree's Memory_Array driver.
  1106.     *
  1107.     * @param    string  parent block name
  1108.     * @param    bool    whether to return a tree of child blocks (true) or a 'flat' array (false)
  1109.     * @access   public
  1110.     * @return   array   a list of child blocks
  1111.     * @throws   PEAR_Error
  1112.     */
  1113.     function getBlockList($parent '__global__'$recursive = false)
  1114.     {
  1115.         if (!isset($this->_blocks[$parent])) {
  1116.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$parent)SIGMA_BLOCK_NOT_FOUND);
  1117.         }
  1118.         if (!$recursive{
  1119.             return isset($this->_children[$parent])array_keys($this->_children[$parent]): array();
  1120.         else {
  1121.             $ret = array('name' => $parent);
  1122.             if (!empty($this->_children[$parent])) {
  1123.                 $ret['children'= array();
  1124.                 foreach (array_keys($this->_children[$parent]as $child{
  1125.                     $ret['children'][$this->getBlockList($childtrue);
  1126.                 }
  1127.             }
  1128.             return $ret;
  1129.         }
  1130.     }
  1131.  
  1132.  
  1133.    /**
  1134.     * Returns a list of placeholders within a block.
  1135.     *
  1136.     * Only 'normal' placeholders are returned, not auto-created ones.
  1137.     *
  1138.     * @param    string  block name
  1139.     * @access   public
  1140.     * @return   array   a list of placeholders
  1141.     * @throws   PEAR_Error
  1142.     */
  1143.     function getPlaceholderList($block '__global__')
  1144.     {
  1145.         if (!isset($this->_blocks[$block])) {
  1146.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  1147.         }
  1148.         $ret = array();
  1149.         foreach ($this->_blockVariables[$blockas $var => $v{
  1150.             if ('__' != substr($var02|| '__' != substr($var-2)) {
  1151.                 $ret[$var;
  1152.             }
  1153.         }
  1154.         return $ret;
  1155.     }
  1156.  
  1157.  
  1158.    /**
  1159.     * Clears the variables
  1160.     *
  1161.     * Global variables are not affected. The method is useful when you add
  1162.     * a lot of variables via setVariable() and are not sure whether all of
  1163.     * them appear in the block you parse(). If you clear the variables after
  1164.     * parse(), you don't risk them suddenly showing up in other blocks.
  1165.     *
  1166.     * @access public
  1167.     * @see    setVariable()
  1168.     */
  1169.     function clearVariables()
  1170.     {
  1171.         $this->_variables = array();
  1172.     }
  1173.  
  1174.  
  1175.     //------------------------------------------------------------
  1176.     //
  1177.     // Private methods follow
  1178.     //
  1179.     //------------------------------------------------------------
  1180.  
  1181.     /**
  1182.      * Builds the variable names for nested variables
  1183.      *
  1184.      * @param    string    variable name
  1185.      * @param    array     value array
  1186.      * @return   array     array with 'name.key' keys
  1187.      * @access   private
  1188.      */
  1189.     function _flattenVariables($name$array)
  1190.     {
  1191.         $ret = array();
  1192.         foreach ($array as $key => $value{
  1193.             if (is_array($value)) {
  1194.                 $ret array_merge($ret$this->_flattenVariables($name '.' $key$value));
  1195.             else {
  1196.                 $ret[$name '.' $key$value;
  1197.             }
  1198.         }
  1199.         return $ret;
  1200.     }
  1201.  
  1202.    /**
  1203.     * Reads the file and returns its content
  1204.     *
  1205.     * @param    string    filename
  1206.     * @return   string    file content (or error object)
  1207.     * @access   private
  1208.     */
  1209.     function _getFile($filename)
  1210.     {
  1211.         if (!($fh @fopen($filename'rb'))) {
  1212.             return $this->raiseError($this->errorMessage(SIGMA_TPL_NOT_FOUND$filename)SIGMA_TPL_NOT_FOUND);
  1213.         }
  1214.         $content fread($fhmax(1filesize($filename)));
  1215.         fclose($fh);
  1216.         return $content;
  1217.     }
  1218.  
  1219.  
  1220.    /**
  1221.     * Recursively builds a list of all variables within a block.
  1222.     *
  1223.     * Also calls _buildFunctionlist() for each block it visits
  1224.     *
  1225.     * @param    string block name
  1226.     * @see      _buildFunctionlist()
  1227.     * @access   private
  1228.     */
  1229.     function _buildBlockVariables($block '__global__')
  1230.     {
  1231.         $this->_blockVariables[$block= array();
  1232.         $this->_functions[$block]      = array();
  1233.         preg_match_all($this->variablesRegExp$this->_blocks[$block]$regsPREG_SET_ORDER);
  1234.         foreach ($regs as $match{
  1235.             $this->_blockVariables[$block][$match[1]] = true;
  1236.             if (!empty($match[3])) {
  1237.                 $funcData = array(
  1238.                     'name' => $match[3],
  1239.                     'args' => array($this->openingDelimiter . $match[1$this->closingDelimiter)
  1240.                 );
  1241.                 $funcId   substr(md5(serialize($funcData))010);
  1242.  
  1243.                 // update block info
  1244.                 $this->_blocks[$blockstr_replace($match[0]$this->openingDelimiter . '__function_' $funcId '__' $this->closingDelimiter$this->_blocks[$block]);
  1245.                 $this->_blockVariables[$block]['__function_' $funcId '__'= true;
  1246.                 $this->_functions[$block][$funcId$funcData;
  1247.             }
  1248.         }
  1249.         if (SIGMA_OK != ($res $this->_buildFunctionlist($block))) {
  1250.             return $res;
  1251.         }
  1252.         if (isset($this->_children[$block]&& is_array($this->_children[$block])) {
  1253.             foreach ($this->_children[$blockas $child => $v{
  1254.                 if (SIGMA_OK != ($res $this->_buildBlockVariables($child))) {
  1255.                     return $res;
  1256.                 }
  1257.             }
  1258.         }
  1259.         return SIGMA_OK;
  1260.     }
  1261.  
  1262.  
  1263.    /**
  1264.     * Recusively builds a list of all blocks within the template.
  1265.     *
  1266.     * @param    string    template to be scanned
  1267.     * @see      $_blocks
  1268.     * @throws   PEAR_Error
  1269.     * @return   mixed     array of block names on success or error object on failure
  1270.     * @access   private
  1271.     */
  1272.     function _buildBlocks($string)
  1273.     {
  1274.         $blocks = array();
  1275.         if (preg_match_all($this->blockRegExp$string$regsPREG_SET_ORDER)) {
  1276.             foreach ($regs as $k => $match{
  1277.                 $blockname    $match[1];
  1278.                 $blockcontent $match[2];
  1279.                 if (isset($this->_blocks[$blockname]|| isset($blocks[$blockname])) {
  1280.                     return $this->raiseError($this->errorMessage(SIGMA_BLOCK_DUPLICATE$blockname)SIGMA_BLOCK_DUPLICATE);
  1281.                 }
  1282.                 $this->_blocks[$blockname$blockcontent;
  1283.                 $blocks[$blockname= true;
  1284.                 $inner              $this->_buildBlocks($blockcontent);
  1285.                 if (PEAR::isError($inner)) {
  1286.                     return $inner;
  1287.                 }
  1288.                 foreach ($inner as $name => $v{
  1289.                     $pattern     sprintf('@<!--\s+BEGIN\s+%s\s+-->(.*)<!--\s+END\s+%s\s+-->@sm'$name$name);
  1290.                     $replacement $this->openingDelimiter.'__'.$name.'__'.$this->closingDelimiter;
  1291.                     $this->_blocks[$blockname]          preg_replace($pattern$replacement$this->_blocks[$blockname]);
  1292.                     $this->_children[$blockname][$name= true;
  1293.                 }
  1294.             }
  1295.         }
  1296.         return $blocks;
  1297.     }
  1298.  
  1299.  
  1300.    /**
  1301.     * Resets the object's properties, used before processing a new template
  1302.     *
  1303.     * @access   private
  1304.     * @param    boolean     remove unknown/unused variables?
  1305.     * @param    boolean     remove empty blocks?
  1306.     * @see      setTemplate(), loadTemplateFile()
  1307.     * @access   private
  1308.     */
  1309.     function _resetTemplate($removeUnknownVariables = true$removeEmptyBlocks = true)
  1310.     {
  1311.         $this->removeUnknownVariables = $removeUnknownVariables;
  1312.         $this->removeEmptyBlocks      = $removeEmptyBlocks;
  1313.         $this->currentBlock           = '__global__';
  1314.         $this->_variables             = array();
  1315.         $this->_blocks                = array();
  1316.         $this->_children              = array();
  1317.         $this->_parsedBlocks          = array();
  1318.         $this->_touchedBlocks         = array();
  1319.         $this->_functions             = array();
  1320.         $this->flagGlobalParsed       = false;
  1321.     // _resetTemplate
  1322.  
  1323.  
  1324.    /**
  1325.     * Checks whether we have a "prepared" template cached.
  1326.     *
  1327.     * If we do not do caching, always returns false
  1328.     *
  1329.     * @access private
  1330.     * @param  string source filename
  1331.     * @return bool yes/no
  1332.     * @see loadTemplatefile(), addBlockfile(), replaceBlockfile()
  1333.     */
  1334.     function _isCached($filename)
  1335.     {
  1336.         if (null === $this->_cacheRoot{
  1337.             return false;
  1338.         }
  1339.         $cachedName $this->_cachedName($filename);
  1340.         $sourceName $this->fileRoot . $filename;
  1341.         // if $sourceName does not exist, error will be thrown later
  1342.         $sourceTime @filemtime($sourceName);
  1343.         if ((false !== $sourceTime&& @file_exists($cachedName&& (filemtime($cachedName$sourceTime)) {
  1344.             return true;
  1345.         else {
  1346.             return false;
  1347.         }
  1348.     // _isCached
  1349.  
  1350.  
  1351.    /**
  1352.     * Loads a "prepared" template file
  1353.     *
  1354.     * @access   private
  1355.     * @param    string  filename
  1356.     * @param    string  block name
  1357.     * @param    string  variable placeholder to replace by a block
  1358.     * @return   mixed   SIGMA_OK on success, error object on failure
  1359.     * @see loadTemplatefile(), addBlockfile(), replaceBlockfile()
  1360.     */
  1361.     function _getCached($filename$block '__global__'$placeholder '')
  1362.     {
  1363.         // the same checks are done in addBlock()
  1364.         if (!empty($placeholder)) {
  1365.             if (isset($this->_blocks[$block])) {
  1366.                 return $this->raiseError($this->errorMessage(SIGMA_BLOCK_EXISTS$block)SIGMA_BLOCK_EXISTS);
  1367.             }
  1368.             $parents $this->_findParentBlocks($placeholder);
  1369.             if (0 == count($parents)) {
  1370.                 return $this->raiseError($this->errorMessage(SIGMA_PLACEHOLDER_NOT_FOUND$placeholder)SIGMA_PLACEHOLDER_NOT_FOUND);
  1371.             elseif (count($parents> 1{
  1372.                 return $this->raiseError($this->errorMessage(SIGMA_PLACEHOLDER_DUPLICATE$placeholder)SIGMA_PLACEHOLDER_DUPLICATE);
  1373.             }
  1374.         }
  1375.         $content $this->_getFile($this->_cachedName($filename));
  1376.         if (PEAR::isError($content)) {
  1377.             return $content;
  1378.         }
  1379.         $cache unserialize($content);
  1380.         if ('__global__' != $block{
  1381.             $this->_blocks[$block]         $cache['blocks']['__global__'];
  1382.             $this->_blockVariables[$block$cache['variables']['__global__'];
  1383.             $this->_children[$block]       $cache['children']['__global__'];
  1384.             $this->_functions[$block]      $cache['functions']['__global__'];
  1385.             unset($cache['blocks']['__global__']$cache['variables']['__global__']$cache['children']['__global__']$cache['functions']['__global__']);
  1386.         }
  1387.         $this->_blocks         array_merge($this->_blocks$cache['blocks']);
  1388.         $this->_blockVariables array_merge($this->_blockVariables$cache['variables']);
  1389.         $this->_children       array_merge($this->_children$cache['children']);
  1390.         $this->_functions      array_merge($this->_functions$cache['functions']);
  1391.  
  1392.         // the same thing gets done in addBlockfile()
  1393.         if (!empty($placeholder)) {
  1394.             $this->_replacePlaceholder($parents[0]$placeholder$block);
  1395.         }
  1396.         // pull the triggers, if any
  1397.         if (isset($cache['triggers'])) {
  1398.             return $this->_pullTriggers($cache['triggers']);
  1399.         }
  1400.         return SIGMA_OK;
  1401.     // _getCached
  1402.  
  1403.  
  1404.    /**
  1405.     * Returns a full name of a "prepared" template file
  1406.     *
  1407.     * @access private
  1408.     * @param string  source filename, relative to root directory
  1409.     * @return string filename
  1410.     */
  1411.     function _cachedName($filename)
  1412.     {
  1413.         if (OS_WINDOWS{
  1414.             $filename str_replace(array('/''\\'':')array('__''__''')$filename);
  1415.         else {
  1416.             $filename str_replace('/''__'$filename);
  1417.         }
  1418.         return $this->_cacheRoot$filename'.it';
  1419.     // _cachedName
  1420.  
  1421.  
  1422.    /**
  1423.     * Writes a prepared template file.
  1424.     *
  1425.     * Even if NO caching is going on, this method has a side effect: it calls
  1426.     * the _pullTriggers() method and thus loads all files added via <!-- INCLUDE -->
  1427.     *
  1428.     * @access private
  1429.     * @param string   source filename, relative to root directory
  1430.     * @param string   name of the block to save into file
  1431.     * @return mixed   SIGMA_OK on success, error object on failure
  1432.     */
  1433.     function _writeCache($filename$block)
  1434.     {
  1435.         // do not save anything if no cache dir, but do pull triggers
  1436.         if (null !== $this->_cacheRoot{
  1437.             $cache = array(
  1438.                 'blocks'    => array(),
  1439.                 'variables' => array(),
  1440.                 'children'  => array(),
  1441.                 'functions' => array()
  1442.             );
  1443.             $cachedName $this->_cachedName($filename);
  1444.             $this->_buildCache($cache$block);
  1445.             if ('__global__' != $block{
  1446.                 foreach (array_keys($cacheas $k{
  1447.                     $cache[$k]['__global__'$cache[$k][$block];
  1448.                     unset($cache[$k][$block]);
  1449.                 }
  1450.             }
  1451.             if (isset($this->_triggers[$block])) {
  1452.                 $cache['triggers'$this->_triggers[$block];
  1453.             }
  1454.             if (!($fh @fopen($cachedName'wb'))) {
  1455.                 return $this->raiseError($this->errorMessage(SIGMA_CACHE_ERROR$cachedName)SIGMA_CACHE_ERROR);
  1456.             }
  1457.             fwrite($fhserialize($cache));
  1458.             fclose($fh);
  1459.         }
  1460.         // now pull triggers
  1461.         if (isset($this->_triggers[$block])) {
  1462.             if (SIGMA_OK !== ($res $this->_pullTriggers($this->_triggers[$block]))) {
  1463.                 return $res;
  1464.             }
  1465.             unset($this->_triggers[$block]);
  1466.         }
  1467.         return SIGMA_OK;
  1468.     // _writeCache
  1469.  
  1470.  
  1471.    /**
  1472.     * Builds an array of template data to be saved in prepared template file
  1473.     *
  1474.     * @access private
  1475.     * @param array   template data
  1476.     * @param string  block to add to the array
  1477.     */
  1478.     function _buildCache(&$cache$block)
  1479.     {
  1480.         if (!$this->_options['trim_on_save']{
  1481.             $cache['blocks'][$block$this->_blocks[$block];
  1482.         else {
  1483.             $cache['blocks'][$blockpreg_replace(
  1484.                                          array('/^\\s+/m''/\\s+$/m''/(\\r?\\n)+/'),
  1485.                                          array(''''"\n"),
  1486.                                          $this->_blocks[$block]
  1487.                                        );
  1488.         }
  1489.         $cache['variables'][$block$this->_blockVariables[$block];
  1490.         $cache['functions'][$block= isset($this->_functions[$block])$this->_functions[$block]: array();
  1491.         if (!isset($this->_children[$block])) {
  1492.             $cache['children'][$block= array();
  1493.         else {
  1494.             $cache['children'][$block$this->_children[$block];
  1495.             foreach (array_keys($this->_children[$block]as $child{
  1496.                 $this->_buildCache($cache$child);
  1497.             }
  1498.         }
  1499.     }
  1500.  
  1501.  
  1502.    /**
  1503.     * Recursively removes all data belonging to a block
  1504.     *
  1505.     * @param    string    block name
  1506.     * @param    boolean   true if the parsed contents of the block should be kept
  1507.     * @return   mixed     SIGMA_OK on success, error object on failure
  1508.     * @see      replaceBlock(), replaceBlockfile()
  1509.     * @access   private
  1510.     */
  1511.     function _removeBlockData($block$keepContent = false)
  1512.     {
  1513.         if (!isset($this->_blocks[$block])) {
  1514.             return $this->raiseError($this->errorMessage(SIGMA_BLOCK_NOT_FOUND$block)SIGMA_BLOCK_NOT_FOUND);
  1515.         }
  1516.         if (!empty($this->_children[$block])) {
  1517.             foreach (array_keys($this->_children[$block]as $child{
  1518.                 $this->_removeBlockData($childfalse);
  1519.             }
  1520.             unset($this->_children[$block]);
  1521.         }
  1522.         unset($this->_blocks[$block]);
  1523.         unset($this->_blockVariables[$block]);
  1524.         unset($this->_hiddenBlocks[$block]);
  1525.         unset($this->_touchedBlocks[$block]);
  1526.         unset($this->_functions[$block]);
  1527.         if (!$keepContent{
  1528.             unset($this->_parsedBlocks[$block]);
  1529.         }
  1530.         return SIGMA_OK;
  1531.     }
  1532.  
  1533.  
  1534.    /**
  1535.     * Returns the names of the blocks where the variable placeholder appears
  1536.     *
  1537.     * @param    string    variable name
  1538.     * @return    array    block names
  1539.     * @see addBlock(), addBlockfile(), placeholderExists()
  1540.     * @access   private
  1541.     */
  1542.     function _findParentBlocks($variable)
  1543.     {
  1544.         $parents = array();
  1545.         foreach ($this->_blockVariables as $blockname => $varnames{
  1546.             if (!empty($varnames[$variable])) {
  1547.                 $parents[$blockname;
  1548.             }
  1549.         }
  1550.         return $parents;
  1551.     }
  1552.  
  1553.  
  1554.    /**
  1555.     * Replaces a variable placeholder by a block placeholder.
  1556.     *
  1557.     * Of course, it also updates the necessary arrays
  1558.     *
  1559.     * @param    string  name of the block containing the placeholder
  1560.     * @param    string  variable name
  1561.     * @param    string  block name
  1562.     * @access   private
  1563.     */
  1564.     function _replacePlaceholder($parent$placeholder$block)
  1565.     {
  1566.         $this->_children[$parent][$block= true;
  1567.         $this->_blockVariables[$parent]['__'.$block.'__'= true;
  1568.         $this->_blocks[$parent]    str_replace($this->openingDelimiter.$placeholder.$this->closingDelimiter,
  1569.                                                         $this->openingDelimiter.'__'.$block.'__'.$this->closingDelimiter,
  1570.                                                         $this->_blocks[$parent);
  1571.         unset($this->_blockVariables[$parent][$placeholder]);
  1572.     }
  1573.  
  1574.  
  1575.    /**
  1576.     * Generates a placeholder to replace an <!-- INCLUDE filename --> statement
  1577.     *
  1578.     * @access   private
  1579.     * @param    string  filename
  1580.     * @param    string  current block name
  1581.     * @return   string  a placeholder
  1582.     */
  1583.     function _makeTrigger($filename$block)
  1584.     {
  1585.         $name 'trigger_' substr(md5($filename ' ' uniqid($block))010);
  1586.         $this->_triggers[$block][$name$filename;
  1587.         return $this->openingDelimiter . $name $this->closingDelimiter;
  1588.     }
  1589.  
  1590.  
  1591.    /**
  1592.     * Replaces the "trigger" placeholders by the matching file contents.
  1593.     *
  1594.     * @see _makeTrigger(), addBlockfile()
  1595.     * @param    array   array ('trigger placeholder' => 'filename')
  1596.     * @return   mixed   SIGMA_OK on success, error object on failure
  1597.     * @access   private
  1598.     */
  1599.     function _pullTriggers($triggers)
  1600.     {
  1601.         foreach ($triggers as $placeholder => $filename{
  1602.             if (SIGMA_OK !== ($res $this->addBlockfile($placeholder$placeholder$filename))) {
  1603.                 return $res;
  1604.             }
  1605.             // we actually do not need the resultant block...
  1606.             $parents $this->_findParentBlocks('__' $placeholder '__');
  1607.             // merge current block's children and variables with the parent's ones
  1608.             if (isset($this->_children[$placeholder])) {
  1609.                 $this->_children[$parents[0]] array_merge($this->_children[$parents[0]]$this->_children[$placeholder]);
  1610.             }
  1611.             $this->_blockVariables[$parents[0]] array_merge($this->_blockVariables[$parents[0]]$this->_blockVariables[$placeholder]);
  1612.             if (isset($this->_functions[$placeholder])) {
  1613.                 $this->_functions[$parents[0]] array_merge($this->_functions[$parents[0]]$this->_functions[$placeholder]);
  1614.             }
  1615.             // substitute the block's contents into parent's
  1616.             $this->_blocks[$parents[0]] str_replace(
  1617.                                             $this->openingDelimiter . '__' $placeholder '__' $this->closingDelimiter,
  1618.                                             $this->_blocks[$placeholder],
  1619.                                             $this->_blocks[$parents[0]]
  1620.                                           );
  1621.             // remove the stuff that is no more needed
  1622.             unset($this->_blocks[$placeholder]$this->_blockVariables[$placeholder]$this->_children[$placeholder]$this->_functions[$placeholder]);
  1623.             unset($this->_children[$parents[0]][$placeholder]$this->_blockVariables[$parents[0]]['__' $placeholder '__']);
  1624.         }
  1625.         return SIGMA_OK;
  1626.     }
  1627.  
  1628.  
  1629.    /**
  1630.     * Builds a list of functions in a block.
  1631.     *
  1632.     * @access   private
  1633.     * @param    string  Block name
  1634.     * @see _buildBlockVariables()
  1635.     */
  1636.     function _buildFunctionlist($block)
  1637.     {
  1638.         $template $this->_blocks[$block];
  1639.         $this->_blocks[$block'';
  1640.  
  1641.         while (preg_match($this->functionRegExp$template$regs)) {
  1642.             $this->_blocks[$block.= substr($template0strpos($template$regs[0]));
  1643.             $template substr($templatestrpos($template$regs[0]strlen($regs[0]));
  1644.  
  1645.             $state = 1;
  1646.             $funcData = array(
  1647.                 'name' => $regs[1],
  1648.                 'args' => array()
  1649.             );
  1650.             for ($i = 0$len strlen($template)$i $len$i++{
  1651.                 $char $template{$i};
  1652.                 switch ($state{
  1653.                     case 0:
  1654.                     case -1:
  1655.                         break 2;
  1656.  
  1657.                     case 1:
  1658.                         $arg '';
  1659.                         if (')' == $char{
  1660.                             $state = 0;
  1661.                         elseif (',' == $char{
  1662.                             $error 'Unexpected \',\'';
  1663.                             $state = -1;
  1664.                         elseif ('\'' == $char || '"' == $char{
  1665.                             $quote $char;
  1666.                             $state = 5;
  1667.                         elseif (!ctype_space($char)) {
  1668.                             $arg  .= $char;
  1669.                             $state = 3;
  1670.                         }
  1671.                         break;
  1672.  
  1673.                     case 2:
  1674.                         $arg '';
  1675.                         if (',' == $char || ')' == $char{
  1676.                             $error 'Unexpected \'' $char '\'';
  1677.                             $state = -1;
  1678.                         elseif ('\'' == $char || '"' == $char{
  1679.                             $quote $char;
  1680.                             $state = 5;
  1681.                         elseif (!ctype_space($char)) {
  1682.                             $arg  .= $char;
  1683.                             $state = 3;
  1684.                         }
  1685.                         break;
  1686.  
  1687.                     case 3:
  1688.                         if (')' == $char{
  1689.                             $funcData['args'][rtrim($arg);
  1690.                             $state  = 0;
  1691.                         elseif (',' == $char{
  1692.                             $funcData['args'][rtrim($arg);
  1693.                             $state = 2;
  1694.                         elseif ('\'' == $char || '"' == $char{
  1695.                             $quote $char;
  1696.                             $arg  .= $char;
  1697.                             $state = 4;
  1698.                         else {
  1699.                             $arg  .= $char;
  1700.                         }
  1701.                         break;
  1702.  
  1703.                     case 4:
  1704.                         $arg .= $char;
  1705.                         if ($quote == $char{
  1706.                             $state = 3;
  1707.                         }
  1708.                         break;
  1709.  
  1710.                     case 5:
  1711.                         if ('\\' == $char{
  1712.                             $state = 6;
  1713.                         elseif ($quote == $char{
  1714.                             $state = 7;
  1715.                         else {
  1716.                             $arg .= $char;
  1717.                         }
  1718.                         break;
  1719.  
  1720.                     case 6:
  1721.                         $arg  .= $char;
  1722.                         $state = 5;
  1723.                         break;
  1724.  
  1725.                     case 7:
  1726.                         if (')' == $char{
  1727.                             $funcData['args'][$arg;
  1728.                             $state  = 0;
  1729.                         elseif (',' == $char{
  1730.                             $funcData['args'][$arg;
  1731.                             $state  = 2;
  1732.                         elseif (!ctype_space($char)) {
  1733.                             $error 'Unexpected \'' $char '\' (expected: \')\' or \',\')';
  1734.                             $state = -1;
  1735.                         }
  1736.                         break;
  1737.                 // switch
  1738.             // for
  1739.             if (0 != $state{
  1740.                 return $this->raiseError($this->errorMessage(SIGMA_CALLBACK_SYNTAX_ERROR(empty($error)'Unexpected end of input'$error' in ' $regs[0substr($template0$i))SIGMA_CALLBACK_SYNTAX_ERROR);
  1741.             else {
  1742.                 $funcId   'f' substr(md5(serialize($funcData))010);
  1743.                 $template substr($template$i);
  1744.  
  1745.                 $this->_blocks[$block.= $this->openingDelimiter . '__function_' $funcId '__' $this->closingDelimiter;
  1746.                 $this->_blockVariables[$block]['__function_' $funcId '__'= true;
  1747.                 $this->_functions[$block][$funcId$funcData;
  1748.             }
  1749.         // while
  1750.         $this->_blocks[$block.= $template;
  1751.         return SIGMA_OK;
  1752.     // end func _buildFunctionlist
  1753.  
  1754.  
  1755.    /**
  1756.     * Replaces an opening delimiter by a special string.
  1757.     *
  1758.     * Used to implement $_options['preserve_data'] logic
  1759.     *
  1760.     * @access   private
  1761.     * @param string 
  1762.     * @return string 
  1763.     */
  1764.     function _preserveOpeningDelimiter($str)
  1765.     {
  1766.         return (false === strpos($str$this->openingDelimiter))?
  1767.                 $str:
  1768.                 str_replace($this->openingDelimiter$this->openingDelimiter . '%preserved%' $this->closingDelimiter$str);
  1769.     }
  1770.  
  1771.  
  1772.    /**
  1773.     * Quotes the string so that it can be used in Javascript string constants
  1774.     *
  1775.     * @access private
  1776.     * @param  string 
  1777.     * @return string 
  1778.     */
  1779.     function _jsEscape($value)
  1780.     {
  1781.         return strtr($valuearray(
  1782.                     "\r" => '\r'"'"  => "\\'""\n" => '\n',
  1783.                     '"'  => '\"'"\t" => '\t',  '\\' => '\\\\'
  1784.                ));
  1785.     }
  1786. }
  1787. ?>

Documentation generated on Mon, 11 Mar 2019 14:40:18 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.