<?php

/**
* Error constants.
*/

define('SAVANT2_ERROR_ASSIGN', -1);
define('SAVANT2_ERROR_ASSIGNREF', -2);
define('SAVANT2_ERROR_COMPILER', -3);
define('SAVANT2_ERROR_NOFILTER', -4);
define('SAVANT2_ERROR_NOPLUGIN', -5);
define('SAVANT2_ERROR_NOSCRIPT', -6);
define('SAVANT2_ERROR_NOTEMPLATE', -7);

/**
* Error messages.
*/

if (! isset($GLOBALS['_SAVANT2']['error'])) {
    $GLOBALS['_SAVANT2']['error'] = array(
        SAVANT2_ERROR_ASSIGN => 'assign() parameters not correct',
        SAVANT2_ERROR_ASSIGNREF => 'assignRef() parameters not correct',
        SAVANT2_ERROR_COMPILER => 'compiler not an object or has no compile() method',
        SAVANT2_ERROR_NOFILTER => 'filter file not found',
        SAVANT2_ERROR_NOPLUGIN => 'plugin file not found',
        SAVANT2_ERROR_NOSCRIPT => 'compiled template script file not found',
        SAVANT2_ERROR_NOTEMPLATE => 'template source file not found',
        );
}

/**
* Provides an object-oriented template system.
*
* Savant2 helps you separate model logic from view logic using PHP as
* the template language. By default, Savant2 does not compile templates.
* However, you may pass an optional compiler object to compile template
* source to include-able PHP code.
*
* Please see the documentation at {@link http://phpsavant.com/}, and be
* sure to donate! :-)
*
* @author Paul M. Jones <pmjones@ciaweb.net>
* @package Savant2
* @version 2.3.2 stable
* @license http://www.gnu.org/copyleft/lesser.html LGPL
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*/

class Savant2 {
    /**
    * PHP5 ONLY: What method __call() will alias to.
    *
    * Generally 'plugin' or 'splugin' (as __call() is intended for those).
    *
    * @access private
    * @var string
    */

    var $_call = 'plugin';

    /**
    * The custom compiler (pre-processor) object, if any.
    *
    * @access private
    * @var object
    */

    var $_compiler = null;

    /**
    * The class type to use when instantiating error objects.
    *
    * @access private
    * @var string
    */

    var $_error = null;

    /**
    * Whether or not to extract assigned variables into fetch() scope.
    *
    * When true, all variables and references assigned to Savant2 are
    * extracted into the local scope of the template script at fetch()
    * time, and may be addressed as "$varname" instead of
    * "$this->varname".  The "$this->varname" notation will also work.
    *
    * When false, you //must// use "$this->varname" in your templates to
    * address a variable instead of "$varname".  This has three
    * benefits: speed (no time spent extracting variables), memory use
    * (saves RAM by not making new references to variables), and clarity
    * (any $this->varname is obviously an assigned var, and vars created
    * within the template are not prefixed with $this).
    *
    * @access private
    * @var bool
    */

    var $_extract = false;

    /**
    * The output of the template script.
    *
    * @access private
    * @var string
    */

    var $_output = null;

    /**
    * The set of search directories for resources (plugins/filters) and
    * templates.
    *
    * @access private
    * @var array
    */

    var $_path = array('resource' => array(),
        'template' => array()
        );

    /**
    * Array of resource (plugin/filter) object instances.
    *
    * @access private
    * @var array
    */

    var $_resource = array('plugin' => array(),
        'filter' => array()
        );

    /**
    * Whether or not to automatically self-reference in plugins and filters.
    *
    * @access private
    * @var bool
    */

    var $_reference = false;

    /**
    * The path to the compiled template script file.
    *
    * By default, the template source and template script are the same file.
    *
    * @access private
    * @var string
    */

    var $_script = null;

    /**
    * The name of the default template source file.
    *
    * @access private
    * @var string
    */

    var $_template = null;
    // -----------------------------------------------------------------

    // Constructor and general property setters

    // -----------------------------------------------------------------
    /**
    * Constructor.
    *
    * @access public
    * @param array $conf An associative array of configuration keys for
    * the Savant2 object.  Any, or none, of the keys may be set. The
    * keys are:
    *
    * 'template_path' => The default path string or array of directories
    * to search for templates.
    *
    * 'resource_path' => The default path string or array of directories
    * to search for plugin and filter resources.
    *
    * 'error' => The custom error class that Savant2 should use
    * when returning errors.
    *
    * 'extract' => Whether or not to extract variables into the local
    * scope when executing a template.
    *
    * 'template' => The default template source name to use.
    */

    function Savant2($conf = array())
    {
        // set the default template search dirs
        if (isset($conf['template_path'])) {
            // user-defined dirs
            $this->setPath('template', $conf['template_path']);
        } else {
            // default directory only
            $this->setPath('template', null);
        }
        // set the default filter search dirs
        if (isset($conf['resource_path'])) {
            // user-defined dirs
            $this->setPath('resource', $conf['resource_path']);
        } else {
            // default directory only
            $this->setPath('resource', null);
        }
        // set the error class
        if (isset($conf['error'])) {
            $this->setError($conf['error']);
        }
        // set the extraction flag
        if (isset($conf['extract'])) {
            $this->setExtract($conf['extract']);
        }
        // set the Savant reference flag
        if (isset($conf['reference'])) {
            $this->setReference($conf['reference']);
        }
        // set the default template
        if (isset($conf['template'])) {
            $this->setTemplate($conf['template']);
        }
    }

    /**
    * Sets a custom compiler/pre-processor for template sources.
    *
    * By default, Savant2 does not use a compiler; use this to set your
    * own custom compiler (pre-processor) for template sources.
    *
    * @access public
    * @param object $compiler The compiler object; it must have a
    * "compile()" method.  If null or false, the current compiler object
    * is removed from Savant2.
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_COMPILER code.
    */

    function setCompiler(&$compiler)
    {
        if (! $compiler) {
            $this->_compiler = null;
        } elseif (is_object($compiler) && method_exists($compiler, 'compile')) {
            $this->_compiler = &$compiler;
        } else {
            $this->_compiler = null;
            return $this->error(SAVANT2_ERROR_COMPILER);
        }
    }

    /**
    * Sets the method that __call() will alias to.
    *
    * @access public
    * @param string $method The Savant2 method for __call() to alias to,
    * generally 'plugin' or 'splugin'.
    * @return void
    */

    function setCall($method = 'plugin')
    {
        $this->_call = $method;
    }

    /**
    * Sets the custom error class for Savant2 errors.
    *
    * @access public
    * @param string $error The name of the custom error class name; if
    * null or false, resets the error class to 'Savant2_Error'.
    * @return void
    */

    function setError($error)
    {
        if (! $error) {
            $this->_error = null;
        } else {
            $this->_error = $error;
        }
    }

    /**
    * Turns extraction of variables on/off.
    *
    * @access public
    * @param bool $flag True to turn on extraction, false to turn off.
    * @return void
    */

    function setExtract($flag = true)
    {
        if ($flag) {
            $this->_extract = true;
        } else {
            $this->_extract = false;
        }
    }

    /**
    * Sets the automated Savant reference for plugins and filters.
    *
    * @access public
    * @param bool $flag Whether to reference Savant2 or not.
    * @return void
    */

    function setReference($flag = false)
    {
        $this->_reference = $flag;
    }

    /**
    * Sets the default template name.
    *
    * @access public
    * @param string $template The default template name.
    * @return void
    */

    function setTemplate($template)
    {
        $this->_template = $template;
    }
    // -----------------------------------------------------------------

    // Path management and file finding

    // -----------------------------------------------------------------
    /**
    * Sets an entire array of search paths.
    *
    * @access public
    * @param string $type The type of path to set, typcially 'template'
    * or 'resource'.
    * @param string $ |array $new The new set of search paths.  If null or
    * false, resets to the current directory only.
    * @return void
    */

    function setPath($type, $new)
    {
        // clear out the prior search dirs
        $this->_path[$type] = array();
        // convert from string to path
        if (is_string($new) && ! strpos('://', $new)) {
            // the search config is a string, and it's not a stream
            // identifier (the "://" piece), add it as a path
            // string.
            $new = explode(PATH_SEPARATOR, $new);
        } else {
            // force to array
            settype($new, 'array');
        }
        // always add the fallback directories as last resort
        switch (strtolower($type)) {
            case 'template':
                $this->addPath($type, '.');
                break;
            case 'resource':
                $this->addPath($type, dirname(__FILE__) . '/Savant2/');
                break;
        }
        // actually add the user-specified directories
        foreach ($new as $dir) {
            $this->addPath($type, $dir);
        }
    }

    /**
    * Adds a search directory for templates.
    *
    * @access public
    * @param string $dir The directory or stream to search.
    * @return void
    */

    function addPath($type, $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->_path[$type], $dir);
    }

    /**
    * Gets the array of search directories for template sources.
    *
    * @access public
    * @return array The array of search directories for template sources.
    */

    function getPath($type = null)
    {
        if (! $type) {
            return $this->_path;
        } else {
            return $this->_path[$type];
        }
    }

    /**
    * Searches a series of paths for a given file.
    *
    * @param array $type The type of paths to search (template, plugin,
    * or filter).
    * @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.
    */

    function findFile($type, $file)
    {
        // get the set of paths
        $set = $this->getPath($type);
        // start looping through them
        foreach ($set as $path) {
            $fullname = $path . $file;
            if (file_exists($fullname) && is_readable($fullname)) {
                return $fullname;
            }
        }
        // could not find the file in the set of paths
        return false;
    }
    // -----------------------------------------------------------------

    // Variable and reference assignment

    // -----------------------------------------------------------------
    /**
    * Sets variables for the template.
    *
    * 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 set variables that begin with an underscore;
    * these are either private properties for Savant2 or private variables
    * within the template script itself.
    *
    * <code>
    *
    * $Savant2 =& new Savant2();
    *
    * // assign directly
    * $Savant2->var1 = 'something';
    * $Savant2->var2 = 'else';
    *
    * // assign by name and value
    * $Savant2->assign('var1', 'something');
    * $Savant2->assign('var2', 'else');
    *
    * // assign by assoc-array
    * $ary = array('var1' => 'something', 'var2' => 'else');
    * $Savant2->assign($obj);
    *
    * // assign by object
    * $obj = new stdClass;
    * $obj->var1 = 'something';
    * $obj->var2 = 'else';
    * $Savant2->assign($obj);
    *
    * </code>
    *
    * Greg Beaver came up with the idea of assigning to public class
    * properties.
    *
    * @access public
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_ASSIGN code.
    */

    function assign()
    {
        // this method is overloaded.
        $arg = func_get_args();
        // must have at least one argument. no error, just do nothing.
        if (! isset($arg[0])) {
            return;
        }
        // assign by object
        if (is_object($arg[0])) {
            // assign public properties
            foreach (get_object_vars($arg[0]) as $key => $val) {
                if (substr($key, 0, 1) != '_') {
                    $this->$key = $val;
                }
            }
            return;
        }
        // assign by associative array
        if (is_array($arg[0])) {
            foreach ($arg[0] as $key => $val) {
                if (substr($key, 0, 1) != '_') {
                    $this->$key = $val;
                }
            }
            return;
        }
        // assign by string name and mixed value.
        //
        // we use array_key_exists() instead of isset() becuase isset()
        // fails if the value is set to null.
        if (is_string($arg[0]) &&
                substr($arg[0], 0, 1) != '_' &&
                array_key_exists(1, $arg)) {
            $this->$arg[0] = $arg[1];
        } else {
            return $this->error(SAVANT2_ERROR_ASSIGN, $arg);
        }
    }

    /**
    * Sets references for the template.
    *
    * // assign by name and value
    * $Savant2->assignRef('ref', $reference);
    *
    * // assign directly
    * $Savant2->ref =& $reference;
    *
    * Greg Beaver came up with the idea of assigning to public class
    * properties.
    *
    * @access public
    * @param string $key The name for the reference in the template.
    * @param mixed $ &$val The referenced variable.
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_ASSIGNREF code.
    */

    function assignRef($key, &$val)
    {
        if (is_string($key) && substr($key, 0, 1) != '_') {
            $this->$key = &$val;
        } else {
            return $this->error(
                SAVANT2_ERROR_ASSIGNREF,
                array('key' => $key, 'val' => $val)
                );
        }
    }

    /**
    * Unsets assigned variables and references.
    *
    * @access public
    * @param mixed $var If null, clears all variables; if a string, clears
    * the one variable named by the string; if a sequential array, clears
    * the variables names in that array.
    * @return void
    */

    function clear($var = null)
    {
        if (is_null($var)) {
            // clear all variables
            $var = array_keys(get_object_vars($this));
        } else {
            // clear specific variables
            settype($var, 'array');
        }
        // clear out the selected variables
        foreach ($var as $name) {
            if (substr($name, 0, 1) != '_' && isset($this->$name)) {
                unset($this->$name);
            }
        }
    }

    /**
    * Gets the current value of one, many, or all assigned variables.
    *
    * Never returns variables starting with an underscore; these are
    * reserved for internal Savant2 use.
    *
    * @access public
    * @param mixed $key If null, returns a copy of all variables and
    * their values; if an array, returns an only those variables named
    * in the array; if a string, returns only that variable.
    * @return mixed If multiple variables were reqested, returns an
    * associative array where the key is the variable name and the
    * value is the variable value; if one variable was requested,
    * returns the variable value only.
    */

    function getVars($key = null)
    {
        if (is_null($key)) {
            $key = array_keys(get_object_vars($this));
        }

        if (is_array($key)) {
            // return a series of vars
            $tmp = array();
            foreach ($key as $var) {
                if (substr($var, 0, 1) != '_' && isset($this->$var)) {
                    $tmp[$var] = $this->$var;
                }
            }
            return $tmp;
        } else {
            // return a single var
            if (substr($key, 0, 1) != '_' && isset($this->$key)) {
                return $this->$key;
            }
        }
    }
    // -----------------------------------------------------------------

    // Template processing

    // -----------------------------------------------------------------
    /**
    * Loads a template script for execution (does not execute the script).
    *
    * This will optionally compile the template source into a PHP script
    * if a compiler object has been passed into Savant2.
    *
    * Also good for including templates from the template paths within
    * another template, like so:
    *
    * include $this->loadTemplate('template.tpl.php');
    *
    * @access public
    * @param string $tpl The template source name to look for.
    * @param bool $setScript Default false; if true, sets the $this->_script
    * property to the resulting script path (or null on error).  Normally,
    * only $this->fetch() will need to set this to true.
    * @return string The full path to the compiled template script.
    * @throws object An error object with a SAVANT2_ERROR_NOTEMPLATE code.
    */

    function loadTemplate($tpl = null, $setScript = false)
    {
        // set to default template if none specified.
        if (is_null($tpl)) {
            $tpl = $this->_template;
        }
        // find the template source.
        $file = $this->findFile('template', $tpl);
        if (! $file) {
            return $this->error(
                SAVANT2_ERROR_NOTEMPLATE,
                array('template' => $tpl)
                );
        }
        // are we compiling source into a script?
        if (is_object($this->_compiler)) {
            // compile the template source and get the path to the
            // compiled script (will be returned instead of the
            // source path)
            $script = $this->_compiler->compile($file);
        } else {
            // no compiling requested, return the source path
            $script = $file;
        }
        // is there a script from the compiler?
        if (! $script || $this->isError($script)) {
            if ($setScript) {
                $this->_script = null;
            }

            return $this->error(
                SAVANT2_ERROR_NOSCRIPT,
                array('template' => $tpl)
                );
        } else {
            if ($setScript) {
                $this->_script = $script;
            }

            return $script;
        }
    }

    /**
    * This is an alias to loadTemplate() and is identical in every way.
    *
    * @access public
    * @param string $tpl The template source name to look for.
    * @param bool $setScript Default false; if true, sets the $this->_script
    * property to the resulting script path (or null on error).  Normally,
    * only $this->fetch() will need to set this to true.
    * @return string The full path to the compiled template script.
    * @throws object An error object with a SAVANT2_ERROR_NOTEMPLATE code.
    */

    function findTemplate($tpl = null, $setScript = false)
    {
        return $this->loadTemplate($tpl, $setScript);
    }

    /**
    * Executes a template script and returns the results as a string.
    *
    * @param string $_tpl The name of the template source file ...
    * automatically searches the template paths and compiles as needed.
    * @return string The output of the the template script.
    * @throws object An error object with a SAVANT2_ERROR_NOSCRIPT code.
    */

    function fetch($_tpl = null)
    {
        // clear prior output
        $this->_output = null;
        // load the template script
        $_result = $this->loadTemplate($_tpl, true);
        // is there a template script to be processed?
        if ($this->isError($_result)) {
            return $_result;
        }
        // unset so as not to introduce into template scope
        unset($_tpl);
        unset($_result);
        // never allow a 'this' property
        if (isset($this->this)) {
            unset($this->this);
        }
        // are we extracting variables into local scope?
        if ($this->_extract) {
            // extract references to this object's public properties.
            // this allows variables assigned by-reference to refer all
            // the way back to the model logic.  variables assigned
            // by-copy only refer back to the property.
            foreach (array_keys(get_object_vars($this)) as $_prop) {
                if (substr($_prop, 0, 1) != '_') {
                    // set a variable-variable to an object property
                    // reference
                    $$_prop = &$this->$_prop;
                }
            }
            // unset private loop vars
            unset($_prop);
        }
        // start capturing output into a buffer
        ob_start();
        // include the requested template filename in the local scope
        // (this will execute the view logic).
        include $this->_script;
        // done with the requested template; get the buffer and
        // clear it.
        $this->_output = ob_get_contents();
        ob_end_clean();
        // done!
        return $this->applyFilters();
    }

    /**
    * Execute and display a template script.
    *
    * @param string $tpl The name of the template file to parse;
    * automatically searches through the template paths.
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_NOSCRIPT code.
    * @see fetch
    */

    function display($tpl = null)
    {
        $result = $this->fetch($tpl);
        if ($this->isError($result)) {
            return $result;
        } else {
            echo $result;
        }
    }
    // -----------------------------------------------------------------

    // Plugins

    // -----------------------------------------------------------------
    /**
    * Loads a plugin class and instantiates it within Savant2.
    *
    * @access public
    * @param string $name The plugin name (not including Savant2_Plugin_
    * prefix).
    * @param array $conf An associative array of plugin configuration
    * options.
    * @param bool $savantRef Default false.  When true, sets the $Savant
    * property of the filter to a reference to this Savant object.
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
    */

    function loadPlugin($name, $conf = array(), $savantRef = null)
    {
        // if no $savantRef is provided, use the default.
        if (is_null($savantRef)) {
            $savantRef = $this->_reference;
        }
        // some basic information
        $class = "Savant2_Plugin_$name";
        $file = "$class.php";
        // is it loaded?
        if (! class_exists($class)) {
            $result = $this->findFile('resource', $file);
            if (! $result) {
                return $this->error(
                    SAVANT2_ERROR_NOPLUGIN,
                    array('plugin' => $name)
                    );
            } else {
                include_once $result;
            }
        }
        // is it instantiated?
        if (! isset($this->_resource['plugin'][$name]) || ! is_object($this->_resource['plugin'][$name]) || ! is_a($this->_resource['plugin'][$name], $class)) {
            // instantiate it
            $this->_resource['plugin'][$name] = &new $class($conf);
            // add a Savant reference if requested
            if ($savantRef) {
                $this->_resource['plugin'][$name]->Savant = &$this;
            }
        }
    }

    /**
    * Unloads one or more plugins from Savant2.
    *
    * @access public
    * @param string $ |array $name The plugin name (not including Savant2_Plugin_
    * prefix).  If null, unloads all plugins; if a string, unloads that one
    * plugin; if an array, unloads all plugins named as values in the array.
    * @return void
    */

    function unloadPlugin($name = null)
    {
        if (is_null($name)) {
            $this->_resource['plugin'] = array();
        } else {
            settype($name, 'array');
            foreach ($name as $key) {
                if (isset($this->_resource['plugin'][$key])) {
                    unset($this->_resource['plugin'][$key]);
                }
            }
        }
    }

    /**
    * Executes a plugin with arbitrary parameters and returns the
    * result.
    *
    * @access public
    * @param string $name The plugin name (not including Savant2_Plugin_
    * prefix).
    * @return mixed The plugin results.
    * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
    * @see loadPlugin
    */

    function splugin($name)
    {
        // attempt to load the plugin
        $result = $this->loadPlugin($name);
        if ($this->isError($result)) {
            return $result;
        }
        // call the plugin's "plugin()" method with arguments,
        // dropping the first argument (the plugin name)
        $args = func_get_args();
        array_shift($args);
        return call_user_func_array(
            array(&$this->_resource['plugin'][$name], 'plugin'), $args
            );
    }

    /**
    * Executes a plugin with arbitrary parameters and displays the
    * result.
    *
    * @access public
    * @param string $name The plugin name (not including Savant2_Plugin_
    * prefix).
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
    */

    function plugin($name)
    {
        $args = func_get_args();

        $result = call_user_func_array(
            array(&$this, 'splugin'),
            $args
            );

        if ($this->isError($result)) {
            return $result;
        } else {
            echo $result;
        }
    }

    /**
    * PHP5 ONLY: Magic method alias to plugin().
    *
    * E.g., instead of $this->plugin('form', ...) you would use
    * $this->form(...).  You can set this to use any other Savant2 method
    * by issuing, for example, setCall('splugin') to use splugin() ... which
    * is really the only other sensible choice.
    *
    * @access public
    * @param string $func The plugin name.
    * @param array $args Arguments passed to the plugin.
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_NOPLUGIN code.
    */

    function __call($func, $args)
    {
        // add the plugin name to the args
        array_unshift($args, $func);
        // call the plugin() method
        return call_user_func_array(
            array(&$this, $this->_call),
            $args
            );
    }
    // -----------------------------------------------------------------

    // Filters

    // -----------------------------------------------------------------
    /**
    * Loads a filter class and instantiates it within Savant2.
    *
    * @access public
    * @param string $name The filter name (not including Savant2_Filter_
    * prefix).
    * @param array $conf An associative array of filter configuration
    * options.
    * @param bool $savantRef Default false.  When true, sets the $Savant
    * property of the filter to a reference to this Savant object.
    * @return void
    * @throws object An error object with a SAVANT2_ERROR_NOFILTER code.
    */

    function loadFilter($name, $conf = array(), $savantRef = null)
    {
        // if no $savantRef is provided, use the default.
        if (is_null($savantRef)) {
            $savantRef = $this->_reference;
        }
        // some basic information
        $class = "Savant2_Filter_$name";
        $file = "$class.php";
        // is it loaded?
        if (! class_exists($class)) {
            $result = $this->findFile('resource', $file);
            if (! $result) {
                return $this->error(
                    SAVANT2_ERROR_NOFILTER,
                    array('filter' => $name)
                    );
            } else {
                include_once $result;
            }
        }
        // is it instantiated?
        if (! isset($this->_resource['filter'][$name]) || ! is_object($this->_resource['filter'][$name]) || ! is_a($this->_resource['filter'][$name], $class)) {
            // instantiate it
            $this->_resource['filter'][$name] = &new $class($conf);
            // add a Savant reference if requested
            if ($savantRef) {
                $this->_resource['filter'][$name]->Savant = &$this;
            }
        }
    }

    /**
    * Unloads one or more filters from Savant2.
    *
    * @access public
    * @param string $ |array $name The filter name (not including Savant2_Filter_
    * prefix).  If null, unloads all filters; if a string, unloads that one
    * filter; if an array, unloads all filters named as values in the array.
    * @return void
    */

    function unloadFilter($name = null)
    {
        if (is_null($name)) {
            $this->_resource['filter'] = array();
        } else {
            settype($name, 'array');
            foreach ($name as $key) {
                if (isset($this->_resource['filter'][$key])) {
                    unset($this->_resource['filter'][$key]);
                }
            }
        }
    }

    /**
    * Apply all loaded filters, in order, to text.
    *
    * @access public
    * @param string $text The text to which filters should be applied.
    * If null, sets the text to $this->_output.
    * @return string The text after being passed through all loded
    * filters.
    */

    function applyFilters($text = null)
    {
        // set to output text if no text specified
        if (is_null($text)) {
            $text = $this->_output;
        }
        // get the list of filter names...
        $filter = array_keys($this->_resource['filter']);
        // ... and apply them each in turn.
        foreach ($filter as $name) {
            $this->_resource['filter'][$name]->filter($text);
        }
        // done
        return $text;
    }
    // -----------------------------------------------------------------

    // Error handling

    // -----------------------------------------------------------------
    /**
    * Returns an error object.
    *
    * @access public
    * @param int $code A SAVANT2_ERROR_* constant.
    * @param array $info An array of error-specific information.
    * @return object An error object of the type specified by
    * $this->_error.
    */

    function &error($code, $info = array())
    {
        // the error config array
        $conf = array('code' => $code,
            'text' => 'Savant2: ',
            'info' => (array) $info
            );
        // set an error message from the globals
        if (isset($GLOBALS['_SAVANT2']['error'][$code])) {
            $conf['text'] .= $GLOBALS['_SAVANT2']['error'][$code];
        } else {
            $conf['text'] .= '???';
        }
        // set up the error class name
        if ($this->_error) {
            $class = 'Savant2_Error_' . $this->_error;
        } else {
            $class = 'Savant2_Error';
        }
        // set up the error class file name
        $file = $class . '.php';
        // is it loaded?
        if (! class_exists($class)) {
            // find the error class
            $result = $this->findFile('resource', $file);
            if (! $result) {
                // could not find the custom error class, revert to
                // Savant_Error base class.
                $class = 'Savant2_Error';
                $result = dirname(__FILE__) . '/resources/Error.php';
            }
            // include the error class
            include_once $result;
        }
        // instantiate and return the error class
        $err = &new $class($conf);
        return $err;
    }

    /**
    * Tests if an object is of the Savant2_Error class.
    *
    * @access public
    * @param object $ &$obj The object to be tested.
    * @return boolean True if $obj is an error object of the type
    * Savant2_Error, or is a subclass that Savant2_Error. False if not.
    */

    function isError(&$obj)
    {
        if (is_object($obj)) {
            if (is_a($obj, 'Savant2_Error') ||
                    is_subclass_of($obj, 'Savant2_Error')) {
                return true;
            }
        }

        return false;
    }
}

?>