Location: PHPKode > scripts > Template Interface Engine > template-interface-engine/interface4.class.php
<?php
///////////////////////////////////////////////////////////////////////////////
// Template Interface Engine
//
// Version 1.0.1
// This is the version for PHP4 Compatibility
// Copyright (c) 2005-2006 by Muhamad Zakaria <hide@address.com>
//
// This template engine would interface back-end code (as business logic layer) and
// html code (currently known as presentation layer) which is wrote in separated files.
//
// This class implements a template engine that uses regular expressions
// to locate placeholder marks.
//
// The class supports template loops and uses event driven callback functions
// to set variables inside the loop sections.
// Outside loop sections the variable values can be set by using internal Var array
//
// The tags used by this engine are placed enclosed by comment HTML tag inside
// the template file part, so the templates are capable to be edited later
// by any favourites WYSIWYG HTML Editor.
// This engine is equipped by the configuration file for custom behaviour.
//
// This template engine supports:
// - INCLUDE tags
// - LOOP block
// - IF-ELSEIF-ELSE block
// - Configuration file for customization
// - Handle multi template files for header, body and footer separately
// - The output could send result to stdout, file or as string (by set as '_PIPE')
// - The blocks support to be recursively applied
// - and, this is for PHP4 compatibility
//
// Template Interface Engine is released under the terms of the LGPL license
// http://www.gnu.org/copyleft/lesser.html#SEC3
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
///////////////////////////////////////////////////////////////////////////////

	if(!defined('_PIPE')) define('_PIPE', '_PIPE');

	class CInterface {
		// file name of template file
		var $TemplateFile;

		// handle header and footer information from the files if set
		var $HeaderPart;
		var $FooterPart;
		var $HeaderSet;
		var $FooterSet;

		// variables template is saved here
		var $Var = array();
		var $Executed;
		var $OutStr;

		// handles user defined function have been set
		var $LoopFunction = array();

		var $Config;

		function CInterface($template = '') {
			$this->TemplateFile = $template;
			$this->HeaderSet = FALSE;
			$this->FooterSet = FALSE;
			$this->Executed = FALSE;
			$this->OutStr = '';

			require ('interface.config.php');
			$config['open_tag'] = preg_quote($config['open_tag'], "/");
			$config['close_tag'] = preg_quote($config['close_tag'], "/");
			$config['open_var'] = preg_quote($config['open_var'], "/");
			$config['close_var'] = preg_quote($config['close_var'], "/");
			$config['spec_open_var'] = preg_quote($config['spec_open_var'], "/");
			$config['spec_close_var'] = preg_quote($config['spec_close_var'], "/");
			$this->Config = $config;
		}

		function SetHeader($fileheader) {
			$headerfile = file_get_contents($fileheader);
			
			if($this->SplitBody($headerfile, $headerpart)) {
				$this->HeaderPart = $headerpart[1];
				$this->HeaderSet = TRUE;
			}
		}

		function SetFooter($filefooter) {
			$footerfile = file_get_contents($filefooter);
			
			if($this->SplitBody($footerfile, $footerpart)) {
				$this->FooterPart = $footerpart[1];
				$this->FooterSet = TRUE;
			}
		}

		function CompleteOut($str) {
			if(!$this->SplitBody($str, $body)) return $str;

			// insert footer part in body array
			if ($this->FooterSet) { array_splice($body, 2, 0, $this->FooterPart); }
			// insert header part in body array
			if ($this->HeaderSet) { array_splice($body, 1, 0, $this->HeaderPart); }

			return implode('', $body);
		}

		// function to split 'str' to be parts in <body*> tag
		// the 'array' is the split result in array
		// return TRUE if <body*> tag found, otherwise FALSE
		function SplitBody($str, &$array) {
			if(!preg_match('|<body(.*)>|sU', $str, $tagbody)) return FALSE;
			if(empty($tagbody[1])) {
				$splitbody = preg_split('/<body>(.+)<\/body>/sU', $str);
				preg_match('/<body>(.+)<\/body>/sU', $str, $body);
			} else {
				$splitbody = preg_split('/<body[^>]+>(.+)<\/body>/sU', $str);
				preg_match('/<body[^>]+>(.+)<\/body>/sU', $str, $body);
			}
			$array[] = $splitbody[0].$tagbody[0];
			$array[] = $body[1];
			$array[] = '</body>'.$splitbody[1];

			return TRUE;
		}

		function ProcessInclude($str) {
			preg_match_all('/'.$this->Config['open_tag'].'include:\[(.+)\]'.$this->Config['close_tag'].'/sU', $str, $includetag, PREG_SET_ORDER);
			foreach($includetag as $include) {
				$thefile = $include[1];
				preg_match_all('/'.$this->Config['open_var'].'(.+)'.$this->Config['close_var'].'/sU', $thefile, $vars, PREG_SET_ORDER);
				foreach($vars as $var)
					$thefile = str_replace($var[0], isset($this->Var[$var[1]])?$this->Var[$var[1]]:'', $thefile);
				$str = str_replace($include[0], (file_exists($thefile))?file_get_contents($thefile):'', $str);
			}
			return $str;
		}

		function RegisterFunction($functionname, $loopfor) {
			$this->LoopFunction[$loopfor] = $functionname;
		}

		function Identify($str, &$ret) {
			if(!is_array($ret)) $ret = array();
			$i = 0;

			$thetag = array();
			if(preg_match_all('|'.$this->Config['open_tag'].'(.+):.+'.$this->Config['close_tag'].'|U', $str, $tag) !== FALSE)
				$thetag = array_values(array_intersect($tag[1], array('if', 'elseif', 'else', 'loop')));
			if(count($thetag) > 0) {
				switch($thetag[0]) {
					case 'if':
						preg_match('/'.$this->Config['open_tag'].'if:\[(.+)\].*=.*\[(.+)\]'.$this->Config['close_tag'].'/sU', $str, $tagif);
						$currkey = $tagif[1];
						$body = preg_split('/'.$this->Config['open_tag'].'if:\['.$currkey.'\].*'.$this->Config['close_tag'].'.+'.$this->Config['open_tag'].'\/if:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str);
						if($body[0] <> '') {
							while(array_key_exists('::[]'.$i, $ret)||array_key_exists('var'.$i, $ret)) $i++;
							$match = preg_match_all('/'.$this->Config['open_var'].'(.+)'.$this->Config['close_var'].'/sU', $body[0], $vars);
							if($match > 0) $ret['var'.$i] = array_values(array_unique($vars[1]));
							$ret['::[]'.$i] = $body[0];
						}
						preg_match('/'.$this->Config['open_tag'].'if:\['.$currkey.'\].*'.$this->Config['close_tag'].'(.+)'.$this->Config['open_tag'].'\/if:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str, $subloop);
						$this->Identify($subloop[1], $arr);
						$ret['if:'.$currkey.'=['.$tagif[2].']>>>'] = $arr;
						if($body[1] <> '') $this->Identify($body[1], $ret);
						break;
					case 'elseif':
						preg_match('/'.$this->Config['open_tag'].'elseif:\[(.+)\].*=.*\[(.+)\]'.$this->Config['close_tag'].'/sU', $str, $tagelseif);
						$currkey = $tagelseif[1];
						$body = preg_split('/'.$this->Config['open_tag'].'elseif:\['.$currkey.'\].*'.$this->Config['close_tag'].'.+'.$this->Config['open_tag'].'\/elseif:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str, 2);
						if($body[0] <> '') {
							while(array_key_exists('::[]'.$i, $ret)||array_key_exists('var'.$i, $ret)) $i++;
							$match = preg_match_all('/'.$this->Config['open_var'].'(.+)'.$this->Config['close_var'].'/sU', $body[0], $vars);
							if($match > 0) $ret['var'.$i] = array_values(array_unique($vars[1]));
							$ret['::[]'.$i] = $body[0];
						}
						preg_match('/'.$this->Config['open_tag'].'elseif:\['.$currkey.'\].*'.$this->Config['close_tag'].'(.+)'.$this->Config['open_tag'].'\/elseif:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str, $subloop);
						$this->Identify($subloop[1], $arr);
						// the next same expression in elseif field will be lost
						if(!array_key_exists('elseif:'.$currkey.'=['.$tagelseif[2].']>>>', $ret))
							$ret['elseif:'.$currkey.'=['.$tagelseif[2].']>>>'] = $arr;
						if($body[1] <> '') $this->Identify($body[1], $ret);
						break;
					case 'else':
						preg_match('/'.$this->Config['open_tag'].'else:\[(.+)\]'.$this->Config['close_tag'].'/sU', $str, $tagelse);
						$currkey = $tagelse[1];
						$body = preg_split('/'.$this->Config['open_tag'].'else:\['.$currkey.'\]'.$this->Config['close_tag'].'.+'.$this->Config['open_tag'].'\/else:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str);
						if($body[0] <> '') {
							while(array_key_exists('::[]'.$i, $ret)||array_key_exists('var'.$i, $ret)) $i++;
							$match = preg_match_all('/'.$this->Config['open_var'].'(.+)'.$this->Config['close_var'].'/sU', $body[0], $vars);
							if($match > 0) $ret['var'.$i] = array_values(array_unique($vars[1]));
							$ret['::[]'.$i] = $body[0];
						}
						preg_match('/'.$this->Config['open_tag'].'else:\['.$currkey.'\]'.$this->Config['close_tag'].'(.+)'.$this->Config['open_tag'].'\/else:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str, $subloop);
						$this->Identify($subloop[1], $arr);
						$ret['else:'.$currkey.'>>>'] = $arr;
						if($body[1] <> '') $this->Identify($body[1], $ret);
						break;
					case 'loop':
						preg_match('/'.$this->Config['open_tag'].'loop:\[(.+)\]'.$this->Config['close_tag'].'/sU', $str, $tagloop);
						$currkey = $tagloop[1];
						$body = preg_split('/'.$this->Config['open_tag'].'loop:\['.$currkey.'\]'.$this->Config['close_tag'].'.+'.$this->Config['open_tag'].'\/loop:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str, 2);
						if($body[0] <> '') {
							while(array_key_exists('::[]'.$i, $ret)||array_key_exists('var'.$i, $ret)) $i++;
							$match = preg_match_all('/'.$this->Config['open_var'].'(.+)'.$this->Config['close_var'].'/sU', $body[0], $vars);
							if($match > 0) $ret['var'.$i] = array_values(array_unique($vars[1]));
							$ret['::[]'.$i] = $body[0];
						}
						preg_match('/'.$this->Config['open_tag'].'loop:\['.$currkey.'\]'.$this->Config['close_tag'].'(.+)'.$this->Config['open_tag'].'\/loop:\['.$currkey.'\]'.$this->Config['close_tag'].'/sU', $str, $subloop);
						$this->Identify($subloop[1], $arr);
						$nom = 0;
						while(array_key_exists('loop:'.$nom.'<<<'.$currkey.'>>>', $ret)) $nom++;
						$ret['loop:'.$nom.'<<<'.$currkey.'>>>'] = $arr;
						if($body[1] <> '') $this->Identify($body[1], $ret);
						break;
				}
			} else {
				while(array_key_exists('::[]'.$i, $ret)||array_key_exists('var'.$i, $ret)) $i++;
				$match = preg_match_all('/'.$this->Config['open_var'].'(.+)'.$this->Config['close_var'].'/sU', $str, $vars);
				if($match > 0) $ret['var'.$i] = array_values(array_unique($vars[1]));
				$ret['::[]'.$i] = $str;
			}
		}

		// not implemented yet...
		// this function will parse for error syntax and print check result as error messages if any
		function CekSyntax() {
/*
			// print undefined variables error if found
			preg_match_all('/'.$this->Config['open_var'].'(.+)'.$this->Config['close_var'].'/sU', $templ, $var_found);
			$undefined = array_diff($var_found[1], array_keys($this->Var));
			if(count($undefined) > 0) {
				$out .= "<div style=\"color:red\"><b><i>Error: These variables are not defined:</i><br />";
				$i = 0;
				foreach($undefined as $indx=>$val) {
					$out .= (++$i).". ".$var_found[1][$indx]." (or \"".$var_found[0][$indx]."\" in the template file.)<br />";
				}
				$out .= "</b></div>\n";
			}
*/
		}

		// when GenerateLoop is called, then must be a count of rows and colomns in 
		// Var['name_of_the_loop_segment'] as array table variables have been prepared before
		function GenerateLoop($loopblock, $name) {
			if(!is_array($this->Var[$name][0])) return '';

			$func = get_defined_functions();
			$rows = '';
			$row = 0;
			foreach($this->Var[$name] as $indX => $dataX) {
				// defines variables and values inside the LOOP block
				$invars = array();
				if(isset($this->LoopFunction[$name]) && is_array($dataX)) {
					// returned values only affect to the value after below, not to the actual array
					$returned = call_user_func($this->LoopFunction[$name], $dataX);
					if(!is_null($returned)) $dataX = $returned;
				} elseif(function_exists($name) && is_array($dataX)) {
					// this is for backward compatibility only. however, this will deprecated
					$returned = call_user_func($name, $dataX);
					if(!is_null($returned)) $dataX = $returned;
				}
				foreach($dataX as $indY => $dataY) $invars[$name.$indY] = $dataY;
				// execute callback user function with row based if defined
				foreach($this->LoopFunction as $thefunc => $thefunc_name)  {
					if(($thefunc != $name)&&(strncmp($thefunc, $name, strlen($name)) == 0)) {
						if(preg_match('/'.$name.'_(.+)/', $thefunc, $thevar)) {
							$recv = call_user_func($thefunc_name, $row);
							$invars[$thevar[1]] = $recv;
						}
					}
				}
				// this is for backward compatibility only. however, this will deprecated
				foreach($func['user'] as $thefunc)  {
					if(($thefunc != $name)&&(strncmp($thefunc, $name, strlen($name)) == 0)) {
						if(preg_match('/'.$name.'_(.+)/', $thefunc, $thevar)) {
							if(isset($this->LoopFunction[$thefunc])) continue;
							$recv = call_user_func($thevar[0], $row);
							$invars[$thevar[1]] = $recv;
						}
					}
				}
				$str = '';
				foreach($loopblock as $key=>$value) {
					if(preg_match('/^if:(.+)=\[(.+)\]>>>$/', $key, $ifvar)&&is_array($value)) {
						if($this->EvaluateFormula($ifvar[2], $invars)) $str .= $this->Process($value, $invars);
						else {
							$elseifdone = FALSE;
							$elseifkey = array_values(preg_grep('/elseif:'.$ifvar[1].'=\[.+\]>>>/', array_keys($loopblock)));
							foreach($elseifkey as $thekey) {
								if(preg_match('/elseif:'.$ifvar[1].'=\[(.+)\]>>>/', $thekey, $elseif)&&is_array($loopblock[$thekey]))
									if($this->EvaluateFormula($elseif[1], $invars)) {
										$str .= $this->Process($loopblock[$thekey], $invars);
										$elseifdone = TRUE;
										break;
									}
							}
							if(!$elseifdone&&array_key_exists('else:'.$ifvar[1].'>>>', $loopblock)&&is_array($loopblock['else:'.$ifvar[1].'>>>']))
								$str .= $this->Process($loopblock['else:'.$ifvar[1].'>>>'], $invars);
						}
					}
					elseif(preg_match('/loop:.+<<<(.+)>>>/', $key, $thename))
						$str .= $this->GenerateLoop($value, $thename[1]);
					elseif(substr($key, 0, 4) == '::[]') $str .= $value;
				}
				foreach($invars as $inkey=>$inval)
					$str = str_replace('{$'.$inkey.'$}', $inval, $str);
				$rows .= $str;
				$row++;
			}
			return $rows;
		}

		function EvaluateFormula($formula, $values) {
			// detects all PHP function and language construct
			// the result array list will be used to parse any functions which may be used in expression
			$intfunc = get_defined_functions();
			$intfunc = $intfunc[internal];
			$intfunc = array_merge($intfunc, $this->Config['deny_construct']);
			// avoid using PHP functions in expression
			$formula = preg_replace(preg_replace('/(.+)/', '/\b\\1\b/i', array_values(array_diff($intfunc, $this->Config['allow_function']))), '', $formula);

			preg_match_all("/[\042\047]*[A-Za-z0-9\047\137]+[\042\047]*/", $formula, $vars);
			// grap the quoted string and do something here
			$spec_str = array_map(create_function('$a', 'return trim($a, "\x22\x27");'), preg_grep('/[\042\047]/', $vars[0]));
			$formula = str_replace(array_values($spec_str), str_replace("'", "`", array_values(str_replace("''", "'", $spec_str))), $formula);
			// grap the valid variable to array list
			$validvars = array_values(array_diff(array_unique(preg_grep("/[^0-9]/", $vars[0])), array_merge(preg_grep('/[\042\047]|\b(TRUE|FALSE|AND|NAND|OR|NOR|XOR|NOT)\b/i', $vars[0]), $this->Config['allow_function'])));
			foreach($validvars as $var) {
				if(isset($values[$var])&&!is_array($values[$var]))
					$formula = str_replace($var, !is_numeric($values[$var])?"'".str_replace("'", "`", $values[$var])."'":$values[$var], $formula);
				elseif(isset($this->Var[$var])&&is_array($this->Var[$var]))
					$formula = str_replace($var, count($this->Var[$var]), $formula);
				else
					$formula = str_replace($var, "''", $formula);
			}
			return(eval('return('.$formula.');'));
		}

		// process array successively and reindex it
		function Process($array, $variables) {
			$aoutput = array();
			while(list($key, $value) = each($array)) {
				if(preg_match('/var(.+)/', $key, $var)) {
					foreach($value as $val)
						$array['::[]'.$var[1]] = str_replace('{$'.$val.'$}', $variables[$val], $array['::[]'.$var[1]]);
					unset($array[$key]);
				}
				elseif(preg_match('/^if:(.+)=\[(.+)\]>>>$/', $key, $ifvar)&&is_array($value)) {
					unset($array[$key]);
					if($this->EvaluateFormula($ifvar[2], $variables)) $aoutput[] = $this->Process($value, $variables);
					else {
						$elseifdone = FALSE;
						$elseifkey = array_values(preg_grep('/elseif:'.$ifvar[1].'=\[.+\]>>>/', array_keys($array)));
						while(count($elseifkey) > 0) {
							$arrayelseif = $array[$elseifkey[0]];
							unset($array[$elseifkey[0]]);
							if(!$elseifdone&&preg_match('/elseif:'.$ifvar[1].'=\[(.+)\]>>>/', $elseifkey[0], $elseif)&&is_array($arrayelseif))
								if($this->EvaluateFormula($elseif[1], $variables)) {
									$aoutput[] = $this->Process($arrayelseif, $variables);
									$elseifdone = TRUE;
								}
							$elseifkey = array_values(preg_grep('/elseif:'.$ifvar[1].'=\[.+\]>>>/', array_keys($array)));
						}
						if(!$elseifdone&&array_key_exists('else:'.$ifvar[1].'>>>', $array)&&is_array($array['else:'.$ifvar[1].'>>>'])) {
							$arrayelse = $array['else:'.$ifvar[1].'>>>'];
							unset($array['else:'.$ifvar[1].'>>>']);
							$aoutput[] = $this->Process($arrayelse, $variables);
						}
					}
				}
				elseif(preg_match('/loop:.+<<<(.+)>>>/', $key, $thename)&&is_array($value)) {
					unset($array[$key]);
					$aoutput[] = $this->GenerateLoop($value, $thename[1]);
				}
				elseif(substr($key, 0, 4) == '::[]') {
					$aoutput[] = $value;
					unset($array[$key]);
				}
			}
			$output = '';
			foreach($aoutput as $value) $output .= $value;
			return $output;
		}

		// this function could be called by external for some needed in some cases
		// and, this is safe to be called many times as you need
		function Execute() {
			// get the content of template file
			$templ = file_get_contents($this->TemplateFile);

			// concat with header and footer file if set
			$templ = $this->CompleteOut($templ);

			$templ = $this->ProcessInclude($templ);

			// compatibility for prior version ;)
			require ('interface.config.php');
			if(preg_match_all('/<!--{###(.+)-->/sU', $templ, $oldloop) !== FALSE) {
				foreach($oldloop[1] as $oldtag)
					$templ = preg_replace('/<!--{###'.$oldtag.'-->(.+)<!--'.$oldtag.'###}-->/sU',
					$config['open_tag'].'loop:['.$oldtag.']'.$config['close_tag'].'\\1'.$config['open_tag'].'/loop:['.$oldtag.']'.$config['close_tag'], $templ);
			}

			// first identify the template and give result in array
			$this->Identify($templ, $ident);

			// proceed array result and back it with all variables have been applied
			$out = $this->Process($ident, $this->Var);

			// clean up any unneccesary variables which is not defined
			$out = preg_replace('/'.$this->Config['open_var'].'.+'.$this->Config['close_var'].'/sU', '', $out);

			// restore special chars into without backslash
			$out = preg_replace('/'.$this->Config['spec_open_var'].'(.+)'.$this->Config['spec_close_var'].'/sU', $config['open_var'].'\\1'.$config['close_var'], $out);

			$this->OutStr = $out;
			$this->Executed = TRUE;
		}

		function Output($outfile = '', $onlybody = TRUE) {
			if(!$this->Executed) $this->Execute();

			if(empty($outfile)) {
				// send output to stdout
				print $this->OutStr;
			} elseif($outfile == '_PIPE') {
				// this support for string output instead of file or stdout as output
				if($onlybody && $this->SplitBody($this->OutStr, $body)) return $body[1];
				else return $this->OutStr;
			} else {
				// this support for file output instead of stdout as output
				$fp = fopen($outfile, ($onlybody === TRUE) ? "w" : $onlybody);
				fwrite($fp, $this->OutStr);
				fclose($fp);
			}
		}
	}

	// additional function when above class is used by mysql database
	function mysql_to_array($recource) {
		$table = array();
		while($datatabel = mysql_fetch_array($recource))
			$table = array_merge($table, array($datatabel));
		foreach($table as $X=>$dataX) {
			foreach($dataX as $Y=>$cell) {
				$table[$X][$Y] = ($cell == '')?'&#160;':$cell;
			}
		}
		return $table;
	}

?>
Return current item: Template Interface Engine