<?PHP
/**
 * patTemplate
 *
 * $Id: patTemplate.php 12694 2009-09-11 21:03:02Z ian $
 *
 * powerful templating engine
 *
 * @version		3.1.0
 * @package		patTemplate
 * @author		Stephan Schmidt <schst@php.net>
 * @license		LGPL
 * @link		http://www.php-tools.net
 */

// ** Following line Joomla! specific **
require_once( dirname( __FILE__ ) . '/patErrorManager.php' );

/**
 * template already exists
 */
define( 'PATTEMPLATE_ERROR_TEMPLATE_EXISTS', 5010 );

/**
 * template does not exist
 */
define ( 'PATTEMPLATE_WARNING_NO_TEMPLATE', 5011 );

/**
 * unknown type
 */
define ( 'PATTEMPLATE_WARNING_UNKNOWN_TYPE', 5012 );

/**
 * base class for module could not be found
 */
define( 'PATTEMPLATE_ERROR_BASECLASS_NOT_FOUND', 5050 );

/**
 * module could not be found
 */
define( 'PATTEMPLATE_ERROR_MODULE_NOT_FOUND', 5051 );

/**
 * array expected
 */
define( 'PATTEMPLATE_ERROR_EXPECTED_ARRAY', 5052 );

/**
 * No input
 */
define( 'PATTEMPLATE_ERROR_NO_INPUT', 6000 );
/**
 * Recursion
 */
define( 'PATTEMPLATE_ERROR_RECURSION', 6010 );

/**
 * patTemplate
 *
 * powerful templating engine
 *
 * @version		3.1.0
 * @package		patTemplate
 * @author		Stephan Schmidt <schst@php.net>
 * @license		LGPL
 * @link		http://www.php-tools.net
 */
class patTemplate
{
	/**
	* standard system vars that identify pat tools
	* @var	array
	*/
	var	$_systemVars			=	array(
										'appName'		=>	'patTemplate',
										'appVersion'	=>	'3.1.0',
										'author'		=>	array(
																	'Stephan Schmidt <schst@php.net>'
																 )
									);

	/**
	* default attributes for new templates
	* @access	private
	* @var		array
	*/
	var	$_defaultAttributes	=	array(
										'type'			=>	'standard',
										'visibility'	=>	'visible',
										'loop'			=>	1,
										'unusedvars'	=>	'strip',
										'whitespace'	=>	'keep',
										'autoclear'		=>	'off',
										'autoload'		=>	'on'
									);

	/**
	* options for patTemplate
	*
	* Currently the following options are implemented:
	* - maintainBc defines, whether patTemplate should be backwards compatible.
	*   This means, that you may use 'default' and 'empty' for subtemplates.
	*
	* @access	private
	* @var		array
	*/
	var	$_options	=	array(
								'startTag'			=> '{',
								'endTag'			=> '}',
								'root'				=> array('__default' => '.'),
								'namespace'			=> 'patTemplate',
								'maintainBc'		=> true,
								'defaultFunction'	=> false
							 );

	/**
	* start tag
	*
	* @access	private
	* @var		string
	*/
	var $_startTag = '{';

	/**
	* end tag
	*
	* @access	private
	* @var		string
	*/
	var $_endTag = '}';

	/**
	* loaded modules
	*
	* Modules are:
	* - Readers
	* - Caches
	* - Variable modifiers
	* - Filters
	*
	* @access	private
	* @var		array
	*/
	var	$_modules		=	array();

	/**
	* directories, where modules can be stored
	* @access	private
	* @var		array
	*/
	var	$_moduleDirs	=	array();

	/**
	* stores all template names
	* @access	private
	* @var		array
	*/
	var	$_templateList	=	array();

	/**
	* stores all template data
	* @access	private
	* @var		array
	*/
	var	$_templates		=	array();

	/**
	* stores all global variables
	* @access	private
	* @var		array
	*/
	var	$_globals	=	array();

	/**
	* stores all local variables
	* @access	private
	* @var		array
	*/
	var	$_vars	=	array();

	/**
	* stores the name of the first template that has been
	* found
	*
	* @access	private
	* @var		string
	*/
	var	$_root;

	/**
	* output filters that should be used
	*
	* @access	private
	* @var		array
	*/
	var	$_outputFilters = array();

	/**
	* input filters that should be used
	*
	* @access	private
	* @var		array
	*/
	var	$_inputFilters = array();

	/**
	* template cache, that should be used
	*
	* @access	private
	* @var		array
	*/
	var	$_tmplCache = null;

	/**
	* placeholders, that have been discovered
	*
	* @access	private
	* @var		array
	*/
	var	$_discoveredPlaceholders = array();

	/**
	* Create a new patTemplate instance.
	*
	* The constructor accepts the type of the templates as sole parameter.
	* You may choose one of:
	* - html (default)
	* - tex
	*
	* The type influences the tags you are using in your templates.
	*
	* @access	public
	* @param	string	type (either html or tex)
	*/
	function patTemplate( $type = 'html' )
	{
		if( !defined( 'PATTEMPLATE_INCLUDE_PATH' ) ) {
			define( 'PATTEMPLATE_INCLUDE_PATH', dirname( __FILE__ ) . '/patTemplate' );
		}

		$this->setType( $type );
	}

	/**
	* sets an option
	*
	* Currently, the following options are supported
	* - maintainBc (true|false)
	* - namespace (string)
	*
	* @access	public
	* @param	string	option to set
	* @param	string	value of the option
	*/
	function setOption($option, $value)
	{
		$this->_options[$option] = $value;
	}

	/**
	* gets an option
	*
	* @access	public
	* @param	string	option to get
	* @return	mixed	value of the option
	*/
	function getOption( $option )
	{
		if (!isset($this->_options[$option])) {
			return null;
		}
		return $this->_options[$option];
	}

	/**
	* sets name of directory where templates are stored
	*
	* @access	public
	* @param	string	dir where templates are stored
	* @deprecated		please use patTemplate::setRoot() instead
	*/
	function setBasedir($basedir)
	{
		$this->setRoot($basedir);
	}

	/**
	* sets root base for the template
	*
	* The parameter depends on the reader you are using.
	*
	* @access	public
	* @param	string	root base of the templates
	*/
	function setRoot($root, $reader = '__default')
	{
		$this->_options['root'][$reader] = $root;
	}

	/**
	* gets name of root base for the templates
	*
	* @access	public
	* @return	mixed 		root base
	*/
	function getRoot($reader = '__default')
	{
		return	$this->_options['root'][$reader];
	}

	/**
	* sets namespace of patTemplate tags
	*
	* If you want to use more than one namespace, you may set this to
	* an array. All tags in these namespaces will be treated as patTemplate
	* tags.
	*
	* @access	public
	* @param	string|array	namespace(s)
	*/
	function setNamespace($ns)
	{
		$this->_options['namespace'] = $ns;
	}

	/**
	* gets namespace of patTemplate tags
	*
	* @access	public
	* @return	string|array	namespace(s)
	*/
	function getNamespace()
	{
		return $this->_options['namespace'];
	}

	/**
	* set default attribute
	*
	* @access	public
	* @param	string	attribute name
	* @param	mixed	attribute value
	*/
	function setDefaultAttribute( $name, $value )
	{
		$this->_defaultAttributes[$name]	=	$value;
	}

	/**
	* set default attributes
	*
	* @access	public
	* @param	array	attributes
	*/
	function setDefaultAttributes( $attributes )
	{
		$this->_defaultAttributes	=	array_merge( $this->_defaultAttributes, $attributes );
	}

	/**
	* get default attributes
	*
	* @access	public
	* @return	return default attributes
	*/
	function getDefaultAttributes()
	{
		return	$this->_defaultAttributes;
	}

	/**
	* set the type for the templates
	*
	* @access	public
	* @param	string	type (html or tex)
	* @return	boolean	true on success
	*/
	function setType( $type )
	{
		switch( strtolower( $type ) )
		{
			case "tex":
				$this->setTags( '<{', '}>' );
				break;
			case "html":
				$this->setTags( '{', '}' );
				break;
			default:
				return	patErrorManager::raiseWarning(
														PATTEMPLATE_WARNING_UNKNOWN_TYPE,
														"Unknown type '$type'. Please use 'html' or 'tex'."
													);
		}
		return true;
	}

	/**
	* set the start and end tag for variables
	*
	* @access	public
	* @param	string	start tag
	* @param	string	end tag
	* @return	boolean	true on success
	*/
	function setTags( $startTag, $endTag )
	{
		$this->_options['startTag']	=	$startTag;
		$this->_options['endTag']	=	$endTag;

		$this->_startTag	=	$startTag;
		$this->_endTag		=	$endTag;
		return true;
	}

	/**
	* get start tag for variables
	*
	* @access	public
	* @return	string	start tag
	*/
	function getStartTag()
	{
		return $this->_options['startTag'];
	}

	/**
	* get end tag for variables
	*
	* @access	public
	* @return	string	end tag
	*/
	function getEndTag()
	{
		return $this->_options['endTag'];
	}

	/**
	* add a directory where patTemplate should search for
	* modules.
	*
	* You may either pass a string or an array of directories.
	*
	* patTemplate will be searching for a module in the same
	* order you added them. If the module cannot be found in
	* the custom folders, it will look in
	* patTemplate/$moduleType.
	*
	* @access	public
	* @param	string			module type
	* @param	string|array	directory or directories to search.
	*/
	function addModuleDir( $moduleType, $dir )
	{
		if( !isset( $this->_moduleDirs[$moduleType] ) )
			$this->_moduleDirs[$moduleType]	=	array();
		if( is_array( $dir ) )
			$this->_moduleDirs[$moduleType] = array_merge( $this->_moduleDirs[$moduleType], $dir );
		else
			array_push( $this->_moduleDirs[$moduleType], $dir );
	}

	/**
	* Sets an attribute of a template
	*
	* supported attributes: visibilty, loop, parse, unusedvars
	*
	* @param	string	$template	name of the template
	* @param	string	$attribute	name of the attribute
	* @param	mixed	$value	value of the attribute
	* @access	public
	* @see		setAttributes(),getAttribute(), clearAttribute()
	*/
	function setAttribute( $template, $attribute, $value )
	{
		$template	=	strtolower( $template );
		if( !isset( $this->_templates[$template] ) )
		{
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$template' does not exist."
												);
		}

		$attribute	=	strtolower( $attribute );
		$this->_templates[$template]['attributes'][$attribute]	=	$value;
		return true;
	}

	/**
	* Sets several attribute of a template
	*
	* $attributes has to be a assotiative arrays containing attribute/value pairs
	* supported attributes: visibilty, loop, parse, unusedvars
	*
	* @param	string	$template	name of the template
	* @param	array	$attributes	attribute/value pairs
	* @access	public
	* @see		setAttribute(), getAttribute(), clearAttribute()
	*/
	function setAttributes( $template, $attributes )
	{
		if( !is_array( $attributes ) )
		{
			return patErrorManager::raiseError( PATTEMPLATE_ERROR_EXPECTED_ARRAY, 'patTemplate::setAttributes: Expected array as second parameter, '.gettype( $attributes ).' given' );
		}

		$template	=	strtolower( $template );
		$attributes	=	array_change_key_case( $attributes );
		if( !isset( $this->_templates[$template] ) )
		{
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$template' does not exist."
												);
		}

		$this->_templates[$template]['attributes']	=	array_merge( $this->_templates[$template]['attributes'], $attributes );
		return true;
	}

	/**
	* Get all attributes of a template
	*
	* @param	string	name of the template
	* @return	array	attributes
	* @access	public
	*/
	function getAttributes( $template )
	{
		$template	=	strtolower( $template );
		if( !isset( $this->_templates[$template] ) )
		{
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$template' does not exist."
												);
		}
		return	$this->_templates[$template]['attributes'];
	}

	/**
	* Gets an attribute of a template
	*
	* supported attributes: visibilty, loop, parse, unusedvars
	*
	* @param	string	$template	name of the template
	* @param	string	$attribute	name of the attribute
	* @return	mixed	value of the attribute
	* @access	public
	* @see		setAttribute(), setAttributes(), clearAttribute()
	*/
	function getAttribute( $template, $attribute )
	{
		$template	=	strtolower( $template );
		$attribute	=	strtolower( $attribute );
		if( !isset( $this->_templates[$template] ) )
		{
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$template' does not exist."
												);
		}
		return	$this->_templates[$template]['attributes'][$attribute];
	}

	/**
	* Clears an attribute of a template
	*
	* supported attributes: visibilty, loop, parse, unusedvars
	*
	* @param	string	$template	name of the template
	* @param	string	$attribute	name of the attribute
	* @access	public
	* @see		setAttribute(), setAttributes(), getAttribute()
	*/
	function clearAttribute( $template, $attribute )
	{
		$template	=	strtolower( $template );
		$attribute	=	strtolower( $attribute );

		if( !isset( $this->_templates[$template] ) )
		{
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$template' does not exist."
												);
		}
		$this->_templates[$template]['attributes'][$attribute]	=	'';;
		return true;
	}

	/**
	* Prepare a template
	*
	* This can be used if you want to add variables to
	* a template, that has not been loaded yet.
	*
	* @access	public
	* @param	string	template name
	*/
	function prepareTemplate( $name )
	{
		$name	=	strtolower( $name );
		if( !isset( $this->_vars[$name] ) )
		{
			$this->_vars[$name]	=	array(
												'scalar'	=>	array(),
												'rows'		=>	array()
											);
		}
	}

	/**
	* add a variable to a template
	*
	* A variable may also be an indexed array, but _not_
	* an associative array!
	*
	* @access	public
	* @param	string	$template	name of the template
	* @param	string	$varname	name of the variable
	* @param	mixed	$value		value of the variable
	*/
	function addVar( $template, $varname, $value )
	{
		$template = strtolower( $template );
		$varname  = strtoupper( $varname );

		if( !is_array( $value ) ) {
			$this->_vars[$template]['scalar'][$varname] = $value;
			return true;
		}

		$cnt = count( $value );
		for ($i = 0; $i < $cnt; $i++) {
			if (!isset( $this->_vars[$template]['rows'][$i] )) {
				$this->_vars[$template]['rows'][$i] = array();
			}
			$this->_vars[$template]['rows'][$i][$varname] = $value[$i];
		}

		return true;
	}

	/**
	* get the value of a variable
	*
	* @access	public
	* @param	string	name of the template
	* @param	string	name of the variable
	* @return	string	value of the variable, null if the variable is not set
	*/
	function getVar( $template, $varname )
	{
		$template	=	strtolower( $template );
		$varname	=	strtoupper( $varname );

		if( isset( $this->_vars[$template]['scalar'][$varname] ) )
			return $this->_vars[$template]['scalar'][$varname];

		$value = array();

		if(!isset($this->_vars[$template]['rows']))
			return null;

		$cnt = count( $this->_vars[$template]['rows'] );
		for( $i = 0; $i < $cnt; $i++ )
		{
			if( !isset( $this->_vars[$template]['rows'][$i][$varname] ) )
				continue;
			array_push( $value, $this->_vars[$template]['rows'][$i][$varname] );
		}
		if( !empty( $value ) )
			return $value;
		return null;
	}

	/**
	* clear the value of a variable
	*
	* @access	public
	* @param	string	name of the template
	* @param	string	name of the variable
	* @return   boolean
	* @see	  clearVars(), clearTemplate()
	*/
	function clearVar( $template, $varname )
	{
		$template	=	strtolower( $template );
		$varname	=	strtoupper( $varname );

		if (isset( $this->_vars[$template]['scalar'][$varname] )) {
			unset ($this->_vars[$template]['scalar'][$varname]);
			return true;
		}

		$result = false;
		$cnt = count( $this->_vars[$template]['rows'] );
		for ($i = 0; $i < $cnt; $i++) {
			if (!isset($this->_vars[$template]['rows'][$i][$varname])) {
				continue;
			}
			unset($this->_vars[$template]['rows'][$i][$varname]);
			$result = true;
		}
		return $result;
	}


	/**
	* Adds several variables to a template
	*
	* Each Template can have an unlimited amount of its own variables
	* $variables has to be an assotiative array containing variable/value pairs
	*
	* @param	string	$template	name of the template
	* @param	array	$variables	assotiative array of the variables
	* @param	string	$prefix	prefix for all variable names
	* @access	public
	* @see		addVar(), addRows(), addGlobalVar(), addGlobalVars()
	*/
	function addVars( $template, $variables, $prefix = '' )
	{
		$template	=	strtolower( $template );
		$prefix		=	strtoupper( $prefix );
		$variables	=	array_change_key_case( $variables, CASE_UPPER );

		foreach ($variables as $varname => $value) {
			$varname = $prefix.$varname;

			if (!is_array($value)) {
				if (!is_scalar($value)) {
					continue;
				}
				$this->_vars[$template]['scalar'][$varname] = $value;
				continue;
			}

			$cnt = count( $value );
			for( $i = 0; $i < $cnt; $i++ ) {
				if( !isset( $this->_vars[$template]['rows'][$i] ) )
					$this->_vars[$template]['rows'][$i]	=	array();

				$this->_vars[$template]['rows'][$i][$varname]	=	$value[$i];
			}
		}
	}

	/**
	* Clear all variables in a template
	*
	* This clears only variables, but does
	*
	* @access	public
	* @param	string	$template	name of the template
	* @return   boolean
	* @see		clearVar(), clearTemplate()
	*/
	function clearVars( $template )
	{
		$template = strtolower($template);
		$this->_vars[$template] = array(
										 'scalar' => array(),
										 'rows'   => array()
										);
		return true;
	}


	/**
	* Adds several rows of variables to a template
	*
	* Each Template can have an unlimited amount of its own variables
	* Can be used to add a database result as variables to a template
	*
	* @param	string	$template	name of the template
	* @param	array	$rows	array containing assotiative arrays with variable/value pairs
	* @param	string	$prefix	prefix for all variable names
	* @access	public
	* @see		addVar(), addVars(), addGlobalVar(), addGlobalVars()
	*/
	function addRows( $template, $rows, $prefix = '' )
	{
		$template	=	strtolower( $template );
		$prefix		=	strtoupper( $prefix );

		$cnt		=	count( $rows );
		for( $i = 0; $i < $cnt; $i++ )
		{
			if( !isset( $this->_vars[$template]['rows'][$i] ) )
				$this->_vars[$template]['rows'][$i]	=	array();

			$rows[$i]	=	array_change_key_case( $rows[$i], CASE_UPPER );

			foreach( $rows[$i] as $varname => $value )
			{
				$this->_vars[$template]['rows'][$i][$prefix.$varname]	=	$value;
			}
		}
	}

	/**
	* Adds an object to a template
	*
	* All properties of the object will be available as template variables.
	*
	* @access	public
	* @param	string			name of the template
	* @param	object|array	object or array of objects
	* @param	string			prefix for all variable names
	* @param	boolean			ignore private properties (starting with _)
	* @see		addVar(), addRows(), addGlobalVar(), addGlobalVars()
	*/
	function addObject( $template, $object, $prefix = '', $ignorePrivate = false )
	{
		if( is_array( $object ) ) {
			$rows = array();
			foreach($object as $o) {
				array_push( $rows, $this->getObjectVars($o, $ignorePrivate) );
			}

	   		return $this->addRows( $template, $rows, $prefix );
		} elseif (is_object($object)) {
			return $this->addVars( $template, $this->getObjectVars($object, $ignorePrivate), $prefix );
		}
		return false;
	}

	/**
	* get the vars from an object
	*
	* @access   private
	* @param	object
	* @param	boolean	 ignore private properties (starting with _)
	* @return   array
	*/
	function getObjectVars($obj, $ignorePrivate = false)
	{
		if (method_exists($obj, 'getVars')) {
			return $obj->getVars();
		}
		$vars = get_object_vars($obj);
		if ($ignorePrivate === false) {
			return $vars;
		}
		foreach ($vars as $var => $value) {
			if ($var{0} == '_') {
				unset($vars[$var]);
			}
		}
		return $vars;
	}

	/**
	* Adds a global variable
	*
	* Global variables are valid in all templates of this object.
	* A global variable has to be scalar, it will be converted to a string.
	*
	* @access	public
	* @param	string	$varname	name of the global variable
	* @param	string	$value		value of the variable
	* @return	boolean	true on success
	* @see		addGlobalVars(), addVar(), addVars(), addRows()
	*/
	function addGlobalVar( $varname, $value )
	{
		$this->_globals[strtoupper( $varname )]	=	( string )$value;
		return	true;
	}

	/**
	* Clears a global variable
	*
	* @access	public
	* @param	string	$varname	name of the global variable
	* @return	boolean	true on success
	* @see		clearVar(), clearVars(), clearGlobalVars()
	*/
	function clearGlobalVar( $varname )
	{
		$varname = strtoupper( $varname );
		if (!isset($this->_globals[$varname])) {
			return false;
		}
		unset($this->_globals[$varname]);
		return	true;
	}

	/**
	* Clears all global variables
	*
	* @access	public
	* @return	boolean	true on success
	* @see		clearVar(), clearVars(), clearGlobalVar()
	*/
	function clearGlobalVars()
	{
		$this->_globals = array();
		return	true;
	}

	/**
	* Adds several global variables
	*
	* Global variables are valid in all templates of this object.
	*
	* $variables is an associative array, containing name/value pairs of the variables.
	*
	* @access	public
	* @param	array	$variables	array containing the variables
	* @param	string	$prefix		prefix for variable names
	* @return	boolean	true on success
	* @see		addGlobalVar(), addVar(), addVars(), addRows()
	*/
	function addGlobalVars( $variables, $prefix = '' )
	{
		$variables	=	array_change_key_case( $variables, CASE_UPPER );
		$prefix		=	strtoupper( $prefix );
		foreach( $variables as $varname => $value )
		{
			$this->_globals[$prefix.$varname]	=	( string )$value;
		}

		return	true;
	}

	/**
	* get all global variables
	*
	* @access	public
	* @return	array	global variables
	*/
	function getGlobalVars()
	{
		return	$this->_globals;
	}

	/**
	* checks wether a template exists
	*
	* @access	public
	* @param	string		name of the template
	* @return	boolean		true, if the template exists, false otherwise
	*/
	function exists( $name )
	{
		return	in_array( strtolower( $name ), $this->_templateList );
	}

	/**
	* enable a template cache
	*
	* A template cache will improve performace, as the templates
	* do not have to be read on each request.
	*
	* @access	public
	* @param	string		name of the template cache
	* @param	array		parameters for the template cache
	* @return	boolean		true on success, patError otherwise
	*/
	function useTemplateCache( $cache, $params = array() )
	{
		if( !is_object( $cache ) )
		{
			$cache = &$this->loadModule( 'TemplateCache', $cache, $params );
		}
		if( patErrorManager::isError( $cache ) )
			return $cache;

		$this->_tmplCache = &$cache;
		return true;
	}

	/**
	* enable an output filter
	*
	* Output filters are used to modify the template
	* result before it is sent to the browser.
	*
	* They are applied, when displayParsedTemplate() is called.
	*
	* @access	public
	* @param	string		name of the output filter
	* @param	array		parameters for the output filter
	* @return	boolean		true on success, patError otherwise
	*/
	function applyOutputFilter( $filter, $params = array(), $template = null )
	{
		if (!is_object($filter)) {
			$filter = &$this->loadModule( 'OutputFilter', $filter, $params );
		}
		if (patErrorManager::isError($filter)) {
			return $filter;
		}

		if ($template === null) {
			$this->_outputFilters[] = &$filter;
			return true;
		}

		$template = strtolower($template);
		if (!$this->exists($template)) {
			return patErrorManager::raiseWarning(PATTEMPLATE_WARNING_NO_TEMPLATE, 'The selected template does not exist');
		}
		$this->_templates[$template]['attributes']['outputfilter'] = &$filter;
		return true;
	}

	/**
	* enable an input filter
	*
	* input filters are used to modify the template
	* stream before it is split into smaller templates-
	*
	* @access	public
	* @param	string		name of the input filter
	* @param	array		parameters for the input filter
	* @return	boolean		true on success, patError otherwise
	*/
	function applyInputFilter( $filter, $params = array() )
	{
		if( !is_object( $filter ) )
		{
			$filter = &$this->loadModule( 'InputFilter', $filter, $params );
		}
		if( patErrorManager::isError( $filter ) )
			return $filter;

		$this->_inputFilters[] = &$filter;
		return true;
	}

	/**
	* open a file and parse for patTemplate tags
	*
	* @access		public
	* @param		name of the file
	* @return		true, if the template could be parsed
	* @deprecated	Use patTemplate::readTemplatesFromInput() instead, as the method name is misleading
	* @see			readTemplatesFromInput()
	*/
	function readTemplatesFromFile( $filename )
	{
		return	$this->readTemplatesFromInput( $filename, 'File' );
	}

	/**
	* open any input and parse for patTemplate tags
	*
	* @access	public
	* @param	string	name of the input (filename, shm segment, etc.)
	* @param	string	driver that is used as reader, you may also pass a Reader object
	* @param	array	additional options that will only be used for this template
	* @param	string	name of the template that should be used as a container, should not be used by public
	*					calls.
	* @return	boolean	true, if the template could be parsed, false otherwise
	*/
	function readTemplatesFromInput( $input, $reader = 'File', $options = null, $parseInto = null )
	{
		if ((string)$input === '') {
			return patErrorManager::raiseError(PATTEMPLATE_ERROR_NO_INPUT, 'No input to read has been passed.');
		}

		if (is_array($options)) {
			$options = array_merge( $this->_options, $options );
		} else {
			$options = $this->_options;
		}

		if (!is_null($parseInto)) {
			$parseInto	=	strtolower( $parseInto );
		}

		$templates = false;
		if ($this->_tmplCache !== null) {
			/**
			 * get the unique cache key
			 */
			$key = $this->_tmplCache->getKey($input, $options);

			$templates = $this->_loadTemplatesFromCache( $input, $reader, $options, $key );

			/**
			 * check for error returned from cache
			 */
			if (patErrorManager::isError($templates)) {
				return $templates;
			}
		}

		/**
		 * templates have not been loaded from cache
		 */
		if ($templates === false) {
			if (!is_object( $reader)) {
				$reader = &$this->loadModule('Reader', $reader);
				if (patErrorManager::isError($reader)) {
					return $reader;
				}
			}

			if ($reader->isInUse()) {
				$reader = &$this->loadModule( 'Reader', $reader->getName(), array(), true);
				if( patErrorManager::isError( $reader ) ) {
					return $reader;
				}
			}

			$reader->setOptions($options);

			/**
			 * set the root attributes
			 */
			if( !is_null( $parseInto ) )
			{
				$attributes = $this->getAttributes( $parseInto );
				if( !patErrorManager::isError( $attributes ) )
				{
					$reader->setRootAttributes( $attributes );
				}
			}

			$templates	=	$reader->readTemplates( $input );

			/**
			 * check for error returned from reader
			 */
			if( patErrorManager::isError( $templates ) )
				return $templates;

			/**
			 * store the
			 */
			if( $this->_tmplCache !== null )
			{
				$this->_tmplCache->write( $key, $templates );
			}
		}

		/**
		 * traverse all templates
		 */
		foreach( $templates as $name => $spec )
		{

			/**
			 * root template
			 */
			if( $name == '__ptroot' )
			{
				if( $parseInto === false )
				{
					continue;
				}
				if( !in_array( $parseInto, $this->_templateList ) )
					continue;

				$spec['loaded']		= true;
				$spec['attributes']	= $this->_templates[$parseInto]['attributes'];
				$name	=	$parseInto;
			}
			else
			{
				/**
				 * store the name
				 */
				array_push( $this->_templateList, $name );
			}

			/**
			 * if this is the first template that has been loaded
			 * set it as the root template
			 */
			if( $this->_root === null && is_null( $parseInto ) && isset( $spec['isRoot'] ) && $spec['isRoot'] == true )
			{
				$this->_root = $name;
			}

			/**
			 * set some default values
			 */
			$spec['iteration']			=	0;
			$spec['lastMode']			=	'w';
			$spec['result']				=	'';
			$spec['modifyVars']			=	array();
			$spec['copyVars']			=	array();
			$spec['defaultVars']		=	array();

			/**
			 * store the template
			 */
			$this->_templates[$name]	=	$spec;

			$this->prepareTemplate( $name );

			/**
			 * store the default values of the variables
			 */
			foreach( $spec['varspecs'] as $varname => $varspec )
			{
				if (isset($varspec['modifier'])) {
					$this->_templates[$name]['modifyVars'][$varname] = $varspec['modifier'];
				}

				if( isset( $varspec['copyfrom'] ) )
				{
					$this->_templates[$name]['copyVars'][$varname] = $varspec['copyfrom'];
				}

				if( !isset( $varspec['default'] ) )
					continue;

				$this->_templates[$name]['defaultVars'][$varname] = $varspec['default'];

				if( !is_null( $this->getVar( $name, $varname ) ) )
					continue;

				$this->addVar( $name, $varname, $varspec['default'] );
			}

			unset($this->_templates[$name]['varspecs']);

			/**
			 * autoload the template
			 *
			 * Some error management is needed here...
			 */
			if( isset( $this->_templates[$name]['attributes']['src'] ) && $this->_templates[$name]['attributes']['autoload'] == 'on' )
			{
				if( $this->_templates[$name]['loaded'] !== true )
				{
					if( $this->_templates[$name]['attributes']['parse'] == 'on' )
					{
						$this->readTemplatesFromInput( $this->_templates[$name]['attributes']['src'], $this->_templates[$name]['attributes']['reader'], $options, $name );
					}
					else
					{
						$this->loadTemplateFromInput( $this->_templates[$name]['attributes']['src'], $this->_templates[$name]['attributes']['reader'], null, $name );
					}
					$this->_templates[$name]['loaded']	=	true;
				}
			}
		}

		return true;
	}

	/**
	* load from template cache
	*
	* @access	private
	* @param	string	name of the input (filename, shm segment, etc.)
	* @param	string	driver that is used as reader, you may also pass a Reader object
	* @param	array	options for the reader
	* @param	string	cache key
	* @return	array|boolean	either an array containing the templates, or false
	*/
	function _loadTemplatesFromCache( $input, &$reader, $options, $key )
	{
		if( is_object( $reader ) )
			$statName   =   $reader->getName();
		else
			$statName	=	$reader;

		$stat	=	&$this->loadModule( 'Stat', $statName );
		$stat->setOptions( $options );

		/**
		 * get modification time
		 */
		$modTime   = $stat->getModificationTime( $input );
		$templates = $this->_tmplCache->load( $key, $modTime );

		return $templates;
	}

	/**
	* open any input and load content into template
	*
	* @access	public
	* @param	string	name of the input (filename, shm segment, etc.)
	* @param	string	driver that is used as reader
	* @param	string	name of the template that should be used as a container,
	* @return	boolean	true, if the template could be parsed, false otherwise
	*/
	function loadTemplateFromInput( $input, $reader = 'File', $options = null, $parseInto = false )
	{
		if( is_array( $options ) )
			$options = array_merge( $this->_options, $options );
		else
			$options = $this->_options;

		if( !is_null( $parseInto ) )
			$parseInto	=	strtolower( $parseInto );

		$reader	= &$this->loadModule( 'Reader', $reader );
		if( patErrorManager::isError( $reader ) )
		{
			return $reader;
		}
		$reader->setOptions($options);

		$result	= $reader->loadTemplate( $input );

		if( patErrorManager::isError( $result ) )
		{
			return $result;
		}

		$this->_templates[$parseInto]['content'] .= $result;
		$this->_templates[$parseInto]['loaded']   = true;
		return true;
	}

	/**
	* load a template that had autoload="off"
	*
	* This is needed, if you change the source of a template and want to
	* load it, after changing the attribute.
	*
	* @access	public
	* @param	string		template name
	* @return	boolean		true, if template could be loaded
	*/
	function  loadTemplate( $template )
	{
		$template = strtolower( $template );
		if( !isset( $this->_templates[$template] ) )
		{
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$template' does not exist."
												);
		}

		if( $this->_templates[$template]['loaded'] === true )
			return true;

		if( $this->_templates[$template]['attributes']['parse'] == 'on' )
		{
			return $this->readTemplatesFromInput( $this->_templates[$template]['attributes']['src'], $this->_templates[$template]['attributes']['reader'], null, $template );
		}
		else
		{
			return $this->loadTemplateFromInput( $this->_templates[$template]['attributes']['src'], $this->_templates[$template]['attributes']['reader'], null, $template );
		}
	}

	/**
	* loads a patTemplate module
	*
	* Modules are located in the patTemplate folder and include:
	* - Readers
	* - Caches
	* - Variable Modifiers
	* - Filters
	* - Functions
	* - Stats
	*
	* @access	public
	* @param	string	moduleType (Reader|TemplateCache|Modifier|OutputFilter|InputFilter)
	* @param	string	moduleName
	* @param	array	parameters for the module
	* @return	object
	*/
	function &loadModule( $moduleType, $moduleName, $params = array(), $new = false )
	{
		if( !isset( $this->_modules[$moduleType] ) )
			$this->_modules[$moduleType]	=	array();

		$sig = md5( $moduleName . serialize( $params ) );

		if( isset( $this->_modules[$moduleType][$sig] ) && $new === false ) {
			return	$this->_modules[$moduleType][$sig];
		}

		if( !class_exists( 'patTemplate_Module' ) )
		{
			$file	=	sprintf( "%s/Module.php", $this->getIncludePath() );
			if( !file_exists( $file ) or !include_once $file )
				return	patErrorManager::raiseError( PATTEMPLATE_ERROR_BASECLASS_NOT_FOUND, 'Could not load module base class.' );
		}

		$baseClass	=	'patTemplate_' . $moduleType;
		if( !class_exists( $baseClass ) )
		{
			$baseFile	=	sprintf( "%s/%s.php", $this->getIncludePath(), $moduleType );
			if( !file_exists( $baseFile ) or !include_once $baseFile )
				return	patErrorManager::raiseError( PATTEMPLATE_ERROR_BASECLASS_NOT_FOUND, "Could not load base class for $moduleType ($baseFile)." );
		}

		$moduleClass	=	'patTemplate_' . $moduleType . '_' .$moduleName;
		if( !class_exists( $moduleClass ) )
		{
			if( isset( $this->_moduleDirs[$moduleType] ) )
				$dirs = $this->_moduleDirs[$moduleType];
			else
				$dirs = array();
			array_push( $dirs, $this->getIncludePath() .'/'. $moduleType );

			$found = false;
			foreach( $dirs as $dir )
			{
				$moduleFile	= sprintf( "%s/%s.php", $dir, str_replace( '_', '/', $moduleName ) );

				if ( file_exists( $moduleFile ) and include_once $moduleFile) {
					$found = true;
					break;
				}
			}

			if( !$found ) {
				return	patErrorManager::raiseError( PATTEMPLATE_ERROR_MODULE_NOT_FOUND, "Could not load module $moduleClass ($moduleFile)." );
			}
		}

		if( !class_exists( $moduleClass ) )
		{
			return	patErrorManager::raiseError( PATTEMPLATE_ERROR_MODULE_NOT_FOUND, "Module file $moduleFile does not contain class $moduleClass." );
		}

		$this->_modules[$moduleType][$sig]	= new $moduleClass;
		if( method_exists( $this->_modules[$moduleType][$sig], 'setTemplateReference' ) )
		{
			$this->_modules[$moduleType][$sig]->setTemplateReference( $this );
		}

		$this->_modules[$moduleType][$sig]->setParams( $params );

		return $this->_modules[$moduleType][$sig];
	}

	/**
	* checks whether a module exists.
	*
	* Modules are located in the patTemplate folder and include:
	* - Readers
	* - Caches
	* - Variable Modifiers
	* - Filters
	* - Functions
	* - Stats
	*
	* @access	public
	* @param	string	moduleType (Reader|TemplateCache|Modifier|OutputFilter|InputFilter)
	* @param	string	moduleName
	* @return	boolean
	*/
	function moduleExists( $moduleType, $moduleName )
	{
		// !!!JOOMLA VARIATION!!!
		// cache checks on files
		static $paths;

		if (!$paths)
		{
			$paths = array();
		}

		if (isset($this->_moduleDirs[$moduleType])) {
			$dirs = $this->_moduleDirs[$moduleType];
		} else {
			$dirs = array();
		}
		array_push($dirs, $this->getIncludePath() .'/'. $moduleType);

		foreach ($dirs as $dir) {
			$moduleFile	= sprintf( "%s/%s.php", $dir, str_replace( '_', '/', $moduleName ) );
			if (!isset( $paths[$moduleFile] ))
			{
				if (!file_exists($moduleFile)) {
					$paths[$moduleFile] = false;
				}
				else if (!is_readable($moduleFile)) {
					$paths[$moduleFile] = false;
				}
				else
				{
					$paths[$moduleFile] = true;
				}
			}

			if (!$paths[$moduleFile]) {
				continue;
			}
			return true;
		}
		return false;
	}

	/**
	* parses a template
	*
	* Parses a template and stores the parsed content.
	* mode can be "w" for write (delete already parsed content) or "a" for append (appends the
	* new parsed content to the already parsed content)
	*
	* @access	public
	* @param	string	name of the template
	* @param	string	mode for the parsing
	*/
	function parseTemplate( $template, $mode = 'w' )
	{
		$template = strtolower($template);

		if (!isset($this->_templates[$template])) {
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$template' does not exist."
												);
		}

		/**
		 * template is not visible
		 */
		if ($this->_templates[$template]['attributes']['visibility'] == 'hidden') {
			$this->_templates[$template]['result']	=	'';
			$this->_templates[$template]['parsed']	=	true;
			return true;
		}

		/**
		 * check, if the template has been loaded
		 * and load it if necessary.
		 */
		if ($this->_templates[$template]['loaded'] !== true) {
			if ($this->_templates[$template]['attributes']['parse'] == 'on') {
				$result = $this->readTemplatesFromInput( $this->_templates[$template]['attributes']['src'], $this->_templates[$template]['attributes']['reader'], null, $template );
			} else {
				$result = $this->loadTemplateFromInput( $this->_templates[$template]['attributes']['src'], $this->_templates[$template]['attributes']['reader'], null, $template );
			}
			if (patErrorManager::isError($result)) {
				return $result;
			}
		}

		/**
		 * check for autoclear
		 */
		if(
			isset( $this->_templates[$template]['attributes']['autoclear'] ) &&
			$this->_templates[$template]['attributes']['autoclear'] == 'yes' &&
			$mode === 'w' &&
			$this->_templates[$template]['lastMode'] != 'a'
		  ) {
			$this->_templates[$template]['parsed']	= false;
		}

		/**
		 * template has been parsed and mode is not 'append'
		 */
		if ($this->_templates[$template]['parsed'] === true && $mode === 'w') {
			return true;
		}

		$this->_templates[$template]['lastMode'] = $mode;

		$this->_initTemplate( $template );

		if (!isset($this->_vars[$template]['rows'])) {
			$this->_vars[$template]['rows']	=	array();
		}
		$loop = count( $this->_vars[$template]['rows'] );

		/**
		 * loop at least one times
		 */
		if ($loop < 1) {
			$loop = 1;
		}

		if (isset($this->_templates[$template]['attributes']['maxloop'])) {
			$loop = ceil( $loop / $this->_templates[$template]['attributes']['maxloop'] ) * $this->_templates[$template]['attributes']['maxloop'];
		}

		$this->_templates[$template]['loop'] = max( $this->_templates[$template]['attributes']['loop'], $loop );

		$start = 0;
		if (isset($this->_templates[$template]['attributes']['limit'])) {
			$p = strpos( $this->_templates[$template]['attributes']['limit'], ',' );
			if ($p === false) {
				$this->_templates[$template]['loop'] = min( $this->_templates[$template]['loop'], $this->_templates[$template]['attributes']['limit'] );
				$start = 0;
			} else {
				$start = substr( $this->_templates[$template]['attributes']['limit'], 0, $p );
				$end   = substr( $this->_templates[$template]['attributes']['limit'], $p+1 )+$start;

				$this->_templates[$template]['loop'] = min( $this->_templates[$template]['loop'], $end );
			}
		}

		/**
		 * template should be cleared before parsing
		 */
		if ($mode == 'w') {
			$this->_templates[$template]['result']	= '';
			$this->_templates[$template]['iteration'] = $start;
		}

		$loopCount = 0;
		for ($i = $start; $i < $this->_templates[$template]['loop']; $i++) {
			$finished  = false;

			unset( $this->_templates[$template]['vars'] );

			/**
			 * fetch the variables
			 */
			$this->_fetchVariables( $template );

			/**
			 * fetch the template
			 */
			$result = $this->_fetchTemplate($template);

			if ($result === false) {
				$this->_templates[$template]['iteration']++;
				continue;
			}

			/**
			 * parse
			 */
			$this->_parseVariables( $template );
			$result = $this->_parseDependencies( $template );
			if (patErrorManager::isError($result)) {
				return $result;
			}

			/**
			 * store result
			 */
			$this->_templates[$template]['result'] .= $this->_templates[$template]['work'];

			$this->_templates[$template]['iteration']++;

			++$loopCount;

			/**
			 * check for maximum loops
			 */
			if (isset($this->_templates[$template]['attributes']['maxloop'])) {
				if ($loopCount == $this->_templates[$template]['attributes']['maxloop'] && $i < ($loop-1)) {
					$loopCount = 0;
					$finished  = true;
					$this->_templates[$template]['parsed'] = true;
					$this->parseTemplate( $this->_templates[$template]['attributes']['parent'], 'a' );
					$this->_templates[$template]['parsed'] = false;
					$this->_templates[$template]['result'] = '';
				}
			}
		}

		if (!$finished && isset($this->_templates[$template]['attributes']['maxloop'])) {
			$this->_templates[$template]['parsed'] = true;
			$this->parseTemplate( $this->_templates[$template]['attributes']['parent'], 'a', false );
			$this->_templates[$template]['parsed'] = false;
			$this->_templates[$template]['result'] = '';
			$this->_templates[$this->_templates[$template]['attributes']['parent']]['work'] = '';
		}

		$this->_parseGlobals($template);

		$this->_handleUnusedVars($template);

		$this->_templates[$template]['parsed']	= true;

		if (isset($this->_templates[$template]['attributes']['autoclear']) && $this->_templates[$template]['attributes']['autoclear'] == 'yes') {
			$this->_vars[$template] = array(
											'scalar' => array(),
											'rows'   => array()
											);
		}

		if (isset($this->_templates[$template]['attributes']['outputfilter'])) {
			if (is_object($this->_templates[$template]['attributes']['outputfilter'])) {
				$filter = &$this->_templates[$template]['attributes']['outputfilter'];
			} else {
				$filter = &$this->loadModule('OutputFilter', $this->_templates[$template]['attributes']['outputfilter']);
			}

			if (patErrorManager::isError($filter)) {
				return $filter;
			}

			$this->_templates[$template]['result'] = $filter->apply($this->_templates[$template]['result']);
		}
		return true;
	}

	/**
	* Initialize a template
	*
	* This method checks the variable specifications and
	* copys variables from other templates.
	*
	* @access	private
	* @param	string	name of the template
	* @return	boolean	true on success
	*/
	function _initTemplate( $template )
	{
		foreach( $this->_templates[$template]['copyVars'] as $dest => $src )
		{
			/**
			 * copy from the same template
			 */
			if( !is_array( $src ) )
			{
				$srcTemplate = $template;
				$srcVar	  = $src;
			}
			else
			{
				$srcTemplate = $src[0];
				$srcVar	  = $src[1];
			}

			$copied = false;

			/**
			 * copy from another template
			 */
			if( isset( $this->_vars[$srcTemplate] ) )
			{
				if( isset( $this->_vars[$srcTemplate]['scalar'][$srcVar] ) )
				{
					$this->_vars[$template]['scalar'][$dest] = $this->_vars[$srcTemplate]['scalar'][$srcVar];
					continue;
				}

				$rows = count( $this->_vars[$srcTemplate]['rows'] );

				for( $i = 0; $i < $rows; $i++ )
				{
					if( !isset( $this->_vars[$srcTemplate]['rows'][$i][$srcVar] ) )
						continue;
					if( !isset( $this->_vars[$template]['rows'][$i] ) )
						$this->_vars[$template]['rows'][$i] = array();
					$this->_vars[$template]['rows'][$i][$dest] = $this->_vars[$srcTemplate]['rows'][$i][$srcVar];
					$copied = true;
				}
			}
			if( !$copied && isset( $this->_globals[$srcVar] ))
			{
				$this->_vars[$template]['scalar'][$dest] = $this->_globals[$srcVar];
			}

		}
		return true;
	}

	/**
	* parse all variables in a template
	*
	* @access	private
	* @param	string
	*/
	function _parseVariables( $template )
	{
		/**
		 * modify variables before parsing
		 */
		$this->_applyModifers($template, $this->_templates[$template]['vars']);

		foreach( $this->_templates[$template]['vars'] as $key => $value )
		{
			if( is_array( $value ) )
			{
				if( count( $this->_templates[$template]['currentDependencies'] ) == 1 )
				{
					$child	=	$this->_templates[$template]['currentDependencies'][0];
				}
				else
				{
					if( isset( $this->_templates[$template]['attributes']['child'] ) )
						$child = $this->_templates[$template]['attributes']['child'];
					else
						continue;
				}

				$this->setAttribute( $child, 'autoclear', 'yes' );
				$this->addVar( $child, $key, $value );
				continue;
			}

			$var  = $this->_startTag.$key.$this->_endTag;
			$this->_templates[$template]['work'] = str_replace( $var, $value, $this->_templates[$template]['work'] );
		}
		return true;
	}

	/**
	* parse global variables in the template
	*
	* @access   private
	* @param	string	  name of the template
	* @return   boolean
	*/
	function _parseGlobals($template)
	{
		$globalVars = $this->_globals;
		$this->_applyModifers($template, $globalVars);

		foreach( $globalVars as $key => $value )
		{
			if( is_array( $value ) )
			{
				continue;
			}

			$var  = $this->_startTag.$key.$this->_endTag;
			$this->_templates[$template]['result'] = str_replace( $var, $value, $this->_templates[$template]['result'] );
		}
		return true;
	}

	/**
	* apply variable modifiers
	*
	* The variables will be passed by reference.
	*
	* @access   private
	* @param	string	  name of the template (use modifiers from this template)
	* @param	array	   variables to which the modifiers should be applied
	* @return   boolean
	*/
	function _applyModifers($template, &$vars)
	{
		foreach ($this->_templates[$template]['modifyVars'] as $varname => $modifier) {
			if (!isset($vars[$varname])) {
				continue;
			}

			if (($modifier['type'] === 'php' || $modifier['type'] === 'auto' ) && is_callable($modifier['mod'])) {
				$vars[$varname] = call_user_func($modifier['mod'], $vars[$varname]);
				continue;
			}

			if ($modifier['type'] === 'php') {
				continue;
			}

			$mod = &$this->loadModule( 'Modifier', ucfirst( $modifier['mod'] ) );
			$vars[$varname] = $mod->modify( $vars[$varname], $modifier['params'] );
		}

		// apply the default modifier
		if (isset($this->_templates[$template]['attributes']['defaultmodifier'])) {

			$defaultModifier = $this->_templates[$template]['attributes']['defaultmodifier'];
			if (is_callable($defaultModifier)) {
				$type = 'php';
			} else {
				$type = 'custom';
				$defaultModifier = &$this->loadModule('Modifier', ucfirst($defaultModifier));
			}


			foreach (array_keys($vars) as $varname) {
				if (isset($this->_templates[$template]['modifyVars'][$varname])) {
					continue;
				}
				if ($type === 'php') {
					$vars[$varname] = call_user_func($defaultModifier, $vars[$varname]);
				} else {
					$vars[$varname] = $defaultModifier->modify($vars[$varname], array());
				}
			}
		}

		return true;
	}

	/**
	* parse all dependencies in a template
	*
	* @access	private
	* @param	string
	*/
	function _parseDependencies($template)
	{
		$countDep	=	count( $this->_templates[$template]['currentDependencies'] );
		for ($i = 0; $i < $countDep; $i++) {
			$depTemplate = $this->_templates[$template]['currentDependencies'][$i];
			if ($depTemplate == $template) {
				return patErrorManager::raiseError(PATTEMPLATE_ERROR_RECURSION, 'You have an error in your template "' . $template . '", which leads to recursion');
			}
			$this->parseTemplate($depTemplate);
			$var	= $this->_startTag.'TMPL:'.strtoupper( $depTemplate) .$this->_endTag;
			$this->_templates[$template]['work'] = str_replace( $var, $this->_templates[$depTemplate]['result'], $this->_templates[$template]['work'] );
		}
		return true;
	}

	/**
	* fetch plain template
	*
	* The template content will be stored in the template
	* configuration so it can be used by other
	* methods.
	*
	* @access	private
	* @param	string	template name
	* @return	boolean
	*/
	function _fetchTemplate( $template )
	{
		switch( $this->_templates[$template]['attributes']['type'] )
		{
			/**
			 * condition template
			 */
			case 'condition':
				$value = $this->_getConditionValue($template, $this->_templates[$template]['attributes']['conditionvar']);
				if ($value === false) {
					$this->_templates[$template]['work']				= '';
					$this->_templates[$template]['currentDependencies']	= array();
				} else {
					$this->_templates[$template]['work']				= $this->_templates[$template]['subtemplates'][$value]['data'];
					$this->_templates[$template]['currentDependencies']	= $this->_templates[$template]['subtemplates'][$value]['dependencies'];
				}
				break;

			/**
			 * condition template
			 */
			case 'simplecondition':
				foreach( $this->_templates[$template]['attributes']['requiredvars'] as $var )
				{
					// different template scope
					if( $var[0] !== $template ) {
						$this->_fetchVariables($var[0]);
					}
					$value = null;
					// fetch the local variable
					if( isset( $this->_templates[$var[0]]['vars'][$var[1]] )
					  && strlen( $this->_templates[$var[0]]['vars'][$var[1]] ) > 0 ) {
					   $value = $this->_templates[$var[0]]['vars'][$var[1]];
					}
					if (isset($this->_templates[$template]['attributes']['useglobals'])) {
						if(isset($this->_globals[$var[1]]) && strlen($this->_globals[$var[1]]) > 1) {
							$value = $this->_globals[$var[1]];
						}
					}
					if ($value !== null) {
                        if ($var[2] === null) {
                        	continue;
                        } else {
                        	// Joomla! addition 23-June-2005
                        	// value wrapped in ## uses regex for comparison
							$condition = $var[2];
							if (substr( $condition, 0, 1 ) == '#' && substr( $condition, -1, 1 ) == '#' ) {
								if (preg_match( $condition, $value )) {
									continue;
								}
							} else if ($condition == $value) {
								continue;
							}
                        	/* Pat Original
                            if ($var[2] == $value) {
                               	continue;
                            }
                            */
                        }
                    }

					$this->_templates[$template]['work']				= '';
					$this->_templates[$template]['currentDependencies']	= array();
					break 2;
				}
				$this->_templates[$template]['work'] 				= $this->_templates[$template]['content'];
				$this->_templates[$template]['currentDependencies']	= $this->_templates[$template]['dependencies'];
				break;

			/**
			 * modulo template
			 */
			case 'modulo':
				// check for empty template

				if ($this->_hasVariables($template)) {
					$value = (string)($this->_templates[$template]['iteration'] + 1 ) % $this->_templates[$template]['attributes']['modulo'];
				} else {
					$value = '__empty';
				}

				$value = $this->_getConditionValue($template, $value, false);
				if ($value === false) {
					$this->_templates[$template]['work']				= '';
					$this->_templates[$template]['currentDependencies']	= array();
				} else {
					$this->_templates[$template]['work']				= $this->_templates[$template]['subtemplates'][$value]['data'];
					$this->_templates[$template]['currentDependencies']	= $this->_templates[$template]['subtemplates'][$value]['dependencies'];
				}
				break;

			/**
			 * standard template
			 */
			default:
				$this->_templates[$template]['work'] 				=	$this->_templates[$template]['content'];
				$this->_templates[$template]['currentDependencies']	=	$this->_templates[$template]['dependencies'];
				break;
		}
		return true;
	}

	/**
	* check, whether a template contains variables
	*
	* @access   private
	* @param	string  template name
	* @return   boolean
	*/
	function _hasVariables($template)
	{
		if (!empty($this->_vars[$template]['scalar'])) {
			return true;
		}
		if (isset($this->_vars[$template]['rows'][$this->_templates[$template]['iteration']])) {
			return true;
		}
		return false;
	}

	/**
	* fetch the value of a condition variable
	*
	* _fetchVariables() has to be called before this
	* method is being called.
	*
	* @access	private
	* @param	string	template name
	* @param	string	condition value
	* @param	boolean	flag that indicates whether value is the name of the variable that should be resolved
	*
	* @todo		split this method into smaller check methods that will be called according to
	*			a priority list
	*/
	function _getConditionValue( $template, $value, $isVar = true )
	{
		if ($isVar === true) {
			if (isset($this->_templates[$template]['attributes']['conditiontmpl'])) {
				$_template = $this->_templates[$template]['attributes']['conditiontmpl'];
				$this->_fetchVariables($_template);
			} else {
				$_template = $template;
			}

			/**
			 * get the value from the template variables
			 */
			if (!isset($this->_templates[$_template]['vars'][$value]) || strlen($this->_templates[$_template]['vars'][$value]) === 0) {
				if ($this->_templates[$template]['attributes']['useglobals'] == 'yes' || $this->_templates[$template]['attributes']['useglobals'] == 'useglobals') {
					if (isset( $this->_globals[$value] ) && strlen( $this->_globals[$value] ) > 0) {
						$value = $this->_globals[$value];
					} else {
						$value = '__empty';
					}
				} else {
					$value = '__empty';
				}
			} else {
				$value = $this->_templates[$_template]['vars'][$value];
			}
		} else {
			$_template = $template;
		}

		// if value is empty and a template for empty has been defined, this
		// has priority
		if ($value === '__empty' && isset($this->_templates[$template]['subtemplates']['__empty'])) {
			return $value;
		}

		// only one iteration (but not empty), use the __single condition
		if ($value !== '__empty' && $this->_templates[$_template]['loop'] === 1) {
			if( isset($this->_templates[$template]['subtemplates']['__single'])) {
				return '__single';
			}
		} else {

			// is __first?
			if( $this->_templates[$_template]['iteration'] == 0 ) {
				if( isset( $this->_templates[$template]['subtemplates']['__first'] ) ) {
					return '__first';
				}
			}

			/**
			 * is __last?
			 */
			if (isset($this->_templates[$_template]['loop'])) {
				$max = $this->_templates[$_template]['loop'] - 1;
				if( $this->_templates[$_template]['iteration'] == $max ) {
					if( isset( $this->_templates[$template]['subtemplates']['__last'] ) ) {
						return '__last';
					}
				}
			}
		}

		// search for exact match
		foreach (array_keys($this->_templates[$template]['subtemplates']) as $key) {
			if (isset($this->_templates[$template]['subtemplates'][$key]['attributes']['var'])) {
				$var = $this->_templates[$template]['subtemplates'][$key]['attributes']['var'];
				if (isset($this->_templates[$template]['vars'][$var])) {
					$current = $this->_templates[$template]['vars'][$var];
				} else {
					$current = null;
				}
			} else {
				$current = $key;
			}

			if ((string)$value === (string)$current) {
				return $key;
			}
		}

		/**
		 * is __default?
		 */
		if( isset( $this->_templates[$template]['subtemplates']['__default'] ) ) {
			return '__default';
		}

		return false;
	}

	/**
	* fetch variables for a template
	*
	* The variables will be stored in the template
	* configuration so they can be used by other
	* methods.
	*
	* @access	private
	* @param	string	template name
	* @return	boolean
	*/
	function _fetchVariables( $template )
	{
		/**
		 * variables already have been fetched
		 */
		if (isset($this->_templates[$template]['vars'])) {
			return true;
		}

		$iteration = $this->_templates[$template]['iteration'];

		$vars = array();
		if( isset( $this->_templates[$template]['attributes']['varscope'] ) )
		{
			if (!is_array($this->_templates[$template]['attributes']['varscope'])) {
				$this->_templates[$template]['attributes']['varscope'] = array($this->_templates[$template]['attributes']['varscope']);
			}
			foreach ($this->_templates[$template]['attributes']['varscope'] as $scopeTemplate) {
				if ($this->exists($scopeTemplate)) {
					$this->_fetchVariables( $scopeTemplate );
					$vars = array_merge($this->_templates[$scopeTemplate]['vars'], $vars);
				} else {
					patErrorManager::raiseWarning(PATTEMPLATE_WARNING_NO_TEMPLATE, 'Template \''.$scopeTemplate.'\' does not exist, referenced in varscope attribute of template \''.$template.'\'');
				}
			}
		} else {
			$vars	=	array();
		}

		/**
		 * get the scalar variables
		 */
		if( isset( $this->_vars[$template] ) && isset( $this->_vars[$template]['scalar'] ) )
		{
			$vars = array_merge( $vars, $this->_vars[$template]['scalar'] );
		}

		/**
		 * get the row variables
		 */
		if( isset( $this->_vars[$template]['rows'][$iteration] ) )
		{
			$vars = array_merge( $vars, $this->_vars[$template]['rows'][$iteration] );
		}

		/**
		 * add some system variables
		 */
		$currentRow				=	$iteration + $this->_templates[$template]['attributes']['rowoffset'];
		$vars['PAT_ROW_VAR']	=	$currentRow;

		if( $this->_templates[$template]['attributes']['type'] == 'modulo' )
		{
			$vars['PAT_MODULO_REP']	=	ceil( $currentRow / $this->_templates[$template]['attributes']['modulo'] );
			$vars['PAT_MODULO']		=	( $this->_templates[$template]['iteration'] + 1 ) % $this->_templates[$template]['attributes']['modulo'];
		}

		if( $this->_templates[$template]['attributes']['addsystemvars'] !== false )
		{
			$vars['PATTEMPLATE_VERSION'] = $this->_systemVars['appVersion'];
			$vars['PAT_LOOPS']		=	$this->_templates[$template]['loop'];

			switch ($this->_templates[$template]['attributes']['addsystemvars'])
			{
				case 'boolean':
					$trueValue  = 'true';
					$falseValue = 'false';
					break;
				case 'integer':
					$trueValue  = '1';
					$falseValue = '0';
					break;
				default:
					$trueValue  = $this->_templates[$template]['attributes']['addsystemvars'];
					$falseValue = '';
					break;
			}

			$vars['PAT_IS_ODD']		= ( $currentRow % 2 == 1 ) ? $trueValue : $falseValue;
			$vars['PAT_IS_EVEN']	= ( $currentRow % 2 == 0 ) ? $trueValue : $falseValue;
			$vars['PAT_IS_FIRST']	= ( $currentRow == 1 ) ? $trueValue : $falseValue;
			$vars['PAT_IS_LAST']	= ( $currentRow == $this->_templates[$template]['loop'] ) ? $trueValue : $falseValue;
			$vars['PAT_ROW_TYPE']	= ( $currentRow % 2 == 1 ) ? 'odd' : 'even';
		}

		$this->_templates[$template]['vars'] = $vars;
		return true;
	}

	/**
	* handle all unused variables in a template
	*
	* This is influenced by the 'unusedvars' attribute of the
	* template
	*
	* @access	private
	* @param	string
	*/
	function _handleUnusedVars( $template )
	{
		$regexp = '/([^\\\])('.$this->_startTag.'[^a-z]+[^\\\]'.$this->_endTag.')/U';

		switch( $this->_templates[$template]['attributes']['unusedvars'] )
		{
			case 'comment':
				$this->_templates[$template]['result'] = preg_replace( $regexp, '<!-- \\1\\2 -->', $this->_templates[$template]['result'] );
				break;
			case 'strip':
				$this->_templates[$template]['result'] = preg_replace( $regexp, '\\1', $this->_templates[$template]['result'] );
				break;
			case 'nbsp':
				$this->_templates[$template]['result'] = preg_replace( $regexp, '\\1&nbsp;', $this->_templates[$template]['result'] );
				break;
			case 'ignore':
				break;
			default:
				$this->_templates[$template]['result'] = preg_replace( $regexp, '\\1'.$this->_templates[$template]['attributes']['unusedvars'], $this->_templates[$template]['result'] );
				break;
		}

		// replace quoted variables
		$regexp = '/[\\\]'.$this->_startTag.'([^a-z]+)[\\\]'.$this->_endTag.'/U';
		$this->_templates[$template]['result'] = preg_replace( $regexp, $this->_startTag.'\\1'.$this->_endTag, $this->_templates[$template]['result'] );

		return true;
	}

	/**
	* returns a parsed Template
	*
	* If the template already has been parsed, it just returns the parsed template.
	* If the template has not been loaded, it will be loaded.
	*
	* @access	public
	* @param	string	 name of the template
	* @param	boolean  whether to apply output filters
	* @return	string	 Content of the parsed template
	* @see		displayParsedTemplate()
	*/
	function getParsedTemplate( $name = null, $applyFilters = false )
	{
		if (is_null($name)) {
			$name = $this->_root;
		}

		$name = strtolower( $name );
		$result = $this->parseTemplate( $name );

		if (patErrorManager::isError( $result )) {
			return $result;
		}

		if ($applyFilters === false) {
			return $this->_templates[$name]['result'];
		}

		$result = $this->_templates[$name]['result'];

		$cnt = count ($this->_outputFilters);
		for ($i = 0; $i < $cnt; $i++) {
			$result = $this->_outputFilters[$i]->apply( $result );
		}

		return $result;
	}

	/**
	* displays a parsed Template
	*
	* If the template has not been loaded, it will be loaded.
	*
	* @see		getParsedTemplate()
	* @param	string	name of the template
	* @param	boolean  whether to apply output filters
	* @return	boolean	true on success
	* @access	public
	*/
	function displayParsedTemplate($name = null, $applyFilters = true)
	{
		$result = $this->getParsedTemplate($name, $applyFilters);

		/**
		 * error happened
		 */
		if (patErrorManager::isError($result)) {
			return $result;
		}

		echo $result;
		return true;
	}

	/**
	* parse a template and push the result into a variable of any other
	* template
	*
	* If the template already has been parsed, it will just be pushed into the variable.
	* If the template has not been loaded, it will be loaded.
	*
	* @access	public
	* @param	string	name of the template
	* @return	string	Content of the parsed template
	* @param	boolean	if set to true, the value will be appended to the value already stored.
	* @see		getParsedTemplate()
	* @see		addVar()
	*/
	function parseIntoVar( $srcTmpl, $destTmpl, $var, $append = false )
	{
		$srcTmpl  =	strtolower( $srcTmpl );
		$destTmpl =	strtolower( $destTmpl );
		$var	  = strtoupper($var);

		$result	=	$this->parseTemplate( $srcTmpl );

		if( patErrorManager::isError( $result ) )
			return $result;

		if( $append !== true || !isset( $this->_vars[$destTmpl]['scalar'][$var] ) )
			$this->_vars[$destTmpl]['scalar'][$var] = '';

		$this->_vars[$destTmpl]['scalar'][$var] .= $this->_templates[$srcTmpl]['result'];

		return true;
	}

	/**
	* clears a parsed Template
	*
	* Parsed Content, variables and the loop attribute are cleared
	*
	* If you will not be using this template anymore, then you should
	* call freeTemplate()
	*
	* @access	public
	* @param	string	name of the template
	* @param	boolean		set this to true to clear all child templates, too
	* @see		clearAllTemplates()
	* @see		freeTemplate()
	*/
	function clearTemplate( $name, $recursive = false )
	{
		$name	=	strtolower( $name );
		$this->_templates[$name]['parsed']		=	false;
		$this->_templates[$name]['work']		=	'';
		$this->_templates[$name]['iteration']	=	0;
		$this->_templates[$name]['result']		=	'';
		$this->_vars[$name]						=	array(
														'scalar'	=>	array(),
														'rows'		=>	array()
													);

		if (!empty($this->_templates[$name]['defaultVars'])) {
			foreach ($this->_templates[$name]['defaultVars'] as $varname => $value) {
				$this->addVar($name, $varname, $value);
			}
		}

		/**
		 * clear child templates as well
		 */
		if( $recursive === true )
		{
			$deps = $this->_getDependencies( $name );
			foreach( $deps as $dep )
			{
				$this->clearTemplate( $dep, true );
			}
		}
		return true;
	}

	/**
	* clears all templates
	*
	* @access	public
	* @uses		clearTemplate()
	*/
	function clearAllTemplates()
	{
		$templates	=	array_keys( $this->_templates );
		$cnt		=	count( $templates );
		for( $i = 0; $i < $cnt; $i++ )
		{
			$this->clearTemplate( $templates[$i] );
		}
		return true;
	}

	/**
	* frees a template
	*
	* All memory consumed by the template
	* will be freed.
	*
	* @access	public
	* @param	string	name of the template
	* @param	boolean	clear dependencies of the template
	* @see		freeAllTemplates()
	*/
	function freeTemplate( $name, $recursive = false )
	{
		$name	=	strtolower( $name );
		$key = array_search( $name, $this->_templateList );
		if( $key === false )
		{
			return	patErrorManager::raiseWarning(
													PATTEMPLATE_WARNING_NO_TEMPLATE,
													"Template '$name' does not exist."
												);
		}

		unset( $this->_templateList[$key] );
		$this->_templateList = array_values( $this->_templateList );

		/**
		 * free child templates as well
		 */
		if( $recursive === true )
		{
			$deps = $this->_getDependencies( $name );
			foreach( $deps as $dep )
			{
				$this->freeTemplate( $dep, true );
			}
		}

		unset( $this->_templates[$name] );
		unset( $this->_vars[$name] );
		if (isset($this->_discoveredPlaceholders[$name])) {
			unset($this->_discoveredPlaceholders[$name]);
		}

		return true;
	}

	/**
	* frees all templates
	*
	* All memory consumed by the templates
	* will be freed.
	*
	* @access	public
	* @see		freeTemplate()
	*/
	function freeAllTemplates()
	{
		$this->_templates	 = array();
		$this->_vars		 = array();
		$this->_templateList = array();
	}

	/**
	* get _all_ dependencies of a template,
	* regardless of the subtemplates
	*
	* @access	private
	* @param	string	template name
	* @return	array	list of all subtemplates
	*/
	function _getDependencies( $template )
	{
		$deps = array();
		if( isset( $this->_templates[$template]['dependencies'] ) )
			$deps = $this->_templates[$template]['dependencies'];

		if( isset( $this->_templates[$template]['subtemplates'] ) )
		{
			foreach( $this->_templates[$template]['subtemplates'] as $sub )
			{
				if( isset( $sub['dependencies'] ) )
					$deps = array_merge( $deps, $sub['dependencies'] );
			}
		}
		$deps = array_unique( $deps );
		return $deps;
	}

	/**
	* Displays useful information about all or named templates
	*
	* This method breaks BC, as it now awaits an array instead of
	* unlimited parameters.
	*
	* @param	mixed	array of templates that should be dumped, or null if you
	*					want all templates to be dumped
	* @param	string	dumper
	* @access	public
	*/
	function dump( $restrict = null, $dumper = 'Html' )
	{
		if( is_string( $restrict ) )
			$restrict = array( $restrict );

		$dumper	=	&$this->loadModule( 'Dump', $dumper );

		if( patErrorManager::isError( $dumper ) )
		{
			return	$dumper;
		}

		if( is_null( $restrict ) )
		{
			$templates = $this->_templates;
			$vars	  = $this->_vars;
		}
		else
		{
			$restrict = array_map( 'strtolower', $restrict );

			$templates = array();
			$vars	  = array();

			foreach( $this->_templates as $name => $spec )
			{
				if( !in_array( $name, $restrict ) )
					continue;
				$templates[$name] = $spec;
				$vars[$name]	  = $this->_vars[$name];
			}
		}

		$dumper->displayHeader();
		$dumper->dumpGlobals( $this->_globals );
		$dumper->dumpTemplates( $templates, $vars );
		$dumper->displayFooter();

		return	true;
	}

	/**
	* get the include path
	*
	* @access	public
	* @return   string
	*/
	function getIncludePath()
	{
		return	PATTEMPLATE_INCLUDE_PATH;
	}

	/**
	* apply input filters that have been set
	*
	* This is being called by the readers.
	*
	* @access	public
	* @param	string		template
	* @return	string		filtered templeta
	*/
	function applyInputFilters( $template )
	{
		$cnt = count( $this->_inputFilters );
		for( $i = 0; $i < $cnt; $i++ )
		{
			$template = $this->_inputFilters[$i]->apply( $template );
		}
		return $template;
	}

	/**
	* checks, whether a placeholder exists in a template
	*
	* @access   public
	* @param	string	  name of the placeholder
	* @param	string	  name of the template
	* @param	boolean	 whether to use the cached result of a previous call
	*/
	function placeholderExists($placeholder, $tmpl, $cached = true)
	{
		$tmpl = strtolower($tmpl);
		$placeholder = strtoupper($placeholder);

		if (!$this->exists($tmpl)) {
			return false;
		}

		if ($cached === true) {
			if (isset($this->_discoveredPlaceholders[$tmpl]) && isset($this->_discoveredPlaceholders[$tmpl][$placeholder])) {
				return $this->_discoveredPlaceholders[$tmpl][$placeholder];
			}
		}

		if (isset($this->_templates[$tmpl]['subtemplates'])) {
			$content = '';
			foreach ($this->_templates[$tmpl]['subtemplates'] as $temp) {
				if (!isset($temp['data'])) {
					continue;
				}
				$content .= $temp['data'];
			}
		} else {
			$content = $this->_templates[$tmpl]['content'];
		}

		$search = $this->_startTag . $placeholder . $this->_endTag;
		if (strstr($content, $search) !== false) {
			$this->_discoveredPlaceholders[$tmpl][$placeholder] = true;
			return true;
		}
		$this->_discoveredPlaceholders[$tmpl][$placeholder] = false;
		return false;
	}

	/**
	* Convert the template to its string representation.
	*
	* This method allows you to just echo the patTemplate
	* object in order to display the template.
	*
	* Requires PHP5
	*
	* <code>
	* $tmpl = new patTemplate();
	* $tmpl->readTemplatesFromFile( 'myfile.tmpl' );
	* echo $tmpl;
	* </code>
	*
	* @access	private
	* @return	string
	*/
	function __toString()
	{
		return $this->getParsedTemplate();
	}
}
?>
