<?php
/*
Site Template Parser
(c) 2004-2007 by "Oleg Savchuk" <hide@address.com>
part of phpProjectMaster project
http://phpprojmaster.sourceforge.net
The contents of this file are subject to the GNU GENERAL PUBLIC LICENSE
http://www.gnu.org/copyleft/gpl.html
2006-07-15 Oleg Savchuk - fixed inline tags parsing
2006-12-30 - fixed parse_page_sort_tags (added \b)
2007-03-28 - added multi-language support
2007-03-30 - added 'global' attribute
*/
require_once "lock.php";
//##########################################################
// Universal Page parser
// ATTENTION! this version of parse_page assumes all templates under $site_templ dir, so root path is $site_templ
// mode autodetect $out_filename='' or 's' - variable/screen, >'' - to file $out_filename
// tags in templates should be in <~name [attributes]> format
// supported attributes:
# if="php expression" - tag/template will be parsed only if expression is true, notes:
# vars from $hf can be used as $var_name
# > comparison sign MUST be written as > (for faster tag parsing)
# example: <~aaa if="$bbb < 100"> <~ccc if="$bbb > 100"> <~xxx if="$yyy eq $zzz">
// var - tag is variable, no fileseek necessary
// ifeq="var" value="XXX" - tag/template will be parsed only if var=XXX
// ifneq="var" value="XXX" - tag/template will be parsed only if var!=XXX
// ifge="var" value="XXX" - tag/template will be parsed only if var>XXX
// ifgee="var" value="XXX" - tag/template will be parsed only if var>=XXX
// ifle="var" value="XXX" - tag/template will be parsed only if var<XXX
// iflee="var" value="XXX" - tag/template will be parsed only if var<=XXX
// repeat - this tag is repeat content ($hf hash should contain reference to array of hashes)
# sub - this tag tell parser to use subhash for parse subtemplate ($hf hash should contain reference to hash)
# inline - this tag tell parser that subtemplate is not in file - it's between <~tag>...</~tag> , useful in combination with 'repeat' and 'if'
# global - this tag is a global var, not in $hf hash
# session - this tag is a $_SESSION var, not in $hf hash
# select="var" - this tag tell parser to load file and use it as value|display for <select> tag, example:
# <select name="item[fcombo]">
# <option value=""> - select -
# <~./fcombo.sel select="fcombo">
# </select>
# radio="var" name="YYY" [delim="ZZZ"]- this tag tell parser to load file and use it as value|display for <input type=radio> tags, example:
# <~./fradio.sel radio="fradio" name="item[fradio]" delim=" ">
# selvalue="var" - display value (fetched from the .sel file) for the var (example: to display 'select' and 'radio' values in List view)
# ex: <~../fcombo.sel selvalue="fcombo">
// nolang - for subtemplates - use default language instead of current (usually english)
// htmlescape - replace special symbols by their html equivalents (such as <>,",')
// support ~text~ => replaced by multilang from $site_templ/lang/$lang.txt according to $GLOBAL['lang'] (if !='' - english by default)
// lang.txt line format:
// english string[space]===[space]lang string
// !CHANGED mode of using SCREEN and VARIABLE!
// !!!RECURSIVE
// $page - if set - contains page contents (no need to read file!), $tpl_name in this case is just for helping determine relative dirs
function parse_page($basedir, $tpl_name, $hf, $out_filename='', $page=''){
global $site_templ, $root_url, $site_root;
//make path 'absolute'
if (!preg_match("/^\//",$tpl_name)) $tpl_name="$basedir/$tpl_name";
if (!$page){
//load template
// rw("$basedir, $site_templ|$tpl_name");
$page=precache_file("$site_templ$tpl_name");
}
if ($page){ //if template empty - don't parse it
parse_lang($page);
//get all tags with attributes needed to be filled
preg_match_all("/<~([^>]+)>/",$page,$out,PREG_PATTERN_ORDER);
$tags_full=$out[1];
//rw("$tpl_name: found ".count($tags_full)." tags");
if (count($tags_full)>0){ //if there are no tags found - dont' parse it
$hf['ROOT_URL']=$root_url;
$hf['ROOT_DIR']=$site_root;
// rw("hf=".count($hf)." ");
//re-sort $tags_full, so all 'inline' and 'sub' tags will be parsed first
usort($tags_full, 'parse_page_sort_tags');
$TAGSEEN=array();
for($i=0;$i<count($tags_full);$i++){
$tag_full=$tags_full[$i];
if (array_key_exists($tag_full, $TAGSEEN)) continue;
$TAGSEEN[$tag_full]=1;
$tag='';
$attrs=array();
if (!preg_match("/\s/",$tag_full)) { //if tag doesn't have attrs - don't parse attrs
$tag=$tag_full;
}else{ //now cut attrs <abcd attr1="aa a" attr2='b bb' attr3=ccc attr4> => abcd, attr1="aa a", attr2='b bb', attr3=ccc, attr4
preg_match_all("/((?:\S+\=\".*?\")|(?:\S+\='.*?')|(?:[^'\"\s]+)|(?:\S+\=\S*))/",$tag_full,$out,PREG_PATTERN_ORDER);
$attrs_raw=$out[1];
//dumparr($attrs_raw);
//rw($tag." => ".$attrs_raw);
//echo Dumper(@attrs);
$tag=$attrs_raw[0];
for($j=0;$j<count($attrs_raw);$j++){
$attr=$attrs_raw[$j];
//rw("search attr [$attr]");
if ( preg_match("/(?:(\S+)\=\"([^\"]*)\")|(?:(\S+)\=(\S*))|(?:(\S+)\='([^']*)')/", $attr, $match) ){ //split attr and it's value
$name=$match[1];
$value=$match[2];
//rw("attr [$attr] $name => $value");
$attrs[$name]=$value;
} else {
$attrs[$attr]="";
}
}
}
//echo "tag=$tag<br>\n";
//first - check attrs that control show tag or not
$eqvar='';
if ($attrs['if']){
$eqvar=$attrs['if'];
} elseif ($attrs['ifeq']){
$eqvar=$attrs['ifeq'];
} elseif ($attrs['ifneq']){
$eqvar=$attrs['ifneq'];
} elseif ($attrs['ifge']){
$eqvar=$attrs['ifge'];
} elseif ($attrs['ifle']){
$eqvar=$attrs['ifle'];
} elseif ($attrs['ifgee']){
$eqvar=$attrs['ifgee'];
} elseif ($attrs['iflee']){
$eqvar=$attrs['iflee'];
}
// rw("[$tag_full] eqvar=$eqvar, value=$attrs[value], hfvalue=".$hf[$eqvar]);
// print "IF=".(exists($attrs{ifeq}) && ($hf->{$eqvar} eq $attrs{value}))."\n";
// print "IF2=".(exists($attrs{ifneq}) && ($hf->{$eqvar} ne $attrs{value}))."\n";
if (!$eqvar or $eqvar && (array_key_exists('if', $attrs) && parse_page_if($attrs{'if'}, $hf)
or array_key_exists('ifeq', $attrs) && ($hf[$eqvar] == $attrs['value'])
or array_key_exists('ifneq', $attrs) && ($hf[$eqvar] != $attrs['value'])
or array_key_exists('ifge', $attrs) && ($hf[$eqvar] > $attrs['value'])
or array_key_exists('ifle', $attrs) && ($hf[$eqvar] < $attrs['value'])
or array_key_exists('ifgee', $attrs) && ($hf[$eqvar] >= $attrs['value'])
or array_key_exists('iflee', $attrs) && ($hf[$eqvar] <= $attrs['value'])
) ){
//check for inline template and prepare it
$inline_tpl='';
if ( array_key_exists('inline', $attrs) ){
$inline_tpl=get_inline_tpl($page, $tag, $tag_full);
}
//get var from GLOBALS, not from $hf
if ( array_key_exists('global', $attrs) ){
$hf[$tag]=$GLOBALS[$tag];
}
//get var from SESSION, not from $hf
if ( array_key_exists('session', $attrs) ){
$hf[$tag]=$_SESSION[$tag];
}
if ( array_key_exists($tag, $hf) ){
$tagvalue='';
//rw("$tag EXISTS");
if ( array_key_exists('repeat', $attrs) ){ //if this is 'repeat' tag parse as datarow
for($k=0;$k<count($hf[$tag]);$k++){
$hfrow=$hf[$tag][$k];
$tagvalue.=parse_page($basedir, tag_tplpath($tag,$tpl_name,array_key_exists('nolang', $attrs)), $hfrow, 'v', $inline_tpl);
}
}elseif (array_key_exists('sub', $attrs)){ //if this is 'sub' tag - parse it as independent subtemplate
$tagvalue=parse_page($basedir, tag_tplpath($tag,$tpl_name,array_key_exists('nolang', $attrs)), $hf[$tag], 'v', $inline_tpl);
}else{ //if usual tag - replace it
$tagvalue=$hf[$tag];
}
$page=tag_replace($page,$tag_full,$tagvalue, $attrs);
}elseif ( array_key_exists('var', $attrs) ){
//if tag doesn't exists in $hf and it's marked as variable (var)
//then don't make subparse and just replace to empty string for more performance
$page=tag_replace($page,$tag_full,"", $attrs);
}elseif ( array_key_exists('select', $attrs) ){
//this is special case for '<select>' HTML tag
$v=parse_select_tag($basedir, tag_tplpath($tag,$tpl_name,array_key_exists('nolang', $attrs)), $hf, $attrs);
$page=tag_replace($page,$tag_full, $v, $attrs);
}elseif ( array_key_exists('radio', $attrs) ){
//this is special case for '<index type=radio>' HTML tag
$v=parse_radio_tag($basedir, tag_tplpath($tag,$tpl_name,array_key_exists('nolang', $attrs)), $hf, $attrs);
$page=tag_replace($page,$tag_full, $v, $attrs);
}elseif ( array_key_exists('selvalue', $attrs) ){
//this is special case for displaying just one selected value (for '<select>' or '<index type=radio>' HTML tags
$v=parse_selvalue_tag($basedir, tag_tplpath($tag,$tpl_name,array_key_exists('nolang', $attrs)), $hf, $attrs);
$page=tag_replace($page,$tag_full, $v, $attrs);
}else{
//if tag is not set and not a var - then it's subtemplate in a file - parse it
//echo "$tag SUBPARSE<br>\n";
$v=parse_page($basedir, tag_tplpath($tag,$tpl_name,array_key_exists('nolang', $attrs)), $hf, 'v', $inline_tpl);
$page=tag_replace($page,$tag_full,$v, $attrs);
}
} else {
$page=tag_replace($page,$tag_full,"", $attrs);
// print "$tag not shown\n";
}
}
} //if tags empty
} //if tpl empty
//OLD CODE:
// $tags[]='ROOT_URL';
/*
foreach ($hf as $key=>$value){
rw("hf: $key=>$value");
}
*/
if ($out_filename && $out_filename!='v' && $out_filename!='s'){
$outdir=dirname($out_filename); //check dir
if (!is_dir($outdir)) mkdir($outdir, 0777);
$OUTFILE=fopen($out_filename, "w") or die("Can't open out [$out_filename] file");
fputs($OUTFILE, $page);
fclose($OUTFILE);
}
else{
if ($out_filename=='v'){ #variable mode
return $page;
}else{ #screen mode
print $page;
}
}
}
############## extract inline template from page
function get_inline_tpl(&$page, $tag, $tag_full){
$inline_tpl='';
#get inline template first
$restr='/'.preg_quote('<~'.$tag_full.'>', '/')."(.*?)".preg_quote('</~'.$tag.'>', '/').'/si';
if ( preg_match($restr, $page, $match) ){
$inline_tpl=$match[1];
}
return $inline_tpl;
}
##########
# parse IF condition
function parse_page_if($ifstr, $hf){
// rw("in: $ifstr");
#space at begin added for simpler regexp
$ifstr=" ".str_replace('>', '>', $ifstr); #make normal > sign from encoded
$patt='|[^\\\\]\$([\w\./\\\\]+)|';
$ifstr="return ".preg_replace($patt, '\$hf["$1"]', $ifstr).";";
// rw("evaling: $ifstr");
$result=eval($ifstr);
// rw("result: $result");
return $result;
}
//#################################
// tag to right template path for the parse_page
// IN: $tag,$tpl_name,$is_nolang
function tag_tplpath($tag,$tpl_name,$is_nolang){
global $l_add;
$add_path='';
$result=$tag;
// print "tpl_name=$tpl_name\n";
if (substr($tag,0,2)=='./'){ //if tag start from './' then take template from same dir as tpl_name
$add_path=$tpl_name;
$add_path=preg_replace("/[^\/]+$/", '', $add_path); //delete file name
$result=preg_replace("/^\.\//", '', $result); //delete ./ at the begin
}
// print "add_path=$add_path\n";
$result=$add_path.$result;
if (!preg_match("/\.[^\/]+$/", $tag)){ //set default .html extension (if extension is not set)
if ($is_nolang){
$result="$result.html"; //don't add path to multilang if flag nolang is set
} else {
$result="$result$l_add.html";
}
}
return $result;
}
//#################################
// tag replacer for the parse_page
// IN: $page, $tag, $value, $attrs - references
function tag_replace($page, $tag_full, $value, $attrs){
// echo "tag_replace tag=$tag<br>";
// echo "tag_replace value=$value<br>";
// echo "tag_replace page=".strlen($page)."<br>";
if (array_key_exists('htmlescape', $attrs)) $value=htmlescape($value);
if ( array_key_exists('inline', $attrs) ){ //check if this inline template - replace it in special way
#get just tag without attrs
$tag=$tag_full;
if ( preg_match('/^(\S+)/', $tag, $match) ){
$tag=$match[1];
}
#replace tag+inline tpl+close tag
$restr='/'.preg_quote('<~'.$tag_full.'>', '/').".*?".preg_quote('</~'.$tag.'>', '/').'/si';
#quote special PHP $ and \\12345
$value=str_replace('$', '\$', $value);
$value=preg_replace('/\\\\(d+)/', '\\\\$1', $value);
return preg_replace($restr, $value ,$page);
} else{
//return preg_replace("/<~".preg_quote($tag_full,"/").">/", $value.'', $page);
return str_replace("<~$tag_full>",$value,$page);
}
}
############### parse select tag
# output <option value="XX">YY
function parse_select_tag($basedir, $tpl_path, $hf, $attrs){
global $site_templ;
$sel_value=$hf[ $attrs['select'] ];
if (!preg_match("/^\//",$tpl_path)) $tpl_path="$basedir/$tpl_path";
$lines=preg_split("/[\r\n]+/",file_get_contents("$site_templ$tpl_path"));
$result;
for($i=0;$i<count($lines);$i++){
list ($value,$desc)=preg_split("/\|/",$lines[$i]);
if (!$value && !$desc) continue;
parse_lang($desc);
if ($value==$sel_value){
$result.="<option value=\"$value\" selected>$desc</option>\n";
}
else{
$result.="<option value=\"$value\">$desc</option>\n";
}
}
return $result;
}
############### parse radio tag
# output <input type=radio ...>label
function parse_radio_tag($basedir, $tpl_path, $hf, $attrs){
global $site_templ, $root_url;
$sel_value=$hf[ $attrs['radio'] ];
$name =$attrs['name'];
$delim=$attrs['delim'];
if (!preg_match("/^\//",$tpl_path)) $tpl_path="$basedir/$tpl_path";
$lines=preg_split("/[\r\n]+/",file_get_contents("$site_templ$tpl_path"));
$result;
for($i=0;$i<count($lines);$i++){
list ($value,$desc)=preg_split("/\|/",$lines[$i]);
if (!$value && !$desc) continue;
parse_lang($desc);
$desc=preg_replace("/<~ROOT_URL>/i", $root_url, $desc);
if ($value==$sel_value){
$result.="<input type='radio' name=\"$name\" id=\"$name$i\" value=\"$value\" checked='checked'><label for=\"$name$i\">$desc</label>$delim\n";
}
else{
$result.="<input type='radio' name=\"$name\" id=\"$name$i\" value=\"$value\"><label for=\"$name$i\">$desc</label>$delim\n";
}
}
return $result;
}
############
# output - just string
function parse_selvalue_tag($basedir, $tpl_path, $hf, $attrs){
global $site_templ;
$sel_value=$hf[ $attrs['selvalue'] ];
if (!preg_match("/^\//",$tpl_path)) $tpl_path="$basedir/$tpl_path";
$lines=preg_split("/[\r\n]+/",file_get_contents("$site_templ$tpl_path"));
$result;
for($i=0;$i<count($lines);$i++){
list ($value,$desc)=preg_split("/\|/",$lines[$i]);
if (!$value && !$desc) continue;
if ($value==$sel_value){
parse_lang($desc);
$result=$desc;
last;
}
}
return $result;
}
#########
# compare to full_tags and return -1, 0, 1 for usort function (so 'inline' and 'sub' tags will be parsed first)
function parse_page_sort_tags($a, $b){
$a_inline=0;
$b_inline=0;
if ( preg_match('/inline|sub\b/', $a) ) $a_inline=1;
if ( preg_match('/inline|sub\b/', $b) ) $b_inline=1;
if ($a_inline == $b_inline) {
return 0;
}
return ($a_inline > $b_inline) ? -1 : 1;
}
//#################################
// load file into variable
// CACHED!!!
global $FILE_CACHE;
$FILE_CACHE=array();
function precache_file($infile, $isdie=0){
global $FILE_CACHE;
$result='';
if ( array_key_exists($infile, $FILE_CACHE) ){
return $FILE_CACHE[$infile];
}
if (file_exists($infile)){
$result=file_get_contents($infile);
} else {
if ($isdie){
die("Can't open template [$infile] file");
}
}
$FILE_CACHE[$infile]=$result;
return $result;
}
##########################################################
#Cached Template parser
#
function parse_cache_template($page,$hf,$out_filename=''){
global $root_url;
preg_match_all("/<~([^>]+)>/",$page,$out,PREG_PATTERN_ORDER);
$tags=$out[1];
$tags[]='ROOT_URL';
$hf['ROOT_URL']=$root_url;
foreach ($tags as $key=>$tag){
$page=preg_replace("/<~".preg_quote($tag,"/").">/",$hf[$tag],$page);
}
if ($out_filename && $out_filename!='s'){
$outdir=dirname($out_filename); #check dir
if (!is_dir($outdir)) mkdir($outdir, 0777);
$OUTFILE=fopen($out_filename, "w") or die("Can't open out [$out_filename] file");
fputs($OUTFILE, $page);
fclose($OUTFILE);
}
else{
if ($out_filename==''){ #variable mode
return $page;
}else{ #screen mode
print $page;
}
}
}
############## HELPER UTILS
#############
function htmlescape($str){
$str=preg_replace("/&/",'&', $str);
$str=preg_replace('/"/','"', $str);
$str=preg_replace("/'/",''', $str);
$str=preg_replace("/</",'<', $str);
$str=preg_replace("/>/",'>', $str);
return $str;
}
#############
function htmlescape_back($str){
$str=preg_replace("/&/" ,'&', $str);
$str=preg_replace('/"/','"', $str);
$str=preg_replace("/'/","'", $str);
$str=preg_replace("/</" ,'<', $str);
$str=preg_replace("/>/" ,'>', $str);
return $str;
}
################################################ LANGUAGE UTILS
# parse all lang stings in large text
# BY REFERENCE!
function parse_lang(&$page){
$page=preg_replace_callback("/\`([^\`]*)\`/", "replace_lang", $page);
}
#########
# USES GLOBAL $LANG
function replace_lang($matches){
return lng($matches[1]);
}
#########
function lng($str){
global $site_templ, $LANG;
$result='';
$lang_file="$site_templ/lang/$LANG.txt";
if ($LANG){
$LANG_STR=load_lang($lang_file);
$lang_str=$LANG_STR[ $str ];
$result=$lang_str;
}
if (!$result){
$result=$str; #if no language - return original string
update_lang($result, 'en'); #if no translation - add string to en.txt file
}
return $result;
}
######### precache language file into hash
global $LANG_CACHE;
$LANG_CACHE=array();
#$LANG - hash reference
function load_lang($infile, $isdie=0){
global $LANG_CACHE;
$result='';
if ( array_key_exists($infile, $LANG_CACHE) ){
return $LANG_CACHE[$infile];
}
if (file_exists($infile)){
$result=file_get_contents($infile);
} else {
if ($isdie){
die("Can't open language [$infile] file");
}
}
$arr=preg_split("/[\r\n]/", $result);
$hash=array();
foreach($arr as $value){
$a=preg_split("/ +\=\=\= +/", $value);
$hash[ $a[0] ]=$a[1];
}
$LANG_CACHE[$infile]=$hash;
return $hash;
}
###########
function update_lang($str, $lang){
global $site_templ, $LANG_CACHE;
$lang_file="$site_templ/lang/$lang.txt";
$LANG_STR=load_lang($lang_file);
if ( !array_key_exists($str, $LANG_STR) ){
$LANG_CACHE[$lang_file][$str]="";
add_lockfile($lang_file, "$str === \n");
}
}
?>