text: <?php
  
  /**
  * 
  * Provides an object-oriented template system for PHP5.
  * 
  * @package Savant3
  * 
  * @author Paul M. Jones <pmjones@ciaweb.net>
  * 
  * @license http://www.gnu.org/copyleft/lesser.html LGPL
  * 
  * @version $Id$
  * 
  */
  
  
  /**
  * Always have these classes available.
  */
  //include_once dirname(__FILE__) . '/Savant3/Filter.php';
  //include_once dirname(__FILE__) . '/Savant3/Plugin.php';
  
  
  /**
  * 
  * Provides an object-oriented template system for PHP5.
  * 
  * Savant3 helps you separate business logic from presentation logic
  * using PHP as the template language. By default, Savant3 does not
  * compile templates. However, you may pass an optional compiler object
  * to compile template source to include-able PHP code.  It is E_STRICT
  * compliant for PHP5.
  * 
  * Please see the documentation at {@link http://phpsavant.com/}, and be
  * sure to donate! :-)
  * 
  * @author Paul M. Jones <pmjones@ciaweb.net>
  * 
  * @package Savant3
  * 
  * @version @package_version@
  * 
  */
  
  class Savant3 {
  	
  	
  	/**
  	* 
  	* Array of configuration parameters.
  	* 
  	* @access protected
  	* 
  	* @var array
  	* 
  	*/
  	
  	protected $__config = array(
  		'template_path' => array(),
  		'resource_path' => array(),
  		'error_text'    => "\n\nTemplate error, template file not found\n\n",
  		'exceptions'    => false,
  		'autoload'      => false,
  		'compiler'      => null,
  		'filters'       => array(),
  		'plugins'       => array(),
  		'template'      => null,
  		'plugin_conf'   => array(),
  		'extract'       => false,
  		'fetch'         => null,
  		'escape'        => array('htmlspecialchars'),
  	);
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// Constructor and magic methods
  	//
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	* 
  	* Constructor.
  	* 
  	* @access public
  	* 
  	* @param array $config An associative array of configuration keys for
  	* the Savant3 object.  Any, or none, of the keys may be set.
  	* 
  	* @return object Savant3 A Savant3 instance.
  	* 
  	*/
  	
  	public function __construct($config = null)
  	{
  		// force the config to an array
  		settype($config, 'array');
  		
  		// set the default template search path
  		if (isset($config['template_path'])) {
  			// user-defined dirs
  			$this->setPath('template', $config['template_path']);
  		} else {
  			// no directories set, use the
  			// default directory only
  			$this->setPath('template', null);
  		}
  		
  		// set the default resource search path
  		if (isset($config['resource_path'])) {
  			// user-defined dirs
  			$this->setPath('resource', $config['resource_path']);
  		} else {
  			// no directories set, use the
  			// default directory only
  			$this->setPath('resource', null);
  		}
  		
  		// set the error reporting text
  		if (isset($config['error_text'])) {
  			$this->setErrorText($config['error_text']);
  		}
  		
  		// set the autoload flag
  		if (isset($config['autoload'])) {
  			$this->setAutoload($config['autoload']);
  		}
  		
  		// set the extraction flag
  		if (isset($config['extract'])) {
  			$this->setExtract($config['extract']);
  		}
  		
  		// set the exceptions flag
  		if (isset($config['exceptions'])) {
  			$this->setExceptions($config['exceptions']);
  		}
  		
  		// set the template to use for output
  		if (isset($config['template'])) {
  			$this->setTemplate($config['template']);
  		}
  		
  		// set the output escaping callbacks
  		if (isset($config['escape'])) {
  			$this->setEscape($config['escape']);
  		}
  		
  		// set the default plugin configs
  		if (isset($config['plugin_conf']) && is_array($config['plugin_conf'])) {
  			foreach ($config['plugin_conf'] as $name => $opts) {
  				$this->setPluginConf($name, $opts);
  			}
  		}
  		
  		// set the default filter callbacks
  		if (isset($config['filters'])) {
  			$this->addFilters($config['filters']);
  		}
  	}
  	
  	
  	/**
  	*
  	* Executes a main plugin method with arbitrary parameters.
  	* 
  	* @access public
  	* 
  	* @param string $func The plugin method name.
  	*
  	* @param array $args The parameters passed to the method.
  	*
  	* @return mixed The plugin output, or a Savant3_Error with an
  	* ERR_PLUGIN code if it can't find the plugin.
  	* 
  	*/
  	
  	public function __call($func, $args)
  	{
  		$plugin = $this->plugin($func);
  		
  		if ($this->isError($plugin)) {
  			return $plugin;
  		}
  		
  		// try to avoid the very-slow call_user_func_array()
  		// for plugins with very few parameters.  thanks to
  		// Andreas Korthaus for profiling the code to find
  		// the slowdown.
  		switch (count($args)) {
  		
  		case 0:
  			return $plugin->$func();
  		
  		case 1:
  			return $plugin->$func($args[0]);
  			break;
  			
  		case 2:
  			return $plugin->$func($args[0], $args[1]);
  			break;
  			
  		case 3:
  			return $plugin->$func($args[0], $args[1], $args[2]);
  			break;
  		
  		default:
  			return call_user_func_array(array($plugin, $func), $args);
  			break;
  		}
  	}
  	
  	
  	/**
  	* 
  	* Magic method to echo this object as template output.
  	* 
  	* Note that if there is an error, this will output a simple
  	* error text string and will not return an error object.  Use
  	* fetch() to get an error object when errors occur.
  	* 
  	* @access public
  	*  
  	* @return string The template output.
  	* 
  	*/
  	
  	public function __toString()
  	{
  		return $this->getOutput();
  	}
  	
  	
  	/**
  	* 
  	* Reports the API version for this class.
  	* 
  	* @access public
  	* 
  	* @return string A PHP-standard version number.
  	* 
  	*/
  	
  	public function apiVersion()
  	{
  		return '@package_version@';
  	}
  	
  	
  	/**
  	* 
  	* Returns an internal plugin object; creates it as needed.
  	* 
  	* @access public
  	* 
  	* @param string $name The plugin name.  If this plugin has not
  	* been created yet, this method creates it automatically.
  	*
  	* @return mixed The plugin object, or a Savant3_Error with an
  	* ERR_PLUGIN code if it can't find the plugin.
  	* 
  	*/
  	
  	public function plugin($name)
  	{
  		// shorthand reference
  		$plugins =& $this->__config['plugins'];
  		$autoload = $this->__config['autoload'];
  		
  		// is the plugin method object already instantiated?
  		if (! array_key_exists($name, $plugins)) {
  			
  			// not already instantiated, so load it up.
  			// set up the class name.
  			$class = "Savant3_Plugin_$name";
  			
  			// has the class been loaded?
  			if (! class_exists($class, $autoload)) {
  			
  				// class is not loaded, set up the file name.
  				$file = "$class.php";
  				
  				// make sure the class file is available from the resource path.
  				$result = $this->findFile('resource', $file);
  				if (! $result) {
  					// not available, this is an error
  					return $this->error(
  						'ERR_PLUGIN',
  						array('method' => $name)
  					);
  				} else {
  					// available, load the class file
  					//include_once $result;
  				}
  			}
  			
  			// get the default configuration for the plugin.
  			$plugin_conf =& $this->__config['plugin_conf'];
  			if (! empty($plugin_conf[$name])) {
  				$opts = $plugin_conf[$name];
  			} else {
  				$opts = array();
  			}
  			
  			// add the Savant reference
  			$opts['Savant'] = $this;
  			
  			// instantiate the plugin with its options.
  			$plugins[$name] = new $class($opts);
  		}
  	
  		// return the plugin object
  		return $plugins[$name];
  	}
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// Public configuration management (getters and setters).
  	// 
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	*
  	* Returns a copy of the Savant3 configuration parameters.
  	*
  	* @access public
  	* 
  	* @param string $key The specific configuration key to return.  If null,
  	* returns the entire configuration array.
  	* 
  	* @return mixed A copy of the $this->__config array.
  	* 
  	*/
  	
  	public function getConfig($key = null)
  	{
  		if (is_null($key)) {
  			// no key requested, return the entire config array
  			return $this->__config;
  		} elseif (empty($this->__config[$key])) {
  			// no such key
  			return null;
  		} else {
  			// return the requested key
  			return $this->__config[$key];
  		}
  	}
  	
  	
  	/**
  	* 
  	* Sets __autoload() usage on or off.
  	* 
  	* @access public
  	* 
  	* @param bool $flag True to use __autoload(), false to not use it.
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function setAutoload($flag)
  	{
  		$this->__config['autoload'] = (bool) $flag;
  	}
  	
  	
  	/**
  	* 
  	* Sets a custom compiler/pre-processor callback for template sources.
  	* 
  	* By default, Savant3 does not use a compiler; use this to set your
  	* own custom compiler (pre-processor) for template sources.
  	* 
  	* @access public
  	* 
  	* @param mixed $compiler A compiler callback value suitable for the
  	* first parameter of call_user_func().  Set to null/false/empty to
  	* use PHP itself as the template markup (i.e., no compiling).
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function setCompiler($compiler)
  	{
  		$this->__config['compiler'] = $compiler;
  	}
  	
  	
  	/**
  	* 
  	* Sets the custom error text for __toString().
  	* 
  	* @access public
  	* 
  	* @param string $text The error text when a template is echoed.
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function setErrorText($text)
  	{
  		$this->__config['error_text'] = $text;
  	}
  	
  	
  	/**
  	* 
  	* Sets whether or not exceptions will be thrown.
  	* 
  	* @access public
  	* 
  	* @param bool $flag True to turn on exception throwing, false
  	* to turn it off.
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function setExceptions($flag)
  	{
  		$this->__config['exceptions'] = (bool) $flag;
  	}
  	
  	
  	/**
  	* 
  	* Sets whether or not variables will be extracted.
  	* 
  	* @access public
  	* 
  	* @param bool $flag True to turn on variable extraction, false
  	* to turn it off.
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function setExtract($flag)
  	{
  		$this->__config['extract'] = (bool) $flag;
  	}
  	
  	
  	/**
  	*
  	* Sets config array for a plugin.
  	* 
  	* @access public
  	* 
  	* @param string $plugin The plugin to configure.
  	* 
  	* @param array $config The configuration array for the plugin.
  	* 
  	* @return void
  	*
  	*/
  	
  	public function setPluginConf($plugin, $config = null)
  	{
  		$this->__config['plugin_conf'][$plugin] = $config;
  	}
  	
  	
  	/**
  	*
  	* Sets the template name to use.
  	*
  	* @access public
  	*
  	* @param string $template The template name.
  	*
  	* @return void
  	*
  	*/
  	
  	public function setTemplate($template)
  	{
  		$this->__config['template'] = $template;
  	}
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// Output escaping and management.
  	//
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	* 
  	* Clears then sets the callbacks to use when calling $this->escape().
  	* 
  	* Each parameter passed to this function is treated as a separate
  	* callback.  For example:
  	* 
  	* <code>
  	* $savant->setEscape(
  	*     'stripslashes',
  	*     'htmlspecialchars',
  	*     array('StaticClass', 'method'),
  	*     array($object, $method)
  	* );
  	* </code>
  	* 
  	* @access public
  	*
  	* @return void
  	*
  	*/
  	
  	public function setEscape()
  	{
  		$this->__config['escape'] = (array) @func_get_args();
  	}
  	
  	
  	/**
  	* 
  	* Adds to the callbacks used when calling $this->escape().
  	* 
  	* Each parameter passed to this function is treated as a separate
  	* callback.  For example:
  	* 
  	* <code>
  	* $savant->addEscape(
  	*     'stripslashes',
  	*     'htmlspecialchars',
  	*     array('StaticClass', 'method'),
  	*     array($object, $method)
  	* );
  	* </code>
  	* 
  	* @access public
  	*
  	* @return void
  	*
  	*/
  	
  	public function addEscape()
  	{
  		$args = (array) @func_get_args();
  		$this->__config['escape'] = array_merge(
  			$this->__config['escape'], $args
  		);
  	}
  	
  	
  	/**
  	*
  	* Gets the array of output-escaping callbacks.
  	*
  	* @access public
  	*
  	* @return array The array of output-escaping callbacks.
  	*
  	*/
  	
  	public function getEscape()
  	{
  		return $this->__config['escape'];
  	}
  	
  	
  	/**
  	*
  	* Applies escaping to a value.
  	* 
  	* You can override the predefined escaping callbacks by passing
  	* added parameters as replacement callbacks.
  	* 
  	* <code>
  	* // use predefined callbacks
  	* $result = $savant->escape($value);
  	* 
  	* // use replacement callbacks
  	* $result = $savant->escape(
  	*     $value,
  	*     'stripslashes',
  	*     'htmlspecialchars',
  	*     array('StaticClass', 'method'),
  	*     array($object, $method)
  	* );
  	* </code>
  	*
  	* 
  	* Unfortunately, a call to "echo htmlspecialchars()" is twice
  	* as fast as a call to "echo $this->escape()" under the default
  	* escaping (which is htmlspecialchars).  The benchmark showed
  	* 0.007 seconds for htmlspecialchars(), and 0.014 seconds for
  	* $this->escape(), on 300 calls each.
  	* 
  	* @access public
  	* 
  	* @param mixed $value The value to be escaped.
  	* 
  	* @return mixed
  	*
  	*/
  	
  	public function escape($value)
  	{
  		// were custom callbacks passed?
  		if (func_num_args() == 1) {
  		
  			// no, only a value was passed.
  			// loop through the predefined callbacks.
  			foreach ($this->__config['escape'] as $func) {
  				// this if() shaves 0.001sec off of 300 calls.
  				if (is_string($func)) {
  					$value = $func($value);
  				} else {
  					$value = call_user_func($func, $value);
  				}
  			}
  			
  		} else {
  		
  			// yes, use the custom callbacks
  			$callbacks = func_get_args();
  			
  			// drop $value
  			array_shift($callbacks);
  			
  			// loop through custom callbacks.
  			foreach ($callbacks as $func) {
  				// this if() shaves 0.001sec off of 300 calls.
  				if (is_string($func)) {
  					$value = $func($value);
  				} else {
  					$value = call_user_func($func, $value);
  				}
  			}
  			
  		}
  		
  		return $value;
  	}
  	
  	
  	/**
  	*
  	* Prints a value after escaping it for output.
  	* 
  	* You can override the predefined escaping callbacks by passing
  	* added parameters as replacement callbacks.
  	* 
  	* <code>
  	* // use predefined callbacks
  	* $this->eprint($value);
  	* 
  	* // use replacement callbacks
  	* $this->eprint(
  	*     $value,
  	*     'stripslashes',
  	*     'htmlspecialchars',
  	*     array('StaticClass', 'method'),
  	*     array($object, $method)
  	* );
  	* </code>
  	* 
  	* @access public
  	* 
  	* @param mixed $value The value to be escaped and printed.
  	* 
  	* @return void
  	*
  	*/
  	
  	public function eprint($value)
  	{
  		// avoid the very slow call_user_func_array() when there
  		// are no custom escaping callbacks.  thanks to
  		// Andreas Korthaus for profiling the code to find
  		// the slowdown.
  		$num = func_num_args();
  		if ($num == 1) {
  			echo $this->escape($value);
  		} else {
  			$args = func_get_args();
  			echo call_user_func_array(
  				array($this, 'escape'),
  				$args
  			);
  		}
  	}
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// File management
  	//
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	*
  	* Sets an entire array of search paths for templates or resources.
  	*
  	* @access public
  	*
  	* @param string $type The type of path to set, typically 'template'
  	* or 'resource'.
  	* 
  	* @param string|array $path The new set of search paths.  If null or
  	* false, resets to the current directory only.
  	*
  	* @return void
  	*
  	*/
  	
  	public function setPath($type, $path)
  	{
  		// clear out the prior search dirs
  		$this->__config[$type . '_path'] = array();
  		
  		// always add the fallback directories as last resort
  		switch (strtolower($type)) {
  		case 'template':
  			// the current directory
  			$this->addPath($type, '.');
  			break;
  		case 'resource':
  			// the Savant3 distribution resources
  			$this->addPath($type, dirname(__FILE__) . '/Savant3/resources/');
  			break;
  		}
  		
  		// actually add the user-specified directories
  		$this->addPath($type, $path);
  	}
  	
  	
  	/**
  	*
  	* Adds to the search path for templates and resources.
  	*
  	* @access public
  	*
  	* @param string|array $path The directory or stream to search.
  	*
  	* @return void
  	*
  	*/
  	
  	public function addPath($type, $path)
  	{
  		// convert from path string to array of directories
  		if (is_string($path) && ! strpos($path, '://')) {
  		
  			// the path config is a string, and it's not a stream
  			// identifier (the "://" piece). add it as a path string.
  			$path = explode(PATH_SEPARATOR, $path);
  			
  			// typically in path strings, the first one is expected
  			// to be searched first. however, Savant3 uses a stack,
  			// so the first would be last.  reverse the path string
  			// so that it behaves as expected with path strings.
  			$path = array_reverse($path);
  			
  		} else {
  		
  			// just force to array
  			settype($path, 'array');
  			
  		}
  		
  		// loop through the path directories
  		foreach ($path as $dir) {
  		
  			// no surrounding spaces allowed!
  			$dir = trim($dir);
  			
  			// add trailing separators as needed
  			if (strpos($dir, '://') && substr($dir, -1) != '/') {
  				// stream
  				$dir .= '/';
  			} elseif (substr($dir, -1) != DIRECTORY_SEPARATOR) {
  				// directory
  				$dir .= DIRECTORY_SEPARATOR;
  			}
  			
  			// add to the top of the search dirs
  			array_unshift(
  				$this->__config[$type . '_path'],
  				$dir
  			);
  		}
  	}
  	
  	
  	/**
  	* 
  	* Searches the directory paths for a given file.
  	* 
  	* @param array $type The type of path to search (template or resource).
  	* 
  	* @param string $file The file name to look for.
  	* 
  	* @return string|bool The full path and file name for the target file,
  	* or boolean false if the file is not found in any of the paths.
  	*
  	*/
  	
  	protected function findFile($type, $file)
  	{
  		// get the set of paths
  		$set = $this->__config[$type . '_path'];
  		
  		// start looping through the path set
  		foreach ($set as $path) {
  			
  			// get the path to the file
  			$fullname = $path . $file;
  			
  			// is the path based on a stream?
  			if (strpos($path, '://') === false) {
  				// not a stream, so do a realpath() to avoid
  				// directory traversal attempts on the local file
  				// system. Suggested by Ian Eure, initially
  				// rejected, but then adopted when the secure
  				// compiler was added.
  				$path = realpath($path); // needed for substr() later
  				$fullname = realpath($fullname);
  			}
  			
  			// the substr() check added by Ian Eure to make sure
  			// that the realpath() results in a directory registered
  			// with Savant so that non-registered directores are not
  			// accessible via directory traversal attempts.
  			if (file_exists($fullname) && is_readable($fullname) &&
  				substr($fullname, 0, strlen($path)) == $path) {
  				return $fullname;
  			}
  		}
  		
  		// could not find the file in the set of paths
  		return false;
  	}
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// Variable and reference assignment
  	//
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	* 
  	* Sets variables for the template (by copy).
  	* 
  	* This method is overloaded; you can assign all the properties of
  	* an object, an associative array, or a single value by name.
  	* 
  	* You are not allowed to assign any variable named '__config' as
  	* it would conflict with internal configuration tracking.
  	* 
  	* In the following examples, the template will have two variables
  	* assigned to it; the variables will be known inside the template as
  	* "$this->var1" and "$this->var2".
  	* 
  	* <code>
  	* $Savant3 = new Savant3();
  	* 
  	* // assign by object
  	* $obj = new stdClass;
  	* $obj->var1 = 'something';
  	* $obj->var2 = 'else';
  	* $Savant3->assign($obj);
  	* 
  	* // assign by associative array
  	* $ary = array('var1' => 'something', 'var2' => 'else');
  	* $Savant3->assign($ary);
  	* 
  	* // assign by name and value
  	* $Savant3->assign('var1', 'something');
  	* $Savant3->assign('var2', 'else');
  	* 
  	* // assign directly
  	* $Savant3->var1 = 'something';
  	* $Savant3->var2 = 'else';
  	* </code>
  	* 
  	* @access public
  	* 
  	* @return bool True on success, false on failure.
  	* 
  	*/
  	
  	public function assign()
  	{
  		// get the arguments; there may be 1 or 2.
  		$arg0 = @func_get_arg(0);
  		$arg1 = @func_get_arg(1);
  		
  		// assign from object
  		if (is_object($arg0)) {
  			// assign public properties
  			foreach (get_object_vars($arg0) as $key => $val) {
  				// can't assign to __config
  				if ($key != '__config') {
  					$this->$key = $val;
  				}
  			}
  			return true;
  		}
  		
  		// assign from associative array
  		if (is_array($arg0)) {
  			foreach ($arg0 as $key => $val) {
  				// can't assign to __config
  				if ($key != '__config') {
  					$this->$key = $val;
  				}
  			}
  			return true;
  		}
  		
  		// assign by name and value (can't assign to __config).
  		if (is_string($arg0) && func_num_args() > 1 && $arg0 != '__config') {
  			$this->$arg0 = $arg1;
  			return true;
  		}
  		
  		// $arg0 was not object, array, or string.
  		return false;
  	}
  	
  	
  	/**
  	* 
  	* Sets variables for the template (by reference).
  	* 
  	* You are not allowed to assign any variable named '__config' as
  	* it would conflict with internal configuration tracking.
  	* 
  	* <code>
  	* $Savant3 = new Savant3();
  	* 
  	* // assign by name and value
  	* $Savant3->assignRef('var1', $ref);
  	* 
  	* // assign directly
  	* $Savant3->ref =& $var1;
  	* </code>
  	* 
  	* @access public
  	* 
  	* @return bool True on success, false on failure.
  	* 
  	*/
  	
  	public function assignRef($key, &$val)
  	{
  		// assign by name and reference (can't assign to __config).
  		if ($key != '__config') {
  			$this->$key =& $val;
  			return true;
  		} else {
  			return false;
  		}
  	}
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// Template processing
  	//
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	* 
  	* Displays a template directly (equivalent to <code>echo $tpl</code>).
  	* 
  	* @access public
  	* 
  	* @param string $tpl The template source to compile and display.
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function display($tpl = null)
  	{
  		echo $this->getOutput($tpl);
  	}
  	
  	/**
  	 * Returns output, including error_text if an error occurs.
  	 * 
  	 * @param $tpl The template to process; if null, uses the
  	 * default template set with setTemplate().
  	 * 
  	 * @return string The template output.
  	 */
  	public function getOutput($tpl = null)
  	{
  	    $output = $this->fetch($tpl);
          if ($this->isError($output)) {
              $text = $this->__config['error_text'];
              return $this->escape($text);
          } else {
              return $output;
          }
  	}
  	
  	
  	/**
  	* 
  	* Compiles, executes, and filters a template source.
  	* 
  	* @access public
  	* 
  	* @param string $tpl The template to process; if null, uses the
  	* default template set with setTemplate().
  	* 
  	* @return mixed The template output string, or a Savant3_Error.
  	* 
  	*/
  	
  	public function fetch($tpl = null)
  	{
  		// make sure we have a template source to work with
  		if (is_null($tpl)) {
  			$tpl = $this->__config['template'];
  		}
  		
  		// get a path to the compiled template script
  		$result = $this->template($tpl);
  		
  		// did we get a path?
  		if (! $result || $this->isError($result)) {
  		
  			// no. return the error result.
  			return $result;
  			
  		} else {
  		
  			// yes.  execute the template script.  move the script-path
  			// out of the local scope, then clean up the local scope to
  			// avoid variable name conflicts.
  			$this->__config['fetch'] = $result;
  			unset($result);
  			unset($tpl);
  			
  			// are we doing extraction?
  			if ($this->__config['extract']) {
  				// pull variables into the local scope.
  				extract(get_object_vars($this), EXTR_REFS);
  			}
  			
  			// buffer output so we can return it instead of displaying.
  			ob_start();
  			
  			// are we using filters?
  			if ($this->__config['filters']) {
  				// use a second buffer to apply filters. we used to set
  				// the ob_start() filter callback, but that would
  				// silence errors in the filters. Hendy Irawan provided
  				// the next three lines as a "verbose" fix.
  				ob_start();
  				include $this->__config['fetch'];
  				echo $this->applyFilters(ob_get_clean());
  			} else {
  				// no filters being used.
  				include $this->__config['fetch'];
  			}
  			
  			// reset the fetch script value, get the buffer, and return.
  			$this->__config['fetch'] = null;
  			return ob_get_clean();
  		}
  	}
  	
  	
  	/**
  	*
  	* Compiles a template and returns path to compiled script.
  	* 
  	* By default, Savant does not compile templates, it uses PHP as the
  	* markup language, so the "compiled" template is the same as the source
  	* template.
  	* 
  	* Used inside a template script like so:
  	* 
  	* <code>
  	* include $this->template($tpl);
  	* </code>
  	* 
  	* @access protected
  	*
  	* @param string $tpl The template source name to look for.
  	* 
  	* @return string The full path to the compiled template script.
  	* 
  	* @throws object An error object with a 'ERR_TEMPLATE' code.
  	* 
  	*/
  	
  	protected function template($tpl = null)
  	{
  		// set to default template if none specified.
  		if (is_null($tpl)) {
  			$tpl = $this->__config['template'];
  		}
  		
  		// find the template source.
  		$file = $this->findFile('template', $tpl);
  		if (! $file) {
  			return $this->error(
  				'ERR_TEMPLATE',
  				array('template' => $tpl)
  			);
  		}
  		
  		// are we compiling source into a script?
  		if ($this->__config['compiler']) {
  			// compile the template source and get the path to the
  			// compiled script (will be returned instead of the
  			// source path)
  			$result = call_user_func(
  				array($this->__config['compiler'], 'compile'),
  				$file
  			);
  		} else {
  			// no compiling requested, use the source path
  			$result = $file;
  		}
  		
  		// is there a script from the compiler?
  		if (! $result || $this->isError($result)) {
  			// return an error, along with any error info
  			// generated by the compiler.
  			return $this->error(
  				'ERR_COMPILER',
  				array(
  					'template' => $tpl,
  					'compiler' => $result
  				)
  			);
  		} else {
  			// no errors, the result is a path to a script
  			return $result;
  		}
  	}
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// Filter management and processing
  	//
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	* 
  	* Resets the filter stack to the provided list of callbacks.
  	* 
  	* @access protected
  	* 
  	* @param array An array of filter callbacks.
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function setFilters()
  	{
  		$this->__config['filters'] = (array) @func_get_args();
  	}
  	
  	
  	/**
  	* 
  	* Adds filter callbacks to the stack of filters.
  	* 
  	* @access protected
  	* 
  	* @param array An array of filter callbacks.
  	* 
  	* @return void
  	* 
  	*/
  	
  	public function addFilters()
  	{
  		// add the new filters to the static config variable
  		// via the reference
  		foreach ((array) @func_get_args() as $callback) {
  			$this->__config['filters'][] = $callback;
  		}
  	}
  	
  	
  	/**
  	* 
  	* Runs all filter callbacks on buffered output.
  	* 
  	* @access protected
  	* 
  	* @param string The template output.
  	* 
  	* @return void
  	* 
  	*/
  	
  	protected function applyFilters($buffer)
  	{
  	   $autoload = $this->__config['autoload'];
  		foreach ($this->__config['filters'] as $callback) {
  		
  			// if the callback is a static Savant3_Filter method,
  			// and not already loaded, try to auto-load it.
  			if (is_array($callback) &&
  				is_string($callback[0]) &&
  				substr($callback[0], 0, 15) == 'Savant3_Filter_' &&
  				! class_exists($callback[0], $autoload)) {
  				
  				// load the Savant3_Filter_*.php resource
  				$file = $callback[0] . '.php';
  				$result = $this->findFile('resource', $file);
  				if ($result) {
  					include_once $result;
  				}
  			}
  			
  			// can't pass a third $this param, it chokes the OB system.
  			$buffer = call_user_func($callback, $buffer);
  		}
  		
  		return $buffer;
  	}
  	
  	
  	// -----------------------------------------------------------------
  	//
  	// Error handling
  	//
  	// -----------------------------------------------------------------
  	
  	
  	/**
  	*
  	* Returns an error object or throws an exception.
  	* 
  	* @access public
  	* 
  	* @param string $code A Savant3 'ERR_*' string.
  	* 
  	* @param array $info An array of error-specific information.
  	* 
  	* @param int $level The error severity level, default is
  	* E_USER_ERROR (the most severe possible).
  	* 
  	* @param bool $trace Whether or not to include a backtrace, default
  	* true.
  	* 
  	* @return object Savant3_Error
  	* 
  	*/
  	
  	public function error($code, $info = array(), $level = E_USER_ERROR,
  		$trace = true)
  	{
  		$autoload = $this->__config['autoload'];
  		
  		// are we throwing exceptions?
  		if ($this->__config['exceptions']) {
  			if (! class_exists('Savant3_Exception', $autoload)) {
  				include_once dirname(__FILE__) . '/Savant3/Exception.php';
  			}
  			throw new Savant3_Exception($code);
  		}
  		
  		
  		// the error config array
  		$config = array(
  			'code'  => $code,
  			'info'  => (array) $info,
  			'level' => $level,
  			'trace' => $trace
  		);
  		
  		// make sure the Savant3 error class is available
  		if (! class_exists('Savant3_Error', $autoload)) {
  			include_once dirname(__FILE__) . '/Savant3/Error.php';
  		}
  		
  		// return it
  		$err = new Savant3_Error($config);
  		return $err;
  	}
  	
  	
  	/**
  	*
  	* Tests if an object is of the Savant3_Error class.
  	* 
  	* @access public
  	* 
  	* @param object $obj The object to be tested.
  	* 
  	* @return boolean True if $obj is an error object of the type
  	* Savant3_Error, or is a subclass that Savant3_Error. False if not.
  	*
  	*/
  	
  	public function isError($obj)
  	{
  		$autoload = $this->__config['autoload'];
  		
  		// is it even an object?
  		if (! is_object($obj)) {
  			// not an object, so can't be a Savant3_Error
  			return false;
  		} else {
  			// make sure the Savant3 error class is available for
  			// comparison
  			if (! class_exists('Savant3_Error', $autoload)) {
  				include_once dirname(__FILE__) . '/Savant3/Error.php';
  			}
  			// now compare the parentage
  			$is = $obj instanceof Savant3_Error;
  			$sub = is_subclass_of($obj, 'Savant3_Error');
  			return ($is || $sub);
  		}
  	}
  }
  