<?php

/*
	Jerous'-FrantíX' Template Machine (JFTM)
	========================================
	This text gives some introductory insights in the code.
	
	Some definitions:
	 vartag: a tag of the format {{%vartag_type%(':' | '_')%vartag_name%}}
		, where (':' | '_') means after %vartag_type% comes a ':' or a '_'.
	 plugin: a class based on the class Template (class found in this file)
	
	The JFTM is a class, which can be easily extended. These are the methods that
	 should ('+') or could ('*') be overwritten in the childclass.
	 * set_vartag($vartag, $value): this can be used when some data has to be stored, like variables
		$vartag: the name of the vartag, and is the same as $vartag of get_vartag_value
	 + get_vartag_value($vartag): this method will be called from Template whenever a vartag is found.
		$vartag: $vartag will be set to %vartag_name% (i.e. the vartag, without the vartagtype)
	 * get_vartag_data($vartag): use this when the value differs from the stored data. E.g. when vars,
		the data is stored in an other way than it is retrieved through get_vartag_value.
	 + get_info(): this method displays info about the plugin. It consists of an array, with at least these values:
		"info"=>some info about the plugin, "version"=>the current version of the plugin. "name" will be automatically
		added, using the class its name
	
	The current class is called Template. If you create a subclass, it should start with 'Template_', followed by a
	 vartag_type. It is this vartag_type that can be used in the templates, and will be called upon coming across
	 by the JFTM base class.
	E.g.: you want to create a vartag_type Date. You then create a file (if the filename starts with 'tpl_', it is automatically
	 included), and add a class named 'Template_Date', based on the class Template, thus you get:
	 class Template_Date extends Template
	 In this file you then create [some of ]the functions above.
	
	More detailed description:
	JFTM searches (through a regex) after the deepest nested tags, and looks of what type it is. If it is a known
	 type, then it will load the plugin (if it has not been loaded before). Then, it passes the %vartag_name% to the
	 pluginclass using the 'polymorph' method get_vartag_value, which performs some actions on the string, and returns
	 the result. This result is then substituted with the vartag.
	JFTM does these steps for every tag. When the first round is completed, it checks if something has been modified. If yes
	 do the previous again, till nothing is modified anymore, or the maximum number of loops has been exceeded.
	This result is the passed to the method get_template, which returns it to the caller.
	
*/

//some vars and settings ...

	/*this is the folder in which to look for pluginfiles.
		pluginfiles which match against the pattern 'tpl_*.inc' are automatically included.
	*/
	define(FOLDER_JFTM_PLUGINS, './tpl_plugins');
	define(FOLDER_TEMPLATES, './tpl');
	/*use the SKIP_TAG between html-tags, to exclude that part from processing. 
		E.g. <notpl>{{V_name}}</notpl> will remain the same
	*/
	define(SKIP_TAG, 'notpl');

	define(CLASS_PREFIX, "Template_");
	
	define(MAX_DEPTH, 5);
	
	
	/*
		REGEX's
	*/
	//this regex will be used for matching the deepest vartag
	define(VARTAG_REGEX, '{{{([a-zA-Z]+?)[_:]([^{}]+?)}}}');
	//following constants are indices, for use in the returnarray of preg_match_all
		define(RE_WHOLE, 0); //whole string
		define(RE_TYPE, 1); //the vartagtype (i.e. the part before ':' or '_')
		define(RE_NAME, 2); //the vartagname

	define(SKIP_REGEX, '/<'.SKIP_TAG.'\>(.*?)\<\/'.SKIP_TAG.'>/');

	
	//do *not* modify this private var
	$m_objects = array();



	//automatically require all classfiles
	require_plugins(FOLDER_JFTM_PLUGINS);


class Template
{
	/* @return: the template, with all values filled in (if possible)
	   @param tplname: name of the template
	   @param disabled: array containg as values the vartags to temporary skip from processing
	   @desc: read and fill in all (possible) values of a template
	*/
	function get_template($tplname, $disabled = array())
	{
		//read current template ...
		$fstring = read_template($tplname);	
		
		//and process ...
		$fstring = $this->process($fstring, MAX_DEPTH, $disabled);
		
		return $fstring;
	}
	
	//process the templatestring
	function process($string, $max_level_deep, $disabled = array())
	{
		global $m_objects;
		if ($max_level_deep < 0)		return $string;
		
		if (is_array($disabled)==false)	$disabled = array($disabled);
		
		$max_loop_count = $max_level_deep;
		$done = false;
		
		while ($done==false and $max_loop_count-->0)
		{
			$done = true;
			//perform some matching: the first is for the deepest nested tags, the second regex for the regios to skip
			preg_match_all(VARTAG_REGEX, $string, $tags, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
			preg_match_all(SKIP_REGEX, $string, $skips, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
			
			//remove all matchingtags in the skipregios
			foreach($skips as $skip)
			{
				foreach($tags as $index=>$tag)
				{
					if ($skip[0][1]<$tag[0][1]
						and $skip[0][1]+strlen($skip[0][0])>$tag[0][1])
							unset($tags[$index]);
				}
			}
			//make sure we start with the last tag
			$tags = array_reverse($tags);
			foreach($tags as $tag)
			{
				if (in_array($tag[RE_TYPE][0]."_".$tag[RE_NAME][0], $disabled)==false
					and in_array($tag[RE_TYPE][0].":".$tag[RE_NAME][0], $disabled)==false)
				{
					//check if there is some class for the prefix(='prefixclass')
					$class_name = $tag[RE_TYPE][0];
					$class = CLASS_PREFIX.$class_name;
					
					//check if the prefixclass has been initialized, if no, do it now
					if ($this->check_object($class_name))
					{	
						//process the var ...
						$mod = $m_objects[$class_name]->get_vartag_value($tag[RE_NAME][0], $max_level_deep - 1);
						
						if (substr($mod, 0, 2)=='{{' and substr($mod, strlen($mod)-2)=='}}')
							$mod = convert_tag_chars($mod);
						//...and apply the changes!
						$string = substr($string, 0, $tag[RE_WHOLE][1])
							.$mod.substr($string, $tag[RE_WHOLE][1]
							+strlen($tag[RE_WHOLE][0]));
						$done=false;
					}
				}
			}
		}
		
		$string = deconvert_tag_chars($string);
		//now remove all skiptags
// 		$string = str_replace(array('<'.SKIP_TAG.'>', '</'.SKIP_TAG.'>'), "", $string);
		
		return $string;
	}
	
		
	
	function set_vartag($vartag, $vartag_value)
	{
		global $m_objects;
		$type = get_vartag_type($vartag);
		if ($this->check_object($type))
		{
			$m_objects[$type]->set_vartag(get_vartag_name($vartag), $vartag_value);
		}
	}
	
	
	function unset_vartag($vartag)
	{
		global $m_objects;
		$type = get_vartag_type($vartag);
		if ($this->check_object($type))
		{
			$m_objects[$type]->unset_vartag(get_vartag_name($vartag));
		}
	}
	
	
	function get_vartag_value($vartag)
	{
		global $m_objects;
		$type = get_vartag_type($vartag);
		if ($this->check_object($type))
		{
			return $m_objects[$type]->get_vartag_value(get_vartag_name($vartag));
		}
		
		return false;
	}
	
	function get_vartag_data($vartag)
	{
		global $m_objects;
		$type = get_vartag_type($vartag);
		if ($this->check_object($type))
		{
			return $m_objects[$type]->get_vartag_data(get_vartag_name($vartag));
		}
		
		return false;
	}
	
	
	function check_object($suffix)
	{
		global $m_objects;
		$class = CLASS_PREFIX.$suffix;
		if (class_exists($class))
		{
			if (isset($m_objects[$suffix])==false)
				$m_objects[$suffix] = &new $class;
			
			return true;
		}
		
		return false;
	}
	
	
	function list_plugins()
	{
		global $m_objects;
		
		$ret = array($this->get_info());
		foreach($m_objects as $key=>$object)
			$ret[] = array_merge($object->get_info(), array("name"=>CLASS_PREFIX.$key));
		
		return $ret;
	}
	
	function get_info()
	{
		return array("name"=>"JFTM", "info"=>"JFTM Template machine", "version"=>"8.4",
			"history"=>"8.4: *not defined tags are now temporary disabled, by removing the surrounding tags");
	}
}









/*
	this part is to simplify the use of JFTM; no need to create objects,
	 or call methods; just call the functions, which return the values from the
	 JFTM. If you want to use the object, use the variable $JFTM.
*/

	$JFTM = new Template;
	
	function set_vartag($vartag, $vartag_value)
	{
		$GLOBALS['JFTM']->set_vartag($vartag, $vartag_value);
	}
	
	
	function unset_vartag($vartag)
	{
		$GLOBALS['JFTM']->unset_vartag($vartag);
	}
	
	
	function get_vartag_value($vartag)
	{
		return $GLOBALS['JFTM']->get_vartag_value($vartag);
	}
	
	
	function get_template($tplname, $disabled_tags = array())
	{
		return $GLOBALS['JFTM']->get_template($tplname, $disabled_tags);
	}







/* @return string containing file (if filename does not exist, a message will be returned)
 * @param filename file to read
 * @desc read a file to a string (this function always uses a cache for optimized accesspeed;
	this automatically updates the contents of the file if changed in between two calls)
*/
function read_template($filename)
{
	$filename.='.htm';
	
	//make sure path ends in /, and add filename afterwards
	$path=FOLDER_TEMPLATES.(substr(FOLDER_TEMPLATES, strlen(FOLDER_TEMPLATES), 1)=='/'? '':'/').$filename;
	
	if (file_exists($path)==false)	return "<p><b>File '$filename' not found.</b></p>";
	return file_get_contents($path);
}	


function get_vartag_type($vartag)
{
	preg_match_all(VARTAG_REGEX, '{{'.$vartag.'}}', $tags);
	
	return $tags[RE_TYPE][0];
}

function get_vartag_name($vartag)
{
	preg_match_all(VARTAG_REGEX, '{{'.$vartag.'}}', $tags);
	
	return $tags[RE_NAME][0];
}



function convert_tag_chars($string)
{
	return str_replace(array('{{', '}}'), array(chr(1).chr(1), chr(2).chr(2)), $string);
}

function deconvert_tag_chars($string)
{
	return str_replace(array(chr(1).chr(1), chr(2).chr(2)), array('{{', '}}'), $string);
}




function require_plugins($folder)
{
	$folder = (substr($folder, strlen($folder), 1)=='/'? $folder:$folder.'/');
	if (is_dir($folder))
	{
		$tpl_class_files = glob($folder."tpl_*.inc");
		
		if ($tpl_class_files!==false)
			foreach($tpl_class_files as $class_file)
				require_once $class_file;
	}
}
?>