<?php
/**********************************************************************
*
* Loads Templates Into a Class
* and Handles Parsing
* of the .tpl files into HTML
*
* Compiling throws everything into a
* string that will be executed upon printing
* this way php can be included without having to
* do some crazy stuff
*
**********************************************************************
* Copyright 2008
*
* This file is part of Runner.
*
* Runner is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Runner is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Runner. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
include('functions_template.php');
class Template {
var $data_array = array();
var $runner_array = array();
var $tmp_vars = array();
var $template_file = "";
var $dir = "";
var $short_dir = "";
var $css = "";
var $compiled_data = null;
var $found_shell_vars = array();
var $content_blocks = array();
var $current_file = array();
var $current_line = array();
var $open_functions = array();
var $_double_quote_expression = null;
var $_single_quote_expression = null;
var $_number_expression = null;
var $_math_expression = null;
var $_var_qut_number_exp = null;
var $_variable_bracket_exp = null;
var $_variable_dot_exp = null;
var $_basic_var_expression = null;
var $_var_attribute_expression = null;
var $_condition_att_expression = null;
var $_set_att_expression = null;
var $_atribute_expression = null;
var $_function_expression = null;
function Template($desig)
{
$this->_double_quote_expression = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
$this->_single_quote_expression = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
// Matches any numbers
$this->_number_expression = '(?:\-?\d+(?:\.\d+)?)';
// Matches [brackets]
// [foo]
// [$foo]
$this->_math_expression = '[\=\+\-\!\%\>\<]';
$this->_variable_bracket_exp = '\[\$?[\w\.]+\]';
$this->_basic_var_expression = '\$(?:[\w]+)(?:'.$this->_variable_bracket_exp.')*(?:\.\$?\w+(?:'.$this->_variable_bracket_exp. ')*)*(?:'.$this->_variable_bracket_exp.')*';
$this->_var_qut_number_exp = '(?:'.$this->_basic_var_expression
.')|(?:'.$this->_single_quote_expression.')|(?:'.$this->_double_quote_expression
.')|(?:'.$this->_number_expression.')';
$this->_set_att_expression = '\s?(?:(?:(?:[a-zA-Z\_]+)|(?:'.$this->_basic_var_expression
.'))\s?\=\s?(?:'.$this->_var_qut_number_exp.'))';
$this->_condition_att_expression = '\s?(?:'.$this->_var_qut_number_exp.')\s?(?:'.$this->_math_expression.'+?)\s?(?:'.$this->_var_qut_number_exp.')(?:\s?[\&]*?[\|]*?\s?)?';
$this->_atribute_expression = '\s?(?:(?:'.$this->_set_att_expression.')|(?:'.$this->_condition_att_expression.')|(?:'.$this->_var_qut_number_exp.'))*';
$this->_function_expression = '%\{[\w\/]+(?:'.$this->_atribute_expression.')?\}%';
$this->_var_attribute_expression = '(?:\|(?:(?:'.$this->_set_att_expression.')|(?:[\w]+)))*?';
$parts = explode('/', $_SERVER["PHP_SELF"]);
$this->runner_array[page] = $parts[count($parts) - 1];
$this->runner_array[unix_time] = time();
$this->runner_array[now] = time();
$this->runner_array[time] = date(TIME_FORMAT, $this->runner_array[unix_time]);
$this->runner_array[date] = date(DATE_FORMAT, $this->runner_array[unix_time]);
$this->runner_array[website] = array(
"url" => WEBSITE,
"root_dir" => ROOT_DIR,
"page" => $parts[count($parts) - 1],
"dir" => str_replace($parts[count($parts) - 1], "", $_SERVER["PHP_SELF"])
);
$this->runner_array[session] = $_SESSION;
$this->runner_array[get] = $_GET;
$this->runner_array[post] = $_POST;
$this->runner_array[cookie] = $_COOKIE;
$this->runner_array[env] = $_ENV;
$this->runner_array[server] = $_SERVER;
$this->LoadTemplate($desig);
}
function LoadFile($file_name)
{
//Open File
$file = fopen($this->runner_array[template][file_dir].$file_name, "r") or
die("Template->LoadFile Failed: Could not open main file '".$file_name."'\n");
$fdata = "";
while (!feof($file))
{
//Read line by line
$fdata .= fread($file, 2560);
}
fclose($file);
return $fdata;
}
function LoadTemplate($file_name)
{
if (!isset($file_name)) {
die("Template->LoadTemplate Failed: No master file specified\n");
}
$file = fopen( $file_name , "r") or
die("Template->LoadTemplate Failed: Could not open main file $file_name\n");
while (!feof($file)){
$settings = fread($file, 2560);
preg_match_all('#([a-zA-Z0-9\_\-\+\./]+)\s=\s([^\n]+)#', $settings, $setting_array, PREG_SET_ORDER);
foreach ($setting_array as $setting)
{
$setting_name = strtolower($setting[1]);
$val = $setting[2];
if(DEBUG > 1)
echo " <!-- ".$setting_name." = ".$val."-->\n";
switch($setting_name)
{
case "name":
$this->runner_array[template][name] = $val;
break;
case "designer":
$this->runner_array[template][designer] = $val;
break;
case "version":
$this->runner_array[template][version] = $val;
break;
case "copyright":
$this->runner_array[template][copyright] = $val;
break;
case "directory":
$this->runner_array[template][web_dir] = WEBSITE . "templates/" . $val . "/";
$this->runner_array[template][file_dir] = ROOT_DIR . "templates/" . $val . "/";
$this->runner_array[template][short_dir] = "templates/" . $val . "/";
break;
case "stylesheet":
$this->runner_array[template][css_file] = $val;
$this->runner_array[template][css] = $this->runner_array[template][web_dir] . $val;
break;
case "stylesheetie":
$this->runner_array[template][css_ie_file] = $val;
$this->runner_array[template][css_ie] = $this->runner_array[template][web_dir] . $val;
break;
default:
$this->runner_array[template][$setting_name] = $val;
break;
}
}
$settings=NULL;
}
fclose($file);
define(TEMPLATE_DIR, $this->short_dir);
if(DEBUG > 1) {
echo "<!--Loaded Template -->\n";
echo " <!--Loaded on ".date(DATE_FORMAT." ".TIME_FORMAT, time())." -->\n";
}
}
function SetData($var, $eq)
{
//$var = strtolower($var);
if (is_array($eq))
{
$this->data_array[$var] = array();
$keys = array_keys($eq);
if(is_array($keys))
{
$this->data_array[$var] = array();
foreach($eq as $key => $val)
{
$key = strtolower($key);
$this->data_array[$var][$key] = $val;
}
}
}
else
{
$this->data_array[$var] = $eq;
}
return true;
}
function _push_tag($open_tag)
{
array_push($this->open_functions, array($open_tag, $this->current_line));
}
function _pop_tag($close_tag)
{
$message = '';
if (count($this->_tag_stack)>0) {
list($_open_tag, $_line_no) = array_pop($this->open_functions);
if ($close_tag == $_open_tag) {
return $_open_tag;
}
if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) {
return $this->_pop_tag($close_tag);
}
if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
$this->_pop_tag($close_tag);
return $_open_tag;
}
if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
$this->_pop_tag($close_tag);
return $_open_tag;
}
if ($_open_tag == 'else' || $_open_tag == 'elseif') {
$_open_tag = 'if';
} elseif ($_open_tag == 'sectionelse') {
$_open_tag = 'section';
} elseif ($_open_tag == 'foreachelse') {
$_open_tag = 'foreach';
}
$message = " expected {/$_open_tag} (opened line $_line_no).";
}
}
function _parse_variables(&$txt, $brackets = 'true')
{
/* {$VARIABLE}
* or
* {VARIABLE}
*
* Replaces {$DATA} with the proper values
*/
$var_ref = "\$this->data_set";
$this->runner_array[random] = rand(0,100);
if($brackets == 'true' || $brackets == 'echo')
preg_match_all('%\{('.$this->_basic_var_expression.')('.$this->_var_attribute_expression.')?\}%', $txt, $varrefs, PREG_SET_ORDER);
else
preg_match_all('%('.$this->_basic_var_expression.')%', $txt, $varrefs, PREG_SET_ORDER);
foreach ($varrefs as $var_val)
{
$namespace = $var_val[0];
$varname = $var_val[1];
$sub_vars = explode(".", $varname);
switch ($sub_vars[0]) {
case '$runner':
$var_ref = "\$this->runner_array";
break;
default:
$var_ref = "\$this->data_array";
break;
}
$args = array();
$args_txt = "";
foreach ($sub_vars as $var)
{
$v=explode("[", $var);
$v = str_replace("\$", "", $v[0]);
if($v != "" && substr($v, 0, 1) != "_" && $v != "runner")
{
$args_txt .= '['.$v.']';
preg_match_all('#\[(.+?)\]#', $var, $arguments);
foreach($arguments[1] as $t_arg)
{
$args_txt .= '['.$t_arg.']';
array_push($args, $t_arg);
}
}
else
{
$replace = $varname;
}
}
$replace = $var_ref.$args_txt;
$replace_var = $replace;
if($var_val[2] != "")
{
$vargs = explode("|", $var_val[2]);
foreach ($vargs as $v_arg)
{
/// Matches all the modifiers
///
/// Example:
/// capitalize
preg_match_all('%([\w]+)%', $v_arg, $attrib, PREG_SET_ORDER);
$attrib = $attrib[0];
str_replace($attrib[0], "", $v_arg);
switch ($attrib[1])
{
case 'uppercase':
$replace = 'strtoupper('.$replace.')';
break;
case 'lowercase':
$replace = 'strtolower('.$replace.')';
break;
case 'capitalize':
$replace = 'ucwords('.$replace.')';
break;
case 'sentence':
$replace = 'ucfirst('.$replace.')';
break;
case 'count':
$replace = 'strlen('.$replace.')';
break;
case 'reverse':
$replace = 'strrev('.$replace.')';
break;
default:
break;
}
}
foreach ($vargs as $v_arg)
{
///Macth all Attribute expressions
//
// Example:
// default = "Jane Doe"
preg_match_all('%'.$this->_set_att_expression.'%', $v_arg, $attrib);
foreach ($attrib as $attribute)
{
$attribute_full = explode("=",trim($attribute[0]), 2);
str_replace($attribute_full, "", $v_arg);
switch ($attribute_full[0])
{
case 'default':
$replace = '<?php if(isset('.$replace_var.')){echo '.$replace.';}else{echo '.str_replace($replace_var, '"'.substr($attribute_full[1],1,-1).'"', $replace).';} ?>';
$changed = 'true';
break;
case 'date_format':
$replace = '<?php if(date("j", '.$replace_var.')){$time_var = true;}else{$time_var = false;}if(isset('.$replace_var.')){if($t = strtotime('.$replace_var.')){echo date('.$attribute_full[1].', $t);}elseif($time_var == true){echo date('.$attribute_full[1].', '.$replace_var.');}else{echo '.$replace_var.';}} ?>';
$changed = 'true';
default:
break;
}
}
}
}
if($brackets == 'echo' && $changed != 'true')
$replace = '";echo '.$replace.'; echo "';
$txt = str_replace($namespace, $replace, $txt);
}
}
function _get_var($var_name)
{
$this->_parse_variables($var_name, 'false');
if ($var_name == '')
return NULL;
eval("\$replace = $var_name;");
return $replace;
}
function remove_quotes(&$txt)
{
preg_match('%('.$this->_single_quote_expression.')%', $txt, $matches);
if(isset($matches[1]))
$txt = substr($txt, 1, strlen($txt) - 2 );
preg_match('%('.$this->_double_quote_expression.')%', $txt, $matches);
if(isset($matches[1]))
$txt = substr($txt, 1, strlen($txt) - 2 );
return $txt;
}
function _compile_if_tag($tag, $conditions, $elseif = false)
{
if( $elseif == false)
{
$tag = preg_replace("%\{\s?if\s%", "", $tag);
$tag = substr($tag, 0, strlen($tag) - 1);
//echo $tag;
//$tag = $this->_parse_variables($tag, 'false');
return "<?php if(".$tag."): ?>";
}
else
{
$tag = preg_replace("%\{\s?elseif\s%", "", $tag);
$tag = substr($tag, 0, strlen($tag) - 1);
$tag = $this->_parse_variables($tag, 'false');
return "<?php elseif(".$tag."): ?>";
}
}
function _compile_include_tag($tag, $file)
{
$this->_parse_variables($file);
$file = preg_replace("([\"\'])", "", $file);
if(!file_exists($file)) {
if($this->runner_array[template][short_dir] != NULL) {
/*$file = $this->runner_array[template][short_dir] . $file;
if(!file_exists($file)) {
$replace = "<!-- File \"$file\" not found -->";
}*/
//else {
$compile = new Template(TEMPLATE_DESIGN);
$replace = $compile->CompileFile($file);
//}
}
else {
$replace = "<!-- File \"$file\" not found -->";
}
}
else {
$compile = new Template($file);
$replace = $compile->CompileFile($file);
}
$complie = NULL;
return $replace;
}
function _compile_include_php_tag($tag, $file)
{
$this->_parse_variables($file);
$file = preg_replace("([\"\'])", "", $file);
if(!file_exists($file)) {
if(TEMPLATE_DIR != NULL) {
$file = TEMPLATE_DIR . $file;
if(!file_exists($file)) {
$replace = "<!-- File \"$file\" not found -->";
}
else {
$replace = '<?php include("'.$file.'"); ?>';
}
}
else {
$replace = "<!-- File \"$file\" not found -->";
}
}
else {
$replace = '<?php include("'.$file.'"); ?>';
}
$complie = NULL;
return $replace;
}
function _compile_select($tag, $known)
{
if(!isset($known['name']))
compile_error("Select function must contain 'name' attribute", $this->current_file, $this->current_line);
return "<select name='".$this->remove_quotes($known['name'])."' id='".$this->remove_quotes($known['id'])."'>";
}
function _compile_select_option($tag, $known)
{
if(!isset($known['value']))
{
if(!isset($known['val_set']))
compile_error("Option function must contain 'value' or 'val_set' attribute", $this->current_file, $this->current_line);
}
if(!isset($known['name']))
{
if(isset($known['value']))
compile_error("Option function must contain 'name' attribute", $this->current_file, $this->current_line);
}
if(isset($known['val_set']))
{
$V = $this->_get_var($known['val_set']);
$N = $this->_get_var($known['name_set']);
if(count($V) != count($N) && isset($known['name_set']))
compile_error("Name array diffent length than Value array.", $this->current_file, $this->current_line);
$replace = "";
foreach($V as $key=>$value)
{
if(isset($N[$key]))
$name = $N[$key];
elseif(isset($N[$i]))
$name = $N[$i];
else
$name = $key;
$this->remove_quotes($value);
$this->remove_quotes($name);
$replace .= "<option value='".$value."'>".$name."</option>";
}
}
else
{
$value = $known['value'];
$name = $known['name'];
$this->remove_quotes($value);
$this->remove_quotes($name);
$replace = "<option value='".$value."'>".$name."</option>";
}
return $replace;
}
function _compile_foreach($tag, $attrs)
{
if(empty($attrs['from']))
compile_error("foreach: missing 'from' attribute", $this->current_file, $this->current_line);
$from = $attrs['from'];
$this->tmp_vars[$from] = $this->_parse_variables($from, 'false');
if(empty($attrs['item']))
compile_error("foreach: missing 'item' attribute", $this->current_file, $this->current_line);
$item = $this->remove_quotes($attrs['item']);
if (!preg_match('%^\w+$%', $item)) {
compile_error("foreach: 'item' must be a variable name (literal string without leading $)", E_USER_ERROR, __FILE__, __LINE__);
}
if(isset($attrs['key']))
{
$key = $this->remove_quotes($attrs['key']);
if (!preg_match('%^\w+$%', $item)) {
compile_error("foreach: 'key' must be a variable name (literal string without leading $)", E_USER_ERROR, __FILE__, __LINE__);
}
$key_part = "\$this->data_array['".$key."'] => ";
}
$out = "<?php".
"\$_from = $from;\n".
"if(count(\$_from)):\n".
" foreach(\$_from as $key_part\$this->data_array['".$item."']):\n".
"?>";
return $out;
}
function _compile_img($tag, $known)
{
$replace = "<img ";
foreach($known as $key=>$val)
{
if(strtolower(trim($key)) == "src")
{
$replace .= "$key='".$this->runner_array[template][web_dir].$this->remove_quotes($val)."'" ;
}
else
{
$replace .= "$key=$val ";
}
}
$replace .= ">";
return $replace;
}
function _parse_function(&$txt)
{
$full_function = null;
$full_functuin_cut = null;
$func_array = array();
$know_attribute = array();
$cond_attribute = array();
$constants = array();
preg_match_all($this->_function_expression, $txt, $if_array, PREG_SET_ORDER);
foreach($if_array as $fun)
{
$full_function = $fun[0];
$full_functuin_orig = $full_function;
//Find what function it is
preg_match('%\{([\w\/]+)\s?%',$full_function, $function_name);
$function_name = $function_name[1];
$full_function = str_replace("{".$function_name, "{", $full_function);
//Find the "set varibale" arguments
preg_match_all('%'.$this->_set_att_expression.'%',$full_function, $func_array, PREG_SET_ORDER);
foreach( $func_array as $attributes)
{
foreach ($attributes as $attribute)
{
$attribute_full = explode("=",trim($attributes[0]));
//echo "<br>".$attribute_full[0]." == ".$attribute_full[1]."<br>";
$know_attribute[$attribute_full[0]] = $attribute_full[1];
$full_function = str_replace($attributes[0], "", $full_function);
}
}
//Find conditional arguments
preg_match_all('%('.$this->_var_qut_number_exp.')\s?('.$this->_math_expression.'+?)\s?('.$this->_var_qut_number_exp.')%',$full_function, $func_array, PREG_SET_ORDER);
foreach( $func_array as $attributes)
{
//echo "<br>".$attribute_full[0]." == ".$attribute_full[1]."<br>";
array_push($cond_attribute, array($attributes[1], $attributes[2], $attributes[3]));
$full_function = str_replace($attributes[0], "", $full_function);
}
//Find the "constants / varibale" arguments
preg_match_all('%'.$this->_var_qut_number_exp.'%',$full_function, $func_array, PREG_SET_ORDER);
foreach( $func_array as $attributes)
{
foreach ($attributes as $attribute)
{
$attribute_full = explode("=",trim($attributes[0]));
//echo "<br>".$attribute_full[0]." == ".$attribute_full[1]."<br>";
array_push($constants, $attribute_full[0]);
}
}
switch ($function_name) {
case 'style_sheets':
$replace = '<!--[if IE]><link href="'.$this->runner_array[template][css_ie].'" rel="stylesheet" type="text/css" /><![endif]--><link href="'.$this->runner_array[template][css].'" rel="stylesheet" type="text/css" />';
break;
case 'include':
if(isset($know_attribute['file']))
$f = $know_attribute['file'];
else
$f = $constants[0];
$replace = $this->_compile_include_tag($tag, $f);
break;
case 'include_php':
if(isset($know_attribute['file']))
$f = $know_attribute['file'];
else
$f = $constants[0];
$replace = $this->_compile_include_php_tag($tag, $f);
break; case 'if':
$this->_push_tag("if");
$replace = $this->_compile_if_tag($full_functuin_orig, $cond_attribute);
break;
case 'elseif':
$this->_push_tag("if");
$replace = $this->_compile_if_tag($full_functuin_orig, $cond_attribute, true);
break;
case 'else':
$this->_pop_tag("if");
$replace = "<?php else: ?>";
break;
case '/if':
$this->_pop_tag("if");
$replace = "<?php endif; ?>";
break;
case 'select':
$this->_push_tag("select");
$replace = $this->_compile_select($tag, $know_attribute);
break;
case '/select':
$this->_pop_tag("select");
$replace = "</select>";
break;
case 'option':
$replace = $this->_compile_select_option($tag, $know_attribute);
break;
case 'foreach':
$this->_push_tag("foreach");
$replace = $this->_compile_foreach($tag, $know_attribute);
break;
case '/foreach':
$this->_pop_tag("foreach");
$replace = "<?php endforeach; endif; unset(\$_from); ?>";
break;
case 'img':
$replace = $this->_compile_img($tag, $know_attribute);
break;
}
}
$txt = str_replace($full_functuin_orig, $replace, $txt);
}
function _prepare_for_echo($txt)
{
$txt = "echo \"\n" . $txt . "\n\";";
$txt = preg_replace("(<\?(?:php)?)", "\";\n", $txt);
$txt = preg_replace("((php)?\?>)", "\necho \"", $txt);
$echo_txt = '#echo\s*?\"\s*?((?:(?:[^"]*?)\")*?)\n*[$;]#';
preg_match_all($echo_txt, $txt, $quote_array, PREG_SET_ORDER);
foreach($quote_array as $resul)
{
$search = $resul[1];//Same bc we are only replacing the inner text
$replace = $resul[1];
$replace = substr($replace, 0, strlen($replace)-1);
$replace = str_replace("\"", "\\\"", $replace);
//$replace = str_replace("\$", "\\\$", $replace);
$replace .= '"';
//echo "<!--".$replace."-->";
$txt = str_replace($search, $replace, $txt);
}
return $txt;
}
function Compile_line(&$txt)
{
$this->_parse_function($txt);
$this->_parse_variables($txt, 'echo');
return $txt;
}
function CompileFile($file_name)
{
$varrefs = array();
array_push($this->current_file, $file_name);
/* Load template from file */
$currently_compiling = $this->LoadFile($file_name);
_remove_direct_php($currently_compiling);
$lines = explode("\n", $currently_compiling);
$line_num = 0;
foreach($lines as $line)
{
$line_num ++;
array_push($this->current_line, $line_num);
$compiled .= $this->Compile_line($line) . "\n";
array_pop($this->current_line);
}
array_pop($this->current_file);
return $compiled;
}
function CompileShell($file)
{
$this->compiled_data = $this->_prepare_for_echo($this->CompileFile($file));
return true;
}
function PrintPage($file)
{
$cache_file = CACHE_DIR."/".md5($_SERVER['PHP_SELF']).CACHE_EXT;
$open_cache = 'false';
$save_cache = 'false';
$time = time();
if(file_exists(CACHE_DIR) && CACHE == 'true')
{
if(file_exists($cache_file))
{
if(filectime($cache_file) > ($time - CACHE_TIME))
{
echo "<!-- Loading from cache -->\n";
$save_cache = 'false';
}
else
{
unlink($cache_file);
echo "<!-- Updating cache -->\n";
$save_cache = 'true';
}
}
else
{
echo "<!-- Creating cache -->\n";
$save_cache = 'true';
}
}
elseif(CACHE == 'true')
{
mkdir(CACHE_DIR) or die("An error occurred while creating the cache directory. Please change the permissions of ".CHACHE_DIR.".");
$save_cache = 'true';
}
if($save_cache != 'true' && CACHE == 'true')
{
$cf = fopen($cache_file, 'r');
while ($line = fgets($cf, 1024))
{
if(time() > $time + 10)
break;
$cache_code .= $line;
}
if(DEBUG == 3)
echo "<!-- $cache_code -->\n";
eval($cache_code);
fclose($cf);
}
elseif(CACHE == 'true')
{
//compile page
if( !$this->CompileShell($file) )
die("Template->PrintPage Failed: Error Compiling");
//Save new cache file
$cf = fopen($cache_file, 'w');
fwrite($cf, $this->compiled_data);
fclose($cf);
//Print page
if(DEBUG == 3)
echo "<!-- ".str_replace('-->', '>', ($this->compiled_data))." -->\n";
eval($this->compiled_data);
}
else
{
//compile page
if( !$this->CompileShell($file) )
die("Template->PrintPage Failed: Error Compiling");
//Print page
if(DEBUG == 3)
echo "<!-- ".str_replace('-->', '>', ($this->compiled_data))." -->\n";
eval($this->compiled_data);
}
return true;
}
}