| Current Path : /var/www/cesa.co.za/php/ |
| Current File : /var/www/cesa.co.za/php/template.php |
<?php
// BEGIN DEBUG
################################################################################
# PHP-HTML::Template #
# http://phphtmltemplate.sourceforge.net/ #
################################################################################
# A template system for PHP based on HTML::Template Perl Module #
# Version 0.3.3 #
# 14-NOV-2002 #
# See file README for details #
################################################################################
# Author: Juan R. Pozo, jrpozo@conclase.net #
# License: GNU GPL (included in file "LICENSE") #
# (c) 2002 by Juan R. Pozo #
# http://html.conclase.net/ #
# http://www.planhost.net/ #
################################################################################
# HTML::Template Perl module copyright by Sam Tregar #
################################################################################
# Please consider making a donation today. Visit my amazon.com wishlist at: #
# http://html.conclase.net/link/wishlist #
# Thank you :) #
################################################################################
/* ************** *
* TEMPLATE CLASS *
* ************** */
// END DEBUG
class Template {
// The array of options
var $options;
// The tags that need a NAME attribute
var $need_names = array(
"TMPL_VAR" => 1,
"TMPL_LOOP" => 1,
"TMPL_IF" => 1,
"TMPL_UNLESS" => 1,
"TMPL_INCLUDE" => 1
);
// Vars for Parse phase
var $template = NULL; // the template file in memory
var $nodes = array(); // the linearized parse tree
var $names = array(); // the names of the variables that this template needs
var $depth = 0; // the inclusion depth of this template
// Vars for AddParam phase
var $paramScope = array(); // enclosing scopes of variable value as we add parameters
var $param = NULL; // the variables values assigned by the user
// Vars for Output phase
var $output = NULL; // the output string
var $totalPass = array(); // Stack for loops: total passes of current loop
var $curPass = array(); // Stack for loops: current pass of current loop
var $version = "0.3.3";
// The class constructor
function Template($options)
{
// if the argument is a scalar, it is taken as the template file name
// otherwise we take it as an associative array of option=>value pairs
if (is_scalar($options)) {
$filename = $options;
unset($options);
$options = array('filename' => $filename);
} else if (!is_array($options)) {
trigger_error("Template->Template() : Trying to create template without providing a template filename or an options array", E_USER_ERROR);
}
if (isset($options['_parent'])) {
$this->nodes =& $options['_parent']->nodes;
$this->names =& $options['_parent']->names;
$this->depth = $options['_parent']->depth + 1;
}
$this->SetOptions($options);
// BEGIN DEBUG
// We can start showing debug information from now on
if (!isset($options['parent']) && $this->options['debug']) {
echo("<h1>Template::New()</h1>\n");
echo("<p>Debug mode on.</p>");
echo("<p>Thanks for using PHP-HTML::Template.</p>");
echo("<p>If you find any bugs, please report them to <a href='mailto:jrpozo@conclase.net'>jrpozo@conclase.net</a> along with an example and the version number of this library.</p>");
echo("<p>Current version number is " . $this->version . "</p>");
}
// END DEBUG
$filename = $this->options['filename'];
if (!is_readable($filename)) {
trigger_error("Template->Template() : Template file \"".$filename."\" not found", E_USER_ERROR);
}
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>Opening file ".$filename."</p>");
}
// END DEBUG
$f = fopen($filename, "r");
$this->template = fread($f, filesize($filename));
fclose($f);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>File closed. ".filesize($filename)." bytes read into memory.</p>");
}
// END DEBUG
if ($this->options['parse']) {
// BEGIN DEBUG
if (!isset($options['parent']) && $this->options['debug']) {
echo("<p>Going to parse template now... good luck!</p>");
}
// END DEBUG
$this->Parse();
$this->defScope[] =& $this->names;
$this->paramScope[] =& $this->param;
}
}
// Fill in the $options array
function SetOptions($options)
{
// We first set the default values for all options
$this->options = array(
"debug" => 0,
"die_on_bad_params" => 1,
"strict" => 1,
"loop_context_vars" => 0,
"max_includes" => 10,
"global_vars" => 0,
"no_includes" => 0,
"case_sensitive" => 0,
"hash_comments" => 0,
"parse" => 1,
"imark" => '<',
"emark" => '>',
"parse_html_comments" => 1,
"vanguard_compatibility_mode" => 0
);
// and then the values provided by the user override those values
foreach ($options as $key => $value) {
$this->options[strtolower($key)] = $value;
}
// vanguard compatibility mode enforces die_on_bad_params = 0
if ($this->options["vanguard_compatibility_mode"]) {
$this->options["die_on_bad_params"] = 0;
}
// initial and end tags cannot take the same value
if ($this->options["imark"] == $this->options["emark"]) {
// trigger_error("Template::SetOptions() - Error, imark and emark options cannot take the same values", E_USER_ERROR);
}
}
// This functions escapes regex metacharacters (outside square brackets)
function EscapePREGText($text)
{
return strtr($text, array('\\'=>'\\\\', '/'=>'\/', '^'=>'\^', '$'=>'\$', '.'=>'\.', '['=>'\[', ']'=>'\]', '|'=>'\|', '('=>'\(', ')'=>'\)', '?'=>'\?', '*'=>'\*', '+'=>'\+', '{'=>'\{', '}'=>'\}', '%'=>'\%'));
}
function Parse()
{
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<h1>Template::Parse()</h1>");
}
// END DEBUG
$lineNumber = 1; // we reset in case the template is parsed succesive times
// Stacks to keep track of where we are.
// These contain the number of the corresponding initial node
$inLoop = array();
$inIf = array();
$inUnless = array();
// This contains the type of the last opened node type
$curType = array();
// Handle the old vanguard format
if ($this->options['vanguard_compatibility_mode']) {
$expr = $this->options['imark']."TMPL_VAR NAME=\\1".$this->options['emark'];
$this->template = preg_replace("/%([-\w\/\.+]+)%/", $expr, $this->template);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Vanguard style tags converted to normal tags<br>\n");
}
// END DEBUG
}
// Strip hash comments (### comment)
if ($this->options['hash_comments']) {
$this->template = preg_replace("/### .*/", "", $this->template);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Hash comments stripped<br>\n");
}
// END DEBUG
}
// Now split up the template
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Splitting up template in chunks<br>\n");
}
// END DEBUG
// Two possible delimiters, depending on initial mark shape. Examples:
// [[ => <!--\s*[[ and [[
// <[ => <!--\s*[ and <[
// < => <!--\s* and <
$imark = $this->EscapePREGText($this->options['imark']);
$emark = $this->EscapePREGText($this->options['emark']);
$emark0 = $this->EscapePREGText($this->options['emark'][0]);
if ($this->options['parse_html_comments']) {
if ($this->options['imark'][0] == '<') {
$delim = "<(?:!--\\s*)?".substr($imark, 1);
} else {
$delim = "(?:<!--\\s*)?".$imark;
}
if ($this->options['emark'][0] == '>') {
$delim2 = substr($emark, 0, strlen($emark)-1)."(?:\s*--)?>";
} else {
$delim2 = $emark."(?:\s*-->)?";
}
} else {
$delim = $imark;
$delim2 = $emark;
}
// One Regex to rule them all
$regex = "/(" . $delim . "\/?[Tt][Mm][Pp][Ll]_\w+(?:(?:\s+(?:(?:\"[^\"]*\")|(?:\'[\']*\')|(?:[^=\s".$emark0."]+))(?:=(?:\"[^\"]*\"|\'[^\']*\'|(?:[^\s".$emark0."]*)))?)(?:\s+[^=\s]+(?:=(?:\"[^\"]+\"|\'[^\']\'|(?:[^\s".$emark0."]*)))?)*)?".$delim2.")(?m)/";
// One Regex to find them
$regex2 = "/" . $delim . "(\/?[Tt][Mm][Pp][Ll]_\w+)((?:\s+(?:(?:\"[^\"]*\")|(?:\'[\']*\')|(?:[^=\s".$emark0."]+))(?:=(?:\"[^\"]*\"|\'[^\']*\'|(?:[^\s".$emark0."]*)))?)(?:\s+[^=\s]+(?:=(?:\"[^\"]+\"|\'[^\']\'|(?:[^\s".$emark0."]*)))?)*)?".$delim2."(?m)/";
// One Regex to bring them all and in the darkness bind them
// In the Land of Mordor where the Shadows lie.
// Oh, never mind...
$chunks = preg_split($regex, $this->template, -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Template splitted, ".count($chunks)." chunks obtained<br>\n");
echo("<pre>");
foreach($chunks as $k=>$v) {
echo("<b>[$k]</b> ".htmlentities($v)."\n");
}
echo("</pre>");
}
// END DEBUG
// All done with template
unset ($this->template);
// Loop through chunks, filling up the linearized parse tree
for ($i = 0; $i < count($chunks); $i++) {
if (preg_match($regex2, $chunks[$i], $tag)) {
$which = strtoupper($tag[1]);
// This seems to be a template tag
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<hr>Template tag found: <code>".htmlentities($which)."</code><br>\n");
echo("<pre>tag\n");
print_r($tag);
echo("</pre>");
}
// END DEBUG
$var = array("name"=>NULL, "escape"=>NULL, "global"=>NULL, "default"=>NULL);
if (isset($tag[2])) {
$token = preg_split("/((?:[^\s=]+=)?(?:(?:\"[^\"]+\")|(?:\'[^\']+\')|(?:\S*)))/", trim($tag[2]), -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
foreach($token as $tok) {
if (preg_match("/=/", $tok)) {
$t = preg_split("/\s*=\s*/", $tok, 3);
preg_match("/(?:\"([^\"]*)\")|(?:\'([^\']*)\')|(\S*)/", $t[1], $match);
$var[strtolower($t[0])] = max(isset($match[1])?$match[1]:"", isset($match[2])?$match[2]:"", isset($match[3])?$match[3]:"");
} else if (!preg_match("/^\s*$/", $tok)) {
preg_match("/(?:\"([^\"]*)\")|(?:\'([^\']*)\')|(\S*)/", $tok, $match);
$var["name"] = max(isset($match[1])?$match[1]:"", isset($match[2])?$match[2]:"", isset($match[3])?$match[3]:"");
}
}
}
$name = $var["name"];
$escape = $var["escape"];
$global = $var["global"];
$default = $var["default"];
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<b>Found values:</b><br>\n");
echo("<code>name....: ".htmlentities($name)."</code><br>\n");
echo("<code>escape..: ".htmlentities($escape)."</code><br>\n");
echo("<code>global..: ".htmlentities($global)."</code><br>\n");
echo("<code>default.: ".htmlentities($default)."</code><br>\n");
echo("Normalizing...<br><br>\n");
}
// END DEBUG
// ESCAPE
if ($escape == "1") {
$escape = "HTML";
} else if (empty($escape) || !strcmp($escape, "NONE")) {
$escape = 0;
} else {
$escape = strtoupper($escape);
}
// GLOBAL
$global = ($global) ? 1 : 0;
// NAME
// Allow mixed case in filenames, otherwise flatten
if ($which != 'TMPL_INCLUDE' && !$this->options['case_sensitive']) {
$name = strtolower($name);
}
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<b>Final values:</b><br>\n");
echo("<code><b>Which...:</b> ".$which."</code><br>\n");
echo("<code><b>Name....:</b> ".$name."</code><br>\n");
echo("<code><b>Escape..:</b> ".$escape."</code><br>\n");
echo("<code><b>Global..:</b> ".$global."</code><br>\n");
echo("<code><b>Default.:</b> ".$default."</code><br><br>\n");
}
// END DEBUG
// Die if name contains invalid characters
if (!preg_match("/^[-\w\/+_\.]*$/", $name)) {
trigger_error("Template::Parse() : Invalid character(s) in NAME attribute (".htmlentities($name).") for ".$which." tag, found at ".$this->options['filename'].", line ".$lineNumber, E_USER_ERROR);
}
// Die if we need a name and didn't get one
if (empty($name) && isset($this->need_names[$which])) {
trigger_error("Template::Parse() : No NAME given to a ".$which." tag at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
// Die if we got an escape but can't use one
if ($escape and ($which != 'TMPL_VAR')) {
trigger_error("Template::Parse() : ESCAPE option invalid in a ".$which." tag at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
// Die if we got a default but can't use one
if ($default and ($which != 'TMPL_VAR')) {
trigger_error("Template::Parse() : DEFAULT option invalid in a ".$which." tag at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
// Wow, it doesn't die!
// Take actions depending on which tag found
switch ($which) { // ...said the witch
case 'TMPL_VAR':
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Adding VAR node<br>\n");
}
// END DEBUG
if (in_array($name, array('__pass__', '__passtotal__', '__counter__'))) {
if (count($inLoop)) {
$this->nodes[] =& new Node("ContextVAR", $name);
} else {
trigger_error("Template::Parse() : Found context VAR tag outside of LOOP, at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
} else {
$this->nodes[] =& new Node("VAR", $name, $escape, $global, $default);
$this->names[$name] = 1;
}
// BEGIN DEBUG
if ($this->options['debug']) {
echo("VAR Node added.<br>\n");
$this->ListNodes();
}
// END DEBUG
break;
case 'TMPL_LOOP':
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Adding LOOP node<br>\n");
}
// END DEBUG
$this->nodes[] =& new Node("LOOP", $name, NULL, $global);
$inLoop[] = count($this->nodes)-1;
$curType[] = "LOOP";
$this->names[$name] = 1;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("LOOP node added<br>\n");
$this->ListNodes();
}
// END DEBUG
break;
case '/TMPL_LOOP':
if (!strcmp(end($curType), "LOOP")) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Ending LOOP ".end($inLoop)."<br>\n");
}
// END DEBUG
$this->nodes[end($inLoop)]->jumpTo = count($this->nodes);
array_pop($inLoop);
array_pop($curType);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("LOOP ended<br>\n");
$this->ListNodes();
}
// END DEBUG
} else {
trigger_error("Template::Parse() : Nesting error: found end /TMPL_LOOP tag without its corresponding initial tag, at ".$this->options['filename']." : line ".$lineNumber." (last opened tag is of type \"".end($curType)."\")", E_USER_ERROR);
}
break;
case 'TMPL_IF':
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Adding IF node<br>\n");
}
// END DEBUG
if (in_array($name, array('__first__', '__odd__', '__inner__', '__last__'))) {
if (count($inLoop)) {
$this->nodes[] =& new Node("ContextIF", $name);
} else {
trigger_error("Template::Parse() : Found context IF/UNLESS tag outside of LOOP, at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
} else {
$this->nodes[] =& new Node("IF", $name, NULL, $global);
$this->names[$name] = 1;
}
$inIf[] = count($this->nodes)-1;
$curType[] = "IF";
// BEGIN DEBUG
if ($this->options['debug']) {
echo("IF node added<br>\n");
$this->ListNodes();
}
// END DEBUG
break;
case '/TMPL_IF':
if (!strcmp(end($curType), "IF")) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Ending IF<br>\n");
}
// END DEBUG
$this->nodes[end($inIf)]->jumpTo = count($this->nodes);
array_pop($inIf);
array_pop($curType);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("IF ended<br>\n");
$this->ListNodes();
}
// END DEBUG
} else {
trigger_error("Template::Parse() : Nesting error: found end /TMPL_IF tag without its corresponding initial tag, at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
break;
case 'TMPL_UNLESS':
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Adding UNLESS node<br>\n");
}
// END DEBUG
if (in_array($name, array('__first__', '__odd__', '__inner__', '__last__'))) {
$this->nodes[] =& new Node("ContextUNLESS", $name);
} else {
$this->nodes[] =& new Node("UNLESS", $name, NULL, $global);
$this->names[$name] = 1;
}
$inUnless[] = count($this->nodes)-1;
$curType[] = "UNLESS";
// BEGIN DEBUG
if ($this->options['debug']) {
echo("UNLESS node added<br>\n");
$this->ListNodes();
}
// END DEBUG
break;
case '/TMPL_UNLESS':
if (!strcmp(end($curType), "UNLESS")) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Ending UNLESS<br>\n");
}
// END DEBUG
$this->nodes[end($inUnless)]->jumpTo = count($this->nodes);
array_pop($inUnless);
array_pop($curType);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("UNLESS ended<br>\n");
$this->ListNodes();
}
// END DEBUG
} else {
trigger_error("Template::Parse() : Nesting error: found end /TMPL_UNLESS tag without its corresponding initial tag, at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
break;
case 'TMPL_ELSE':
if (!strcmp(end($curType), "IF") || !strcmp(end($curType), "UNLESS")) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Starting ELSE<br>\n");
}
// END DEBUG
if (!strcmp(end($curType), "IF")) {
$this->nodes[end($inIf)]->else = count($this->nodes);
} else {
$this->nodes[end($inUnless)]->else = count($this->nodes);
}
// BEGIN DEBUG
if ($this->options['debug']) {
echo("ELSE started<br>\n");
$this->ListNodes();
}
// END DEBUG
} else {
trigger_error("Template::Parse() : Nesting error: found end TMPL_ELSE tag without its corresponding initial tag, at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
}
break;
case 'TMPL_INCLUDE':
if (!$this->options['no_includes'])
{
if ($this->depth >= $this->options['max_includes'] && $this->options['max_includes'] > 0) {
trigger_error("Template::Parse() : Include error: Too many included templates, found at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
} else {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Including template ".$name."<br>\n");
}
// END DEBUG
$newOptions = $this->options;
$newOptions['filename'] = $name;
$newOptions['_parent'] =& $this;
new Template($newOptions);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<hr>Template included, returning to previous template<hr>\n");
$this->ListNodes();
}
// END DEBUG
}
}
break;
default:
trigger_error("Template::Parse() : Unknown or unmatched TMPL construct at ".$this->options['filename']." : line ".$lineNumber, E_USER_ERROR);
break;
}
} else {
// This is not a template tag. If it is not a delimiter, skip until next delimiter
// and add all trailing chunks as a new markup node
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<hr>Markup node found<br>\n");
}
// END DEBUG
// Make sure we didn't reject something TMPL_* but badly formed
if ($this->options['strict'] && preg_match("/".$delim."\/?[Tt][Mm][Pp][Ll]_/", $chunks[$i])) {
trigger_error("Template::Parse() : Syntax error in <TMPL_*> tag at " . $this->options['filename'] . " : line " . $lineNumber, E_USER_ERROR);
}
$this->nodes[] =& new Node("MARKUP", $chunks[$i]);
// BEGIN DEBUG
if ($this->options['debug']) {
$this->ListNodes();
}
// END DEBUG
}
// Count newlines in chunk and advance line count
$lineNumber += substr_count($chunks[$i], "\n");
}
// Check if there's some unfinished block
if (count($curType)) {
trigger_error("Template::Parse() : Template ".$this->options['filename']." incorrectly terminated. Found ".end($curType)." tag without corresponding end tag", E_USER_ERROR);
}
}
function ListNodes()
{
echo("<b>Contents of linearized parse tree</b><br>");
for ($i=0; $i<count($this->nodes); $i++) {
echo("<b>[".$i."]</b> - ".$this->nodes[$i]->type." - <code>".htmlentities(addcslashes($this->nodes[$i]->name, "\n\r"))."</code>".(($this->nodes[$i]->else===NULL)?"":" <code>{ Next } else { ".$this->nodes[$i]->else." }</code>").(($this->nodes[$i]->jumpTo===NULL)?"":" - Jump to ".$this->nodes[$i]->jumpTo)."<br>\n");
}
echo("<b>Variables used in template</b><br>");
ob_start();
print_r($this->names);
$b = ob_get_contents();
ob_end_clean();
echo("<pre>$b</pre>");
}
function AddParam($arg, $value=NULL)
{
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<h1>Template::AddParam()</h1>");
}
// END DEBUG
// We can call this with a two arguments (name value pair),
// or one argument (an associative array) (see README for details).
// If there are two arguments, the first must be a string, the second may a scalar or an array
// if the second argument is an array, it must be an array of associative arrays for a loop node
// If there's one argument, it must be an array, and its elements are name-value pairs.
if (func_num_args() == 2) {
if (is_scalar($value) || empty($value)) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>Attempting to set scalar value \"".htmlentities($value)."\" for variable \"$arg\".</p>");
}
// END DEBUG
if (!$this->options['case_sensitive']) {
$arg = strtolower($arg);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>case_sensitive option is off. Converting variable name to lowercase (\"$arg\").</p>");
}
// END DEBUG
}
if (isset($this->names[$arg])) {
$this->paramScope[count($this->paramScope)-1][$arg] = $value;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>Value set: \"$arg\" = \"".htmlentities($value)."\".</p>");
}
// END DEBUG
} else if ($this->options['die_on_bad_params']) {
trigger_error("Template::AddParam() : Attempt to set value for non existent variable name '".$arg."' - this variable name doesn't match any declarations in the template file", E_USER_ERROR);
}
} else if (is_array($value)) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>Attempting to set array value for variable \"$arg\".</p>");
}
// END DEBUG
if (!$this->options['case_sensitive']) {
$arg = strtolower($arg);
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>case_sensitive option is off. Converting variable name to lowercase (\"$arg\").</p>");
}
// END DEBUG
}
if (isset($this->names[$arg])) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>The variable \"$arg\" exists in the template.</p>");
}
// END DEBUG
$this->paramScope[count($this->paramScope)-1][$arg] = array();
$this->paramScope[] =& $this->paramScope[count($this->paramScope)-1][$arg];
foreach($value as $a) {
if (!is_array($a)) {
// error
trigger_error("Template::AddParam() : Must use arrays of associative arrays for loop parameters", E_USER_ERROR);
} else {
$this->paramScope[count($this->paramScope)-1][] = array();
$this->paramScope[] =& $this->paramScope[count($this->paramScope)-1][count($this->paramScope[count($this->paramScope)-1])-1];
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>Adding variables inside loop...</p><blockquote>");
}
// END DEBUG
foreach ($a as $k=>$v) {
if (is_scalar($v)) {
$this->SetValue($k, $v);
} else {
$this->AddParam($k, $v);
}
}
// BEGIN DEBUG
if ($this->options['debug']) {
echo("</blockquote>");
}
// END DEBUG
array_pop($this->paramScope);
}
}
array_pop($this->paramScope);
} else if ($this->options['die_on_bad_params']) {
trigger_error("Template::AddParam() : Attempt to set value for non existent variable name '".$arg."' - this variable name doesn't match any declarations in the template file", E_USER_ERROR);
}
} else {
// error
trigger_error("Template::AddParam() : Wrong value type", E_USER_ERROR);
}
} else if (func_num_args() == 1) {
if (is_array($arg)) {
foreach ($arg as $k => $v) {
$this->AddParam($k, $v);
}
} else {
// error
trigger_error("Template::AddParam() : Only one argument set, but it's not an array", E_USER_ERROR);
}
} else {
// error
trigger_error("Template::AddParam() : Wrong argument count (".func_num_args().") arguments", E_USER_ERROR);
}
}
function SetValue($arg, $value)
{
// Like AddParam but exclusively for setting scalar values
if (is_scalar($value) || empty($value)) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>Attempting to set scalar value \"".htmlentities($value)."\" for variable \"$arg\".</p>");
}
// END DEBUG
if (!$this->options['case_sensitive']) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>case_sensitive option is off. Converting variable name to lowercase (\"$arg\").</p>");
}
// END DEBUG
$arg = strtolower($arg);
}
if (isset($this->names[$arg])) {
$this->paramScope[count($this->paramScope)-1][$arg] = $value;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<p>Value set: \"$arg\" = \"".htmlentities($value)."\".</p>");
}
// END DEBUG
} else if ($this->options['die_on_bad_params']) {
trigger_error("Template::SetValue() : Attempt to set value for non existent variable name '".$arg."' - this variable name doesn't match any declarations in the template file", E_USER_ERROR);
}
} else{
// error
trigger_error("Template::SetValue() : Value must be a scalar", E_USER_ERROR);
}
}
function Output()
{
// BEGIN DEBUG
if ($this->options['debug']) {
echo("<h1>Template::Output()</h1>");
}
// END DEBUG
if (!isset($this->output)) {
$this->paramScope = array();
$this->paramScope[] =& $this->param;
$this->totalPass = array();
$this->curPass = array();
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Initial variable scope is:<pre>\n");
print_r($this->paramScope[0]);
echo("</pre>");
}
// END DEBUG
$n = 0;
while (isset($this->nodes[$n])) {
$n = $this->ProcessNode($n);
};
}
return $this->output;
}
function ProcessNode($n)
{
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Processing node $n of type ".$this->nodes[$n]->type." <code>[".htmlentities($this->nodes[$n]->name)."]</code><br>\n");
}
// END DEBUG
$node = $this->nodes[$n];
switch ($node->type) {
case "MARKUP":
$this->output .= $node->name;
return $n+1;
case "VAR":
if (isset($this->paramScope[count($this->paramScope)-1][$node->name])) {
if (is_scalar($this->paramScope[count($this->paramScope)-1][$node->name])) {
$value = $this->paramScope[count($this->paramScope)-1][$node->name];
} else if (is_array($this->paramScope[count($this->paramScope)-1][$node->name])) {
$value = count($this->paramScope[count($this->paramScope)-1][$node->name]);
}
// BEGIN DEBUG
else if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is defined in current scope with empty value.<br>\n");
}
// END DEBUG
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is defined in current scope with value <code>".htmlentities($value)."</code>.<br>\n");
}
// END DEBUG
if (!strcmp($node->escape, "HTML")) {
$this->output .= htmlspecialchars($value);
} else if (!strcmp($node->escape, "URL")) {
$this->output .= htmlentities(urlencode($value));
} else {
$this->output .= $value;
}
} else if ($node->default !== NULL) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is not defined in current scope but has a default value: <code>".htmlentities($node->default)."</code>.<br>\n");
}
// END DEBUG
if (!strcmp($node->escape, "HTML")) {
$this->output .= htmlspecialchars($node->default);
} else if (!strcmp($node->escape, "URL")) {
$this->output .= htmlentities(urlencode($node->default));
} else {
$this->output .= $node->default;
}
} else if ($this->options['global_vars'] || $this->nodes[$n]->global) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is not defined in current scope, searching in enclosing scopes.<br>\n");
}
// END DEBUG
for ($lvl = count($this->paramScope)-2; !isset($this->paramScope[$lvl][$node->name]) && $lvl>=0; $lvl--);
if ($lvl>=0) {
if (is_scalar($this->paramScope[$lvl][$node->name])) {
$value = $this->paramScope[$lvl][$node->name];
} else if (is_array($this->paramScope[$lvl][$node->name])) {
$value = count($this->paramScope[$lvl][$node->name]);
}
// BEGIN DEBUG
else if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is defined with empty value.<br>\n");
}
// END DEBUG
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Found variable in scope depth $lvl with value <code>".htmlentities($value)."</code><br>\n");
}
// END DEBUG
if (!strcmp($node->escape, "HTML")) {
$this->output .= htmlentities($value);
} else if (!strcmp($node->escape, "URL")) {
$this->output .= htmlentities(urlencode($value));
} else {
$this->output .= $value;
}
}
// BEGIN DEBUG
else if ($this->options['debug']) {
echo("Variable not found<br>\n");
}
// END DEBUG
}
// BEGIN DEBUG
else if ($this->options['debug']) {
echo("Variable not found or it is empty/NULL<br>\n");
}
// END DEBUG
return $n+1;
case "ContextVAR":
if ($this->options['loop_context_vars'] && count($this->totalPass)) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is defined in current scope.<br>\n");
}
// END DEBUG
switch ($node->name) {
case "__pass__":
case "__counter__":
$this->output .= $this->curPass[count($this->curPass)-1];
break;
case "__passtotal__":
$this->output .= $this->totalPass[count($this->totalPass)-1];
break;
}
}
return $n+1;
case "LOOP":
if (isset($this->paramScope[count($this->paramScope)-1][$node->name])) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is defined in current scope.<br>\n");
}
// END DEBUG
if (!is_array($this->paramScope[count($this->paramScope)-1][$node->name])) {
trigger_error("Template->Output() : A scalar value was assigned to a LOOP var (".$node->name."), but LOOP vars only accept arrays of associative arrays as values,", E_USER_ERROR);
}
$this->paramScope[] =& $this->paramScope[count($this->paramScope)-1][$node->name];
$this->totalPass[] = count($this->paramScope[count($this->paramScope)-1]);
$this->curPass[] = 0;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Entering loop. New variable scope is:<pre>\n");
print_r($this->paramScope[count($this->paramScope)-1]);
echo("</pre>");
echo("Loop will be traversed ".count($this->paramScope[count($this->paramScope)-1])." times<hr>\n");
}
// END DEBUG
for ($i=0; $i<$this->totalPass[count($this->totalPass)-1]; $i++) {
$this->curPass[count($this->curPass)-1]++;
$this->paramScope[] =& $this->paramScope[count($this->paramScope)-1][$i];
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable scope for this pass:<pre>\n");
print_r($this->paramScope[count($this->paramScope)-1]);
echo("</pre>");
echo("</pre>Traversing from node ".($n+1)." to node ".($node->jumpTo-1)."<hr>\n");
}
// END DEBUG
for($j=$n+1; $j<$node->jumpTo;) {
$j = $this->ProcessNode($j);
}
array_pop($this->paramScope);
}
array_pop($this->curPass);
array_pop($this->totalPass);
array_pop($this->paramScope);
return $node->jumpTo;
} else if ($this->options['global_vars'] || $this->nodes[$n]->global) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is not defined in current scope, searching in enclosing scopes.<br>\n");
}
// END DEBUG
for ($lvl = count($this->paramScope)-2; !isset($this->paramScope[$lvl][$node->name]) && $lvl>=0; $lvl--);
if ($lvl>=0) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Found variable in scope depth $lvl<br>\n");
}
// END DEBUG
if (!is_array($this->paramScope[$lvl][$node->name])) {
trigger_error("Template->Output() : A LOOP var (".$node->name.") was trying to use a scalar value, but LOOP vars only accept arrays of associative arrays as values,", E_USER_ERROR);
}
$this->paramScope[] =& $this->paramScope[$lvl][$node->name];
$this->totalPass[] = count($this->paramScope[count($this->paramScope)-1]);
$this->curPass[] = 0;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Entering loop. New variable scope is:<pre>\n");
print_r($this->paramScope[count($this->paramScope)-1]);
echo("</pre>");
echo("Loop will be traversed ".count($this->paramScope[count($this->paramScope)-1])." times<hr>\n");
}
// END DEBUG
for ($i=0; $i<$this->totalPass[count($this->totalPass)-1]; $i++) {
$this->curPass[count($this->curPass)-1]++;
$this->paramScope[] =& $this->paramScope[count($this->paramScope)-1][$i];
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable scope for this pass:<pre>\n");
print_r($this->paramScope[count($this->paramScope)-1]);
echo("</pre>");
echo("</pre>Traversing from node ".($n+1)." to node ".($node->jumpTo-1)."<hr>\n");
}
// END DEBUG
for($j=$n+1; $j<$node->jumpTo;) {
$j = $this->ProcessNode($j);
}
array_pop($this->paramScope);
}
array_pop($this->curPass);
array_pop($this->totalPass);
array_pop($this->paramScope);
return $node->jumpTo;
}
// BEGIN DEBUG
else if ($this->options['debug']) {
echo("Variable not found<br>\n");
}
// END DEBUG
}
case "IF":
case "UNLESS":
$cond = 0; // by default, condition is false
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Entering IF/UNLESS branch<br>\n");
}
// END DEBUG
$else = $node->else;
if (isset($this->paramScope[count($this->paramScope)-1][$node->name])) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is defined in current scope.<br>\n");
}
// END DEBUG
if (is_scalar($this->paramScope[count($this->paramScope)-1][$node->name])) {
$cond = $this->paramScope[count($this->paramScope)-1][$node->name];
} else if (is_array($this->paramScope[count($this->paramScope)-1][$node->name])) {
$cond = count($this->paramScope[count($this->paramScope)-1][$node->name]);
}
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable found, value is <code>$cond</code>, condition is ".($cond!=false)."<br>\n");
}
// END DEBUG
} else if ($this->options['global_vars'] || $this->nodes[$n]->global) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable <code>".$node->name."</code> is not defined in current scope, searching in enclosing scopes.<br>\n");
}
// END DEBUG
for ($lvl = count($this->paramScope)-2; !isset($this->paramScope[$lvl][$node->name]) && $lvl>=0; $lvl--);
if ($lvl>=0) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Found variable in scope depth $lvl<br>\n");
}
// END DEBUG
if (is_scalar($this->paramScope[$lvl][$node->name])) {
$cond = $this->paramScope[$lvl][$node->name];
} else if (is_array($this->paramScope[$lvl][$node->name])) {
$cond = count($this->paramScope[$lvl][$node->name]);
} else { // empty var
$cond = 0;
}
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable found, value is <code>$cond</code>, condition is ".($cond!=false)."<br>\n");
}
// END DEBUG
} else {
$cond = 0;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable not found, condition is false<br>\n");
}
// END DEBUG
}
} else {
$cond = 0;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Variable not found, condition is false<br>\n");
}
// END DEBUG
}
if (!strcmp($node->type, "UNLESS")) {
$cond = !$cond;
}
if ($cond) {
$last = ($else)?$else:$node->jumpTo;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Condition is true, traversing nodes ".($n+1)." to ".($last-1)."<br>\n");
}
// END DEBUG
for ($j = $n+1; $j < $last;) {
$j = $this->processNode($j);
}
} else if ($else) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Condition is false, traversing nodes ".$else." to ".($node->jumpTo-1)."<br>\n");
}
// END DEBUG
for ($j = $else; $j < $node->jumpTo;) {
$j = $this->processNode($j);
}
}
return $node->jumpTo;
case "ContextIF":
case "ContextUNLESS":
if ($this->options['loop_context_vars']) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Entering ContextIF/ContextUNLESS branch<br>\n");
}
// END DEBUG
$else = $node->else;
$cond = 0;
switch ($node->name) {
case "__first__":
if ($this->curPass[count($this->curPass)-1] == 1) {
$cond = 1;
}
break;
case "__odd__":
if ($this->curPass[count($this->curPass)-1] % 2) {
$cond = 1;
}
break;
case "__inner__":
if ($this->curPass[count($this->curPass)-1] > 1 && $this->curPass[count($this->curPass)-1] < $this->totalPass[count($this->totalPass)-1]) {
$cond = 1;
}
break;
case "__last__":
if ($this->curPass[count($this->curPass)-1] == $this->totalPass[count($this->totalPass)-1]) {
$cond = 1;
}
break;
}
if (!strcmp($node->type, "ContextUNLESS")) {
$cond = !$cond;
}
if ($cond) {
$last = ($else) ? $else : $node->jumpTo;
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Condition is true, traversing nodes ".($n+1)." to ".($last-1)."<br>\n");
}
// END DEBUG
for ($j = $n+1; $j < $last;) {
$j = $this->processNode($j);
}
} else if ($else) {
// BEGIN DEBUG
if ($this->options['debug']) {
echo("Condition is false, traversing nodes ".$else." to ".($node->jumpTo-1)."<br>\n");
}
// END DEBUG
for ($j = $else; $j < $node->jumpTo;) {
$j = $this->processNode($j);
}
}
return $node->jumpTo;
}
}
}
function EchoOutput()
{
$this->Output();
echo($this->output);
}
// ResetParams() and ResetOutput() can be useful when processing the same template with new
// variable values, without repeating the Parse phase.
function ResetParams()
{
$this->param = array();
}
function ResetOutput()
{
$this->output = NULL;
}
function SaveCompiled($outputdir = NULL, $overwrite = 0) {
if ($outputdir === NULL) {
$outputdir = dirname($this->options['filename']);
}
if (!is_writable($outputdir)) {
trigger_error("Template::SaveCompiled() - Output directory not writable", E_USER_ERROR);
} else {
$file = basename($this->options['filename']);
$output = $outputdir . "/" . $file . "c";
if (file_exists($output) && !$overwrite) {
trigger_error("Template::SaveCompiled() - File " . $output . " already exists, cannot write compiled template", E_USER_ERROR);
} else {
$f = fopen($output, "w");
fwrite($f, serialize($this));
fclose($f);
return realpath($output);
}
}
return NULL; // should never arrive here anyway...
}
}
class Node
{
var $type = NULL; // Current types: MARKUP, VAR, IF/ContextIF, UNLESS/ContextUNLESS, LOOP
var $name = NULL; // Variable name, or markup value
var $escape = NULL; // Current types: HTML, URL or NULL
var $global = NULL; // Set to 1 if GLOBAL attribute is set for this node, NULL otherwise
var $default = NULL; // Default value
var $jumpTo = NULL;
var $else = NULL;
function Node($type, $name, $global=NULL, $escape=NULL, $default=NULL)
{
$this->type = $type;
$this->name = $name;
$this->global = $global;
$this->escape = $escape;
$this->default = $default;
}
}
function &LoadCompiledTemplate($filename) {
if (!is_readable($filename)) {
trigger_error("LoadCompiledTemplate() - Cannot read file " . $filename, E_USER_ERROR);
} else {
$f = fopen($filename, "r");
$data = fread($f, filesize($filename));
fclose($f);
return unserialize($data);
}
}