Location: PHPKode > projects > PhpProjectMaster > www/inc/sitetpl.php
<?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 &gt; (for faster tag parsing)
#    example: <~aaa if="$bbb < 100"> <~ccc if="$bbb &gt; 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="&nbsp;">
# 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('&gt;', '>', $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("/&/",'&amp;', $str);
 $str=preg_replace('/"/','&quot;', $str);
 $str=preg_replace("/'/",'&#039;', $str);
 $str=preg_replace("/</",'&lt;', $str);
 $str=preg_replace("/>/",'&gt;', $str);

 return $str;
}
#############
function htmlescape_back($str){

 $str=preg_replace("/&amp/"  ,'&', $str);
 $str=preg_replace('/&quot;/','"', $str);
 $str=preg_replace("/&#039;/","'", $str);
 $str=preg_replace("/&lt;/"  ,'<', $str);
 $str=preg_replace("/&gt;/"  ,'>', $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");
 }
}


?>
Return current item: PhpProjectMaster