From cocomp at users.sourceforge.net Wed Sep 17 12:54:18 2008 From: cocomp at users.sourceforge.net (cocomp@users.sourceforge.net) Date: Wed Sep 17 12:56:36 2008 Subject: [xtpl-svn] SF.net SVN: xtpl:[27] trunk Message-ID: Revision: 27 http://xtpl.svn.sourceforge.net/xtpl/?rev=27&view=rev Author: cocomp Date: 2008-09-17 11:54:17 +0000 (Wed, 17 Sep 2008) Log Message: ----------- Big changes to tag callback regex - much improved, change in constructor - now pass in an array of options, assign objects as well as arrays, _pre_var_dump made protected instead of private, new protected method _ob_var_dump Modified Paths: -------------- trunk/changelog.txt trunk/ex9.xtpl trunk/xtemplate.class.php Modified: trunk/changelog.txt =================================================================== --- trunk/changelog.txt 2007-08-16 22:31:06 UTC (rev 26) +++ trunk/changelog.txt 2008-09-17 11:54:17 UTC (rev 27) @@ -6,6 +6,13 @@ * For latest log information always use svn log in a checked out working copy * or svn log https://xtpl.svn.sourceforge.net/svnroot/xtpl/trunk/ for direct access * + * r27 | cocomp | 2008-09-17 12:54:00 +0100 (Wed, 17 Sep 2008) | 6 lines + * Big changes to tag callback regex - much improved + * change in constructor - now pass in an array of options + * assign objects as well as arrays + * _pre_var_dump made protected instead of private + * new protected method _ob_var_dump + * * r26 | cocomp | 2007-08-16 23:30:00 +0100 (Thu, 16 Aug 2007) | 4 lines * Added XTemplate::force_globals parameter to force existance of $_SERVER, * $_ENV and $_REQUEST if PHP 5 auto_globals_jit directive is in effect Modified: trunk/ex9.xtpl =================================================================== --- trunk/ex9.xtpl 2007-08-16 22:31:06 UTC (rev 26) +++ trunk/ex9.xtpl 2008-09-17 11:54:17 UTC (rev 27) @@ -28,7 +28,7 @@
  • Chained callbacks {example2|strtolower|ucwords}: {{example2|strtolower|ucwords}}
  • Multi-parameter callback {example3|str_replace('Some', 'Replaced', %s)}: {{example3|str_replace('Some', 'Replaced', %s)}}
  • Subclassed method callback {example4|my_custom_callback}: {{example4|my_custom_callback}}
  • -
  • Taking things to the limit {example5|strtolower|ucwords|str_replace('Some', 'Replaced', %s)|my_custom_callback}: {{example5|strtolower|ucwords|str_replace('Some', 'Replaced', %s)|my_custom_callback}}
  • +
  • Taking things to the limit {example5|strtolower|ucwords|str_replace(Some, 'My, Rep, la\'ced Test', "%s")|my_custom_callback}: {{example5|strtolower|ucwords|str_replace(Some, 'My, Rep, la\'ced Test', "%s")|my_custom_callback# It took some real effort to get the regular expression for this working!}}
  • Any methods you create in a sub-class of XTemplate (e.g. my_class extends XTemplate) are automatically available as a callback and will take priority over function calls of the same name.
    Modified: trunk/xtemplate.class.php =================================================================== --- trunk/xtemplate.class.php 2007-08-16 22:31:06 UTC (rev 26) +++ trunk/xtemplate.class.php 2008-09-17 11:54:17 UTC (rev 27) @@ -268,11 +268,13 @@ * @example {tagname|my_callback_func(true, %s)} - tagname contents passed at %s point * @example {tagname|my_callback_func} - tagname contents passed as single argument * @example {tagname|first_callback|second_callback('#value', true, %s)|third_callback #Comment} + * @example If you want quotes within your quoted strings, you'll need to escape them with \ + * @example {tagname|callback('I hope this won\'t break') * * @access public * @var string */ - public $callback_preg = '(\|[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\(.*?%s.*?\))?)*'; + public $callback_preg = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\(.*?\))?'; /** * Whether to enable callback feature or not @@ -295,8 +297,9 @@ // Simple string modifiers 'strtoupper', 'strtolower', 'ucwords', 'ucfirst', 'strrev', 'str_word_count', 'strlen', // String replacement modifiers - 'str_replace', 'str_ireplace', 'preg_replace', 'strip_tags', 'stripcslashes', 'stripslashes', + 'str_replace', 'str_ireplace', 'preg_replace', 'strip_tags', 'stripcslashes', 'stripslashes', 'substr', 'str_pad', 'str_repeat', 'strtr', 'trim', 'ltrim', 'rtrim', 'nl2br', 'wordwrap', 'printf', 'sprintf', + 'addslashes', 'addcslashes', // Encoding / decoding modifiers 'htmlentities', 'html_entity_decode', 'htmlspecialchars', 'htmlspecialchars_decode', 'urlencode', 'urldecode', @@ -385,16 +388,30 @@ /** * PHP 5 Constructor - Instantiate the object * - * @param string $file Template file to work on + * @param array $options Options array (was $file) * @param string/array $tpldir Location of template files (useful for keeping files outside web server root) * @param array $files Filenames lookup * @param string $mainblock Name of main block in the template * @param boolean $autosetup If true, run setup() as part of constuctor * @return XTemplate */ - public function __construct($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) { + public function __construct($options, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) { - $this->restart($file, $tpldir, $files, $mainblock, $autosetup, $this->tag_start_delim, $this->tag_end_delim); + /** + * Support deprecated multi-param constructor behaviour + */ + if (!is_array($options)) { + $options = array('file' => $options, 'path' => $tpldir, 'files' => $files, 'mainblock' => $mainblock, 'autosetup' => $autosetup); + } + + if (!isset($options['tag_start'])) { + $options['tag_start'] = $this->tag_start_delim; + } + if (!isset($options['tag_end'])) { + $options['tag_end'] = $this->tag_end_delim; + } + + $this->restart($options); } @@ -420,26 +437,72 @@ * @param string $tag_start { * @param string $tag_end } */ - public function restart ($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true, $tag_start = '{', $tag_end = '}') { + public function restart ($options, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true, $tag_start = '{', $tag_end = '}') { - $this->filename = $file; + /** + * Encourage an options array to be passed as the first parameter + * + * Deprecate the massive list of parameters + */ + if (is_array($options)) { + foreach ($options as $option => $value) { + switch ($option) { + case 'path': + case 'tpldir': + $tpldir = $value; + break; + case 'callbacks': + $this->allow_callbacks = true; + $this->allowed_callbacks = array_merge($this->allowed_callbacks, (array) $value); + break; + + case 'debug': + $this->debug = $value; + break; + + case 'file': + case 'files': + case 'mainblock': + case 'autosetup': + case 'tag_start': + case 'tag_end': + $$option = $value; + break; + } + } + + $this->filename = $file; + + } else { + $this->filename = $options; + } + // From SF Feature request 1202027 // Kenneth Kalmer - $this->tpldir = $tpldir; + if (isset($tpldir)) { + $this->tpldir = $tpldir; + } if (defined('XTPL_DIR') && empty($this->tpldir)) { $this->tpldir = XTPL_DIR; } - if (is_array($files)) { + if (isset($files) && is_array($files)) { $this->files = $files; } - $this->mainblock = $mainblock; + if (isset($mainblock)) { + $this->mainblock = $mainblock; + } - $this->tag_start_delim = $tag_start; - $this->tag_end_delim = $tag_end; + if (isset($tag_start)) { + $this->tag_start_delim = $tag_start; + } + if (isset($tag_end)) { + $this->tag_end_delim = $tag_end; + } + // Start with fresh file contents $this->filecontents = ''; @@ -454,7 +517,17 @@ $this->filevar_parent = array(); $this->filecache = array(); - if ($autosetup) { + if ($this->allow_callbacks) { + $delim = preg_quote($this->callback_delim); + if (strlen($this->callback_delim) < strlen($delim)) { + // Quote our quotes + $delim = preg_quote($delim); + } + + $this->callback_preg = preg_replace($this->preg_delimiter . '^\(' . $delim . '(.*)\)\*$' . $this->preg_delimiter, '\\1', $this->callback_preg); + } + + if (!isset($autosetup) || $autosetup) { $this->setup(); } } @@ -481,6 +554,9 @@ // regexp for file includes w/ newlines $this->filevar_delim_nl = $this->preg_delimiter . "^\s*" . $this->tag_start_delim . "FILE\s*" . $this->tag_start_delim . "([A-Za-z0-9\._\x7f-\xff]+?)" . $this->comment_preg . $this->tag_end_delim . $this->comment_preg . $this->tag_end_delim . "\s*\n" . $this->preg_delimiter . 'm'; + // regexp for tag callback matching + $this->callback_preg = '(' . preg_quote($this->callback_delim) . $this->callback_preg . ')*'; + if (empty($this->filecontents)) { // read in template file $this->filecontents = $this->_r_getfile($this->filename); @@ -518,19 +594,25 @@ * @example {name.key} {name.key2} {name.key3} in template * * @access public - * @param string $name Variable to assign $val to - * @param string / array $val Value to assign to $name + * @param string / array / object $name Variable to assign $val to + * @param string / array / object $val Value to assign to $name * @param boolean $reset_array Reset the variable array if $val is an array */ public function assign ($name, $val = '', $reset_array = true) { - if (is_array($name)) { + /** + * Allow assigning with objects as well as arrays + * + * @author JRCoates + * @since 04/09/2008 + */ + if (is_array($name) || is_object($name)) { foreach ($name as $k => $v) { $this->vars[$k] = $v; } - } elseif (is_array($val)) { + } elseif (is_array($val) || is_object($val)) { // Clear the existing values if ($reset_array) { @@ -616,6 +698,8 @@ $var_array = $var_array[1]; foreach ($var_array as $k => $v) { + // Use in regexes later + $orig_v = $v; // Are there any comments in the tags {tag#a comment for documenting the template} $comment = ''; @@ -700,39 +784,44 @@ // NW 4 Oct 2002 - Added isset and is_array check to avoid NOTICE messages // JC 17 Oct 2002 - Changed EMPTY to strlen=0 // if (empty($var[$v1])) { // this line would think that zeros(0) were empty - which is not true - if (!isset($var[$v1]) || (!is_array($var[$v1]) && strlen($var[$v1]) == 0)) { + /** + * Allow assigning with objects as well as arrays + * + * @author JRCoates + * @since 04/09/2008 + */ + switch (true) { + case is_array($var): + if (!isset($var[$v1]) || (is_string($var[$v1]) && strlen($var[$v1]) == 0)) { - // Check for constant, when variable not assigned - if (defined($v1)) { + // Check for constant, when variable not assigned + if (defined($v1)) { - $var[$v1] = constant($v1); + $var[$v1] = constant($v1); - } else { + } else { - $var[$v1] = null; - } - } + $var[$v1] = null; + } + } + $var = $var[$v1]; + break; - $var = $var[$v1]; - } + case is_object($var): + if (!isset($var->$v1) || (is_string($var->$v1) && strlen($var->$v1) == 0)) { + // Check for constant, when variable not assigned + if (defined($v1)) { - $nul = (!isset($this->_null_string[$v])) ? ($this->_null_string[""]) : ($this->_null_string[$v]); - $var = (!isset($var)) ? $nul : $var; + $var->$v1 = constant($v1); - if ($var === '') { - $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . $v . $this->callback_preg . $this->comment_preg . $this->tag_end_delim . $this->preg_delimiter . 'm', '', $copy); - } + } else { - // Prevent cast to strings when arrays passed in - if (is_string($var)) { - $var = trim($var); - // SF Bug no. 810773 - thanks anonymous - $var = str_replace('\\', '\\\\', $var); - // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04 - $var = str_replace('$', '\\$', $var); - // Replace str_replaces with preg_quote - //$var = preg_quote($var); - $var = str_replace('\\|', '|', $var); + $var->$v1 = null; + } + } + $var = $var->$v1; + break; + } } /** @@ -742,51 +831,90 @@ * @author JRCoates (cocomp) * @since 03/08/2007 */ - if (is_array($callback_funcs) && !empty($callback_funcs)) { - foreach ($callback_funcs as $callback) { - // See if we've got parameters being used e.g. |str_replace('A', 'B', %s) - if (preg_match($this->preg_delimiter . '\((.*%s.*)\)' . $this->preg_delimiter, $callback, $matches)) { - $parameters = explode(',', $matches[1]); - if (count($parameters) > 1) { - array_walk($parameters, array($this, 'trim_callback')); - $key = array_search('%s', $parameters); - $parameters[$key] = $var; - } else { - unset($parameters); + if ($this->allow_callbacks) { + if (is_array($callback_funcs) && !empty($callback_funcs)) { + foreach ($callback_funcs as $callback) { + // See if we've got parameters being used e.g. |str_replace('A', 'B', %s) + if (preg_match($this->preg_delimiter . '\((.*?)\)' . $this->preg_delimiter, $callback, $matches)) { + $parameters = array(); + /** + * Zero width assertion positive look behind (?<=a)x + * Zero width assertion negative look behind (?preg_delimiter . '(?# + match optional comma, optional other stuff, then + apostrophes / quotes then stuff followed by comma or + closing bracket negative look behind for an apostrophe + or quote not preceeded by an escaping back slash + )[,?\s*?]?[\'|"](.*?)(?preg_delimiter, $matches[1] . ')', $param_matches)) { + $parameters = $param_matches[0]; + } + + if (count($parameters)) { + array_walk($parameters, array($this, 'trim_callback')); + if (($key = array_search('%s', $parameters)) !== false) { + $parameters[$key] = $var; + } else { + array_unshift($parameters, $var); + } + } else { + unset($parameters); + } } - } - // Remove the parameters - $callback = preg_replace($this->preg_delimiter . '\(.*\)' . $this->preg_delimiter, '', $callback); + // Remove the parameters + $callback = preg_replace($this->preg_delimiter . '\(.*?\)' . $this->preg_delimiter, '', $callback); - // Allow callback of methods in a sub-class of XTemplate - // e.g. you must my_class extends XTemplate {} if you want to use this feature - if (is_subclass_of($this, 'XTemplate') && method_exists($this, $callback) && is_callable(array($this, $callback))) { - if (isset($parameters)) { - $var = call_user_func_array(array($this, $callback), $parameters); - unset($parameters); - } else { - // Standard form e.g. {tag|callback} - $var = call_user_func(array($this, $callback), $var); + // Allow callback of methods in a sub-class of XTemplate + // e.g. you must my_class extends XTemplate {} if you want to use this feature + if (is_subclass_of($this, 'XTemplate') && method_exists($this, $callback) && is_callable(array($this, $callback))) { + if (isset($parameters)) { + $var = call_user_func_array(array($this, $callback), $parameters); + unset($parameters); + } else { + // Standard form e.g. {tag|callback} + $var = call_user_func(array($this, $callback), $var); + } + } elseif (in_array($callback, $this->allowed_callbacks) && function_exists($callback) && is_callable($callback)) { + if (isset($parameters)) { + $var = call_user_func_array($callback, $parameters); + unset($parameters); + } else { + // Standard form e.g. {tag|callback} + $var = call_user_func($callback, $var); + } } - } elseif (in_array($callback, $this->allowed_callbacks) && function_exists($callback) && is_callable($callback)) { - if (isset($parameters)) { - $var = call_user_func_array($callback, $parameters); - unset($parameters); - } else { - // Standard form e.g. {tag|callback} - $var = call_user_func($callback, $var); - } } } + } - $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . $v . preg_quote($this->callback_delim . implode($this->callback_delim, $callback_funcs)) . '( )?(' . $this->comment_delim . ')?' . $comment . $this->tag_end_delim . $this->preg_delimiter . 'm', "$var", $copy); + $nul = (!isset($this->_null_string[$v])) ? ($this->_null_string[""]) : ($this->_null_string[$v]); + $var = (!isset($var)) ? $nul : $var; - } else { - - $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . $v . '( )?(' . $this->comment_delim . ')?' . $comment . $this->tag_end_delim . $this->preg_delimiter . 'm', "$var", $copy); + // Prevent cast to strings when arrays passed in + if (is_string($var)) { + if ($var === '') { + $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . preg_quote($orig_v) . $this->tag_end_delim . $this->preg_delimiter . 'm', '', $copy); + } else { + //$var = trim($var); + // SF Bug no. 810773 - thanks anonymous + $var = str_replace('\\', '\\\\', $var); + // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04 + $var = str_replace('$', '\\$', $var); + // Replace str_replaces with preg_quote + //$var = preg_quote($var); + $var = str_replace('\\|', '|', $var); + } } + $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . preg_quote($orig_v) . $this->tag_end_delim . $this->preg_delimiter . 'm', "$var", $copy); + if (preg_match($this->preg_delimiter . "^\n" . $this->preg_delimiter, $copy) && preg_match($this->preg_delimiter . "\n$" . $this->preg_delimiter, $copy)) { $copy = substr($copy, 1); } @@ -1450,7 +1578,12 @@ * @param string $value */ protected function trim_callback (&$value) { - $value = preg_replace($this->preg_delimiter . "^['|\"](.*)['|\"]$" . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . "^.*(%s).*$" . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . '^,?\s*?(.*?)[,|\)]?$' . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . '^[\'|"]?(.*?)[\'|"]?$' . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . '\\\\(?=\'|")' . $this->preg_delimiter, '', $value); + // Deal with escaped commas (beta) + $value = preg_replace($this->preg_delimiter . '\\\,' . $this->preg_delimiter, ',', $value); } /** @@ -1469,10 +1602,10 @@ /** * Debug function - var_dump wrapped in '

    ' tags
          *
    -     * @access private
    +     * @access protected
          * @param multiple var_dumps all the supplied arguments
          */
    -	private function _pre_var_dump ($args) {
    +	protected function _pre_var_dump ($args) {
     
     		if ($this->debug) {
     			echo '
    ';
    @@ -1480,6 +1613,21 @@
     			echo '
    '; } } + + /** + * Debug function - var_dump and return + * + * @access protected + * @param multiple var_dumps all the supplied arguments + */ + protected function _ob_var_dump ($args) { + + if ($this->debug) { + ob_start(); + $this->_pre_var_dump(func_get_args()); + return ob_get_clean(); + } + } } /* end of XTemplate class. */ ?> \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. From cocomp at users.sourceforge.net Wed Sep 17 13:08:39 2008 From: cocomp at users.sourceforge.net (cocomp@users.sourceforge.net) Date: Wed Sep 17 13:11:32 2008 Subject: [xtpl-svn] SF.net SVN: xtpl:[28] branches/php4 Message-ID: Revision: 28 http://xtpl.svn.sourceforge.net/xtpl/?rev=28&view=rev Author: cocomp Date: 2008-09-17 12:08:38 +0000 (Wed, 17 Sep 2008) Log Message: ----------- Backport from PHP 5 version changes, Big changes to tag callback regex - much improved, change in constructor - now pass in an array of options, assign objects as well as arrays, _pre_var_dump made protected instead of private, new protected method _ob_var_dump Modified Paths: -------------- branches/php4/changelog.txt branches/php4/ex8.xtpl branches/php4/ex9.xtpl branches/php4/xtemplate.class.php Modified: branches/php4/changelog.txt =================================================================== --- branches/php4/changelog.txt 2008-09-17 11:54:17 UTC (rev 27) +++ branches/php4/changelog.txt 2008-09-17 12:08:38 UTC (rev 28) @@ -6,6 +6,14 @@ * For latest log information always use svn log in a checked out working copy * or svn log https://xtpl.svn.sourceforge.net/svnroot/xtpl/trunk/ for direct access * + * r28 | cocomp | 2008-09-17 13:08:00 +0100 (Wed, 17 Sep 2008) | 7 lines + * Backport from PHP 5 version changes - untested, need feedback in the forums + * Big changes to tag callback regex - much improved + * change in constructor - now pass in an array of options + * assign objects as well as arrays + * _pre_var_dump made protected instead of private + * new protected method _ob_var_dump + * * r26 | cocomp | 2007-08-16 23:30:00 +0100 (Thu, 16 Aug 2007) | 2 lines * Stopped recursion of $GLOBALS in scan_globals method * Modified: branches/php4/ex8.xtpl =================================================================== --- branches/php4/ex8.xtpl 2008-09-17 11:54:17 UTC (rev 27) +++ branches/php4/ex8.xtpl 2008-09-17 12:08:38 UTC (rev 28) @@ -1,13 +1,16 @@ - + + + XTemplate Table Row Example +

    This example shows a method for producing tables with correct blank cells if there are incomplete rows

    Modified: branches/php4/ex9.xtpl =================================================================== --- branches/php4/ex9.xtpl 2008-09-17 11:54:17 UTC (rev 27) +++ branches/php4/ex9.xtpl 2008-09-17 12:08:38 UTC (rev 28) @@ -28,7 +28,7 @@
  • Chained callbacks {example2|strtolower|ucwords}: {{example2|strtolower|ucwords}}
  • Multi-parameter callback {example3|str_replace('Some', 'Replaced', %s)}: {{example3|str_replace('Some', 'Replaced', %s)}}
  • Subclassed method callback {example4|my_custom_callback}: {{example4|my_custom_callback}}
  • -
  • Taking things to the limit {example5|strtolower|ucwords|str_replace('Some', 'Replaced', %s)|my_custom_callback}: {{example5|strtolower|ucwords|str_replace('Some', 'Replaced', %s)|my_custom_callback}}
  • +
  • Taking things to the limit {example5|strtolower|ucwords|str_replace(Some, 'My, Rep, la\'ced Test', "%s")|my_custom_callback}: {{example5|strtolower|ucwords|str_replace(Some, 'My, Rep, la\'ced Test', "%s")|my_custom_callback# It took some real effort to get the regular expression for this working!}}
  • Any methods you create in a sub-class of XTemplate (e.g. my_class extends XTemplate) are automatically available as a callback and will take priority over function calls of the same name.
    Modified: branches/php4/xtemplate.class.php =================================================================== --- branches/php4/xtemplate.class.php 2008-09-17 11:54:17 UTC (rev 27) +++ branches/php4/xtemplate.class.php 2008-09-17 12:08:38 UTC (rev 28) @@ -268,11 +268,13 @@ * @example {tagname|my_callback_func(true, %s)} - tagname contents passed at %s point * @example {tagname|my_callback_func} - tagname contents passed as single argument * @example {tagname|first_callback|second_callback('#value', true, %s)|third_callback #Comment} + * @example If you want quotes within your quoted strings, you'll need to escape them with \ + * @example {tagname|callback('I hope this won\'t break') * * @access public * @var string */ - var $callback_preg = '(\|[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\(.*?%s.*?\))?)*'; + var $callback_preg = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\(.*?\))?'; /** * Whether to enable callback feature or not @@ -295,8 +297,9 @@ // Simple string modifiers 'strtoupper', 'strtolower', 'ucwords', 'ucfirst', 'strrev', 'str_word_count', 'strlen', // String replacement modifiers - 'str_replace', 'str_ireplace', 'preg_replace', 'strip_tags', 'stripcslashes', 'stripslashes', + 'str_replace', 'str_ireplace', 'preg_replace', 'strip_tags', 'stripcslashes', 'stripslashes', 'substr', 'str_pad', 'str_repeat', 'strtr', 'trim', 'ltrim', 'rtrim', 'nl2br', 'wordwrap', 'printf', 'sprintf', + 'addslashes', 'addcslashes', // Encoding / decoding modifiers 'htmlentities', 'html_entity_decode', 'htmlspecialchars', 'htmlspecialchars_decode', 'urlencode', 'urldecode', @@ -325,6 +328,14 @@ var $output_type = 'HTML'; /** + * Force all globals to be available in scan_globals + * if PHP auto_globals_jit config is on (& working!) + * + * @var boolean + */ + var $force_globals = true; + + /** * Debug mode * * @access public @@ -375,6 +386,35 @@ var $_ignore_missing_blocks = true; /** + * PHP 5 Constructor - Instantiate the object + * + * @param array $options Options array (was $file) + * @param string/array $tpldir Location of template files (useful for keeping files outside web server root) + * @param array $files Filenames lookup + * @param string $mainblock Name of main block in the template + * @param boolean $autosetup If true, run setup() as part of constuctor + * @return XTemplate + */ + function __construct($options, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) { + + /** + * Support deprecated multi-param constructor behaviour + */ + if (!is_array($options)) { + $options = array('file' => $options, 'path' => $tpldir, 'files' => $files, 'mainblock' => $mainblock, 'autosetup' => $autosetup); + } + + if (!isset($options['tag_start'])) { + $options['tag_start'] = $this->tag_start_delim; + } + if (!isset($options['tag_end'])) { + $options['tag_end'] = $this->tag_end_delim; + } + + $this->restart($options); + } + + /** * Constructor - Instantiate the object * * @param string $file Template file to work on @@ -384,9 +424,23 @@ * @param boolean $autosetup If true, run setup() as part of constuctor * @return XTemplate */ - function XTemplate ($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) { + function XTemplate ($options, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true) { - $this->restart($file, $tpldir, $files, $mainblock, $autosetup, $this->tag_start_delim, $this->tag_end_delim); + /** + * Support deprecated multi-param constructor behaviour + */ + if (!is_array($options)) { + $options = array('file' => $options, 'path' => $tpldir, 'files' => $files, 'mainblock' => $mainblock, 'autosetup' => $autosetup); + } + + if (!isset($options['tag_start'])) { + $options['tag_start'] = $this->tag_start_delim; + } + if (!isset($options['tag_end'])) { + $options['tag_end'] = $this->tag_end_delim; + } + + $this->restart($options, $tpldir, $files, $mainblock, $autosetup, $this->tag_start_delim, $this->tag_end_delim); } @@ -412,26 +466,72 @@ * @param string $tag_start { * @param string $tag_end } */ - function restart ($file, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true, $tag_start = '{', $tag_end = '}') { + function restart ($options, $tpldir = '', $files = null, $mainblock = 'main', $autosetup = true, $tag_start = '{', $tag_end = '}') { - $this->filename = $file; + /** + * Encourage an options array to be passed as the first parameter + * + * Deprecate the massive list of parameters + */ + if (is_array($options)) { + foreach ($options as $option => $value) { + switch ($option) { + case 'path': + case 'tpldir': + $tpldir = $value; + break; + case 'callbacks': + $this->allow_callbacks = true; + $this->allowed_callbacks = array_merge($this->allowed_callbacks, (array) $value); + break; + + case 'debug': + $this->debug = $value; + break; + + case 'file': + case 'files': + case 'mainblock': + case 'autosetup': + case 'tag_start': + case 'tag_end': + $$option = $value; + break; + } + } + + $this->filename = $file; + + } else { + $this->filename = $options; + } + // From SF Feature request 1202027 // Kenneth Kalmer - $this->tpldir = $tpldir; + if (isset($tpldir)) { + $this->tpldir = $tpldir; + } if (defined('XTPL_DIR') && empty($this->tpldir)) { $this->tpldir = XTPL_DIR; } - if (is_array($files)) { + if (isset($files) && is_array($files)) { $this->files = $files; } - $this->mainblock = $mainblock; + if (isset($mainblock)) { + $this->mainblock = $mainblock; + } - $this->tag_start_delim = $tag_start; - $this->tag_end_delim = $tag_end; + if (isset($tag_start)) { + $this->tag_start_delim = $tag_start; + } + if (isset($tag_end)) { + $this->tag_end_delim = $tag_end; + } + // Start with fresh file contents $this->filecontents = ''; @@ -446,7 +546,17 @@ $this->filevar_parent = array(); $this->filecache = array(); - if ($autosetup) { + if ($this->allow_callbacks) { + $delim = preg_quote($this->callback_delim); + if (strlen($this->callback_delim) < strlen($delim)) { + // Quote our quotes + $delim = preg_quote($delim); + } + + $this->callback_preg = preg_replace($this->preg_delimiter . '^\(' . $delim . '(.*)\)\*$' . $this->preg_delimiter, '\\1', $this->callback_preg); + } + + if (!isset($autosetup) || $autosetup) { $this->setup(); } } @@ -473,6 +583,9 @@ // regexp for file includes w/ newlines $this->filevar_delim_nl = $this->preg_delimiter . "^\s*" . $this->tag_start_delim . "FILE\s*" . $this->tag_start_delim . "([A-Za-z0-9\._\x7f-\xff]+?)" . $this->comment_preg . $this->tag_end_delim . $this->comment_preg . $this->tag_end_delim . "\s*\n" . $this->preg_delimiter . 'm'; + // regexp for tag callback matching + $this->callback_preg = '(' . preg_quote($this->callback_delim) . $this->callback_preg . ')*'; + if (empty($this->filecontents)) { // read in template file $this->filecontents = $this->_r_getfile($this->filename); @@ -510,19 +623,25 @@ * @example {name.key} {name.key2} {name.key3} in template * * @access public - * @param string $name Variable to assign $val to - * @param string / array $val Value to assign to $name + * @param string / array / object $name Variable to assign $val to + * @param string / array / object $val Value to assign to $name * @param boolean $reset_array Reset the variable array if $val is an array */ function assign ($name, $val = '', $reset_array = true) { - if (is_array($name)) { + /** + * Allow assigning with objects as well as arrays + * + * @author JRCoates + * @since 04/09/2008 + */ + if (is_array($name) || is_object($name)) { foreach ($name as $k => $v) { $this->vars[$k] = $v; } - } elseif (is_array($val)) { + } elseif (is_array($val) || is_object($val)) { // Clear the existing values if ($reset_array) { @@ -608,6 +727,8 @@ $var_array = $var_array[1]; foreach ($var_array as $k => $v) { + // Use in regexes later + $orig_v = $v; // Are there any comments in the tags {tag#a comment for documenting the template} $comment = ''; @@ -690,41 +811,46 @@ foreach ($sub as $v1) { // NW 4 Oct 2002 - Added isset and is_array check to avoid NOTICE messages - // JC 17 Oct 2002 - Changed EMPTY to stlen=0 + // JC 17 Oct 2002 - Changed EMPTY to strlen=0 // if (empty($var[$v1])) { // this line would think that zeros(0) were empty - which is not true - if (!isset($var[$v1]) || (!is_array($var[$v1]) && strlen($var[$v1]) == 0)) { + /** + * Allow assigning with objects as well as arrays + * + * @author JRCoates + * @since 04/09/2008 + */ + switch (true) { + case is_array($var): + if (!isset($var[$v1]) || (is_string($var[$v1]) && strlen($var[$v1]) == 0)) { - // Check for constant, when variable not assigned - if (defined($v1)) { + // Check for constant, when variable not assigned + if (defined($v1)) { - $var[$v1] = constant($v1); + $var[$v1] = constant($v1); - } else { + } else { - $var[$v1] = null; - } - } + $var[$v1] = null; + } + } + $var = $var[$v1]; + break; - $var = $var[$v1]; - } + case is_object($var): + if (!isset($var->$v1) || (is_string($var->$v1) && strlen($var->$v1) == 0)) { + // Check for constant, when variable not assigned + if (defined($v1)) { - $nul = (!isset($this->_null_string[$v])) ? ($this->_null_string[""]) : ($this->_null_string[$v]); - $var = (!isset($var)) ? $nul : $var; + $var->$v1 = constant($v1); - if ($var === '') { - $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . $v . $this->callback_preg . $this->comment_preg . $this->tag_end_delim . $this->preg_delimiter . 'm', '', $copy); - } + } else { - // Prevent cast to strings when arrays passed in - if (is_string($var)) { - $var = trim($var); - // SF Bug no. 810773 - thanks anonymous - $var = str_replace('\\', '\\\\', $var); - // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04 - $var = str_replace('$', '\\$', $var); - // Replace str_replaces with preg_quote - //$var = preg_quote($var); - $var = str_replace('\\|', '|', $var); + $var->$v1 = null; + } + } + $var = $var->$v1; + break; + } } /** @@ -734,51 +860,90 @@ * @author JRCoates (cocomp) * @since 03/08/2007 */ - if (is_array($callback_funcs) && !empty($callback_funcs)) { - foreach ($callback_funcs as $callback) { - // See if we've got parameters being used e.g. |str_replace('A', 'B', %s) - if (preg_match($this->preg_delimiter . '\((.*%s.*)\)' . $this->preg_delimiter, $callback, $matches)) { - $parameters = explode(',', $matches[1]); - if (count($parameters) > 1) { - array_walk($parameters, array($this, 'trim_callback')); - $key = array_search('%s', $parameters); - $parameters[$key] = $var; - } else { - unset($parameters); + if ($this->allow_callbacks) { + if (is_array($callback_funcs) && !empty($callback_funcs)) { + foreach ($callback_funcs as $callback) { + // See if we've got parameters being used e.g. |str_replace('A', 'B', %s) + if (preg_match($this->preg_delimiter . '\((.*?)\)' . $this->preg_delimiter, $callback, $matches)) { + $parameters = array(); + /** + * Zero width assertion positive look behind (?<=a)x + * Zero width assertion negative look behind (?preg_delimiter . '(?# + match optional comma, optional other stuff, then + apostrophes / quotes then stuff followed by comma or + closing bracket negative look behind for an apostrophe + or quote not preceeded by an escaping back slash + )[,?\s*?]?[\'|"](.*?)(?preg_delimiter, $matches[1] . ')', $param_matches)) { + $parameters = $param_matches[0]; + } + + if (count($parameters)) { + array_walk($parameters, array($this, 'trim_callback')); + if (($key = array_search('%s', $parameters)) !== false) { + $parameters[$key] = $var; + } else { + array_unshift($parameters, $var); + } + } else { + unset($parameters); + } } - } - // Remove the parameters - $callback = preg_replace($this->preg_delimiter . '\(.*\)' . $this->preg_delimiter, '', $callback); + // Remove the parameters + $callback = preg_replace($this->preg_delimiter . '\(.*?\)' . $this->preg_delimiter, '', $callback); - // Allow callback of methods in a sub-class of XTemplate - // e.g. you must my_class extends XTemplate {} if you want to use this feature - if (is_subclass_of($this, 'XTemplate') && method_exists($this, $callback) && is_callable(array($this, $callback))) { - if (isset($parameters)) { - $var = call_user_func_array(array($this, $callback), $parameters); - unset($parameters); - } else { - // Standard form e.g. {tag|callback} - $var = call_user_func(array($this, $callback), $var); + // Allow callback of methods in a sub-class of XTemplate + // e.g. you must my_class extends XTemplate {} if you want to use this feature + if (is_subclass_of($this, 'XTemplate') && method_exists($this, $callback) && is_callable(array($this, $callback))) { + if (isset($parameters)) { + $var = call_user_func_array(array($this, $callback), $parameters); + unset($parameters); + } else { + // Standard form e.g. {tag|callback} + $var = call_user_func(array($this, $callback), $var); + } + } elseif (in_array($callback, $this->allowed_callbacks) && function_exists($callback) && is_callable($callback)) { + if (isset($parameters)) { + $var = call_user_func_array($callback, $parameters); + unset($parameters); + } else { + // Standard form e.g. {tag|callback} + $var = call_user_func($callback, $var); + } } - } elseif (in_array($callback, $this->allowed_callbacks) && function_exists($callback) && is_callable($callback)) { - if (isset($parameters)) { - $var = call_user_func_array($callback, $parameters); - unset($parameters); - } else { - // Standard form e.g. {tag|callback} - $var = call_user_func($callback, $var); - } } } + } - $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . $v . preg_quote($this->callback_delim . implode($this->callback_delim, $callback_funcs)) . '( )?(' . $this->comment_delim . ')?' . $comment . $this->tag_end_delim . $this->preg_delimiter . 'm', "$var", $copy); + $nul = (!isset($this->_null_string[$v])) ? ($this->_null_string[""]) : ($this->_null_string[$v]); + $var = (!isset($var)) ? $nul : $var; - } else { - - $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . $v . '( )?(' . $this->comment_delim . ')?' . $comment . $this->tag_end_delim . $this->preg_delimiter . 'm', "$var", $copy); + // Prevent cast to strings when arrays passed in + if (is_string($var)) { + if ($var === '') { + $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . preg_quote($orig_v) . $this->tag_end_delim . $this->preg_delimiter . 'm', '', $copy); + } else { + //$var = trim($var); + // SF Bug no. 810773 - thanks anonymous + $var = str_replace('\\', '\\\\', $var); + // Ensure dollars in strings are not evaluated reported by SadGeezer 31/3/04 + $var = str_replace('$', '\\$', $var); + // Replace str_replaces with preg_quote + //$var = preg_quote($var); + $var = str_replace('\\|', '|', $var); + } } + $copy = preg_replace($this->preg_delimiter . $this->tag_start_delim . preg_quote($orig_v) . $this->tag_end_delim . $this->preg_delimiter . 'm', "$var", $copy); + if (preg_match($this->preg_delimiter . "^\n" . $this->preg_delimiter, $copy) && preg_match($this->preg_delimiter . "\n$" . $this->preg_delimiter, $copy)) { $copy = substr($copy, 1); } @@ -1037,6 +1202,13 @@ $GLOB = array(); + if ($this->force_globals && ini_get('auto_globals_jit') == true) { + $tmp = $_SERVER; + $tmp = $_ENV; + $tmp = $_REQUEST; + unset($tmp); + } + foreach ($GLOBALS as $k => $v) { $GLOB[$k] = array(); @@ -1435,7 +1607,12 @@ * @param string $value */ function trim_callback (&$value) { - $value = preg_replace($this->preg_delimiter . "^['|\"](.*)['|\"]$" . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . "^.*(%s).*$" . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . '^,?\s*?(.*?)[,|\)]?$' . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . '^[\'|"]?(.*?)[\'|"]?$' . $this->preg_delimiter, '\\1', trim($value)); + $value = preg_replace($this->preg_delimiter . '\\\\(?=\'|")' . $this->preg_delimiter, '', $value); + // Deal with escaped commas (beta) + $value = preg_replace($this->preg_delimiter . '\\\,' . $this->preg_delimiter, ',', $value); } /** @@ -1454,7 +1631,7 @@ /** * Debug function - var_dump wrapped in '

    ' tags
          *
    -     * @access private
    +     * @access protected
          * @param multiple var_dumps all the supplied arguments
          */
     	function _pre_var_dump ($args) {
    @@ -1465,6 +1642,21 @@
     			echo '';
     		}
     	}
    +	
    +	/**
    +	 * Debug function - var_dump and return
    +	 * 
    +	 * @access protected
    +	 * @param multiple var_dumps all the supplied arguments
    +	 */
    +	function _ob_var_dump ($args) {
    +		
    +		if ($this->debug) {
    +			ob_start();
    +			$this->_pre_var_dump(func_get_args());			
    +			return ob_get_clean();
    +		}
    +	}
     } /* end of XTemplate class. */
     
     ?>
    \ No newline at end of file
    
    
    This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.