home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / HTML / Template / Sigma.php < prev   
Encoding:
PHP Script  |  2008-07-02  |  66.6 KB  |  1,805 lines

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