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

Documentation generated on Mon, 11 Mar 2019 13:57:22 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.