Location: PHPKode > scripts > Phritz MVC framework > view/PhritzTpl.class.php
<?php
/**
 * PhritzTpl.class.php
 *
 * Base class for the front-end template object
 * Variables are assigned via the assign() or append() methods
 * Users can also assign RSS feeds with the assign/append_rss() methods
 * Prefilters, postfilters, resouce functions can all be added with the appropriate
 * methods
 *
 * LICENSE: This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * @package Phritz
 * @link http://phritz.fritz-works.com
 * @author Michael Collado <hide@address.com>
 * @copyright 2006
 * @version 0.0.1a
 **/
require_once PHRITZ_ROOT.'/kernel/PhritzView.class.php';
Phritz::import('phritz.XMLBuilder');
Phritz::import('phritz.XSL_Wrapper');

//Error code definitions here
define('XSL_NOT_INITED', "XSLT API has not been installed");
define('XML_NOT_INITED', "DOM API has not been installed");
define('CALLBACK_FUNC_ERROR', 'An error occurred loading a plugin function');
define('FUNC_NOT_EXIST', 'The specified function can not be called');
define('TPL_UNAVAILABLE', 'The requested template file could not be found');
define('PHP_VERSION_ERROR', 'PHP version 4.3 or higher is required to use PHRITZ_TPL. Please update your version of php');
define('XSL_COMPILE_FAILED', 'The XSL processor was unable to process the file ');
define('XSL_IMPORT_FAILED', 'The XSL processor was unable to load the template file<br />Please check the template file for correct synatx');

define('DIR_SEP', DIRECTORY_SEPARATOR);

class PhritzTpl extends PhritzObject implements PhritzViewInterface {
    private $xsl_wrapper;
    private $tree;
    private $params;
    private $changed;
    private $resources = array();
    private $resource_functions = array();
    private $output_xml = true;
    private $secure_dir = array(PHRITZ_ROOT,'prefilters','postfilters','templates');
    private $prefilter_dir = 'view/prefilters';
    private $postfilter_dir = 'view/postfilters';
    private $tpl_dir = 'templates';
    private $base_url;
    private $allow_caching = false;
    private $cache_dir = 'view/cache';
    private $cache_id = '';
    private $cache_time;
    private $cache_life = 3600;

    function __construct() {
        if (class_exists('XSLTProcessor')) {
            $this->xsl_wrapper =& new XSL_Wrapper();
        } else {
            throw new PhritzDependancyException('XSLT API is required to use this template system', 'XSL');
        }
        $this->setParam('SERVER', $_SERVER);
        $this->setParam('ENV', $_ENV);
        $this->registerDefault();
        $this->cache_time = time();
        $this->changed = false;
        return true;
    }

    /**
     * allow_xml_output()
     *
     * Use to determine whether the tpl object will output strict xml or processed via xslt
     *
     * @param Boolean $bool         true to output xml, false to output html
     * @return void
     */
    public function allow_xml_output($bool = true) {
        $this->output_xml = $bool;
    }

    /**
     * setThemePath()
     *
     * change the default template directory
     *
     * @param string $dir       name of the directory to use
     * @return void
     */
    public function setThemePath($dir) {
		if (is_dir(PHRITZ_ROOT.DIR_SEP.$dir)) {
		    $this->tpl_dir = $dir;
		} else {
		    return false;
		}
    }
    
    /**
     * getThemePath()
     *
     * Returns the current theme path
     */
    public function getThemePath() {
	  	return $this->tpl_dir;
	}

    /**
     * setCacheID()
     *
     * set the cache prefix for all cache files to be stored or called during this
     * session
     */
    public function setCacheID($id) {
        $this->cache_id = $id;
    }

    /**
     * assign()
     *
     * assign values to template variables. assignments can be multidimensional arrays or simple
     * key, value parameters. See the demo for examples.
     *
     * @param mixed $name       string name or associative array with tpl assignments
     * @param mixed $value      string value or associative array with tpl assignments
     */
     public function assign($name, $value=null) {
        if (is_array($name)) {                //$name is array, ignore $value
            foreach ($name AS $var=>$val) {
                $this->vars[$var] = $val;
            }
        } else {
            $this->vars[$name] = $value;
        }
        $this->changed = true;
    }

    /**
     * append()
     *
     * appends values to tpl variables that are already set.
     *
     * <code>
     * $forum = array('NAME'=>'MyForum', 'ID'=>3);
     * $append = array('FORUMS'=>array('FORUM'=>$forum));
     * $tpl->append($append);
     * </code>
     * <b>OR</b>
     * <code>
     * foreach ($forums AS $forum) {
     *      $array = array('NAME'=>$forum->Name, 'ID'=>$forum->ID);
     *      $tpl->append('FORUMS', array('FORUM'=>$array));
     *  }
     * </code>
     * will result in a dom tree that looks like:
     * <forums>
     *     <forum>
     *        <name>Forum's name</name>
     *        <id>Forum ID</id>
     *     </forum>
     * </forums>
     */
    public function append($name, $value = null, $merge = false) {
        if (is_array($name)) {              //name can be array of NAME=>VAR
            foreach ($name AS $key=>$var) {
                if (!@is_array($this->vars[$key])) {
                    settype($this->vars[$key], 'array');
                }
                if ($merge && is_array($var)) {
                    foreach ($var AS $n_key=>$n_var) {
                        $this->vars[$key][$n_key] = $n_var;
                    }
                } else {
                    $this->vars[$key][] = $var;
                }
            }
        } else {
            if (!isset($this->vars[$name])) {
                $this->vars[$name] = array();
            }
            if (!is_array($this->vars[$name])) {
                settype($this->vars[$name], 'array');
            }
            if ($merge && is_array($value)) {
                foreach ($value AS $var=>$val) {
                    $this->vars[$name][$var] = $val;
                }
            } else {
                $this->vars[$name][] = $value;
            }
        }
        $this->changed = true;
    }


    /**
     * assign_rss()
     *
     * Assign RSS data from a given feed to the template XML tree.
     * As long as Sockets are enabled you can pass any url to the method and
     * the feed will be opened, the xml declaration tag will be stripped and the
     * feed will be appended starting and ending with the <rss> tag
     * Exceptionally useful since RSS feeds are already in XML format, you
     * can parse the RSS feed in the same template files you use for anything else.
     *
     * @access public
     * @param String $name  The name of the template variable
     * @param String $page  The page where the RSS feed can be located
     * @return Boolean
     */
    public function assign_rss($name, $page) {
        $info = file_get_contents($page);
        if (!$info) {
            return false;
        } else {
            preg_match("|<rss .+</rss>|isx", $info, $feed);
            $this->assign($name, $feed[0]);
            return true;
        }
    }

    /**
     * append_rss()
     *
     * Same as assign_rss() except that it appends the RSS feed
     * to an already existing template variable.
     *
     * @access public
     * @param String $name  The name of the template variable
     * @param String $page  The page where the RSS feed can be located
     * @return Boolean
     */
    public function append_rss($name, $page) {
        if (is_array($page)) {
            $element = $page[0];
            $url = $page[1];
        } else {
            $element = 'feed';
            $url = $page;
        }
        $info = file_get_contents($url);
        if (!$info) {
            return false;
        } else {
        preg_match("|<rss .+</rss>|isx", $info, $feed);
        $this->append($name, array($element=>$feed[0]));
        return true;
        }
    }

    /**
     * clear_assign()
     *
     * deletes a portion of the dom tree.
     * deletes all of the child nodes of the $key element
     *
     * @param string $key       The name of the tpl var to be deleted.
     * @return void
     */
    public function clear_assign($key) {
        if (is_array($key)) {
            foreach ($key AS $var) {
                unset($this->vars[$var]);
            }
        } else {
            unset($this->vars[$key]);
        }
        $this->changed = true;
    }

    public function setParam($name, $value) {
        if (is_array($value)) {
            foreach ($value AS $k=>$val) {
                $varname = $name.'.'.$k;
                if (is_array($value[$k])) {
				  	$this->setParam($varname, $value[$k]);
				} else {
	                $this->xsl_wrapper->setParameter($varname, $val);
				}
            }
        } else {
            $this->xsl_wrapper->setParameter($name, $value);
        }
    }

    /**
     * register_php_function()
     *
     * Assigns a php function that can be used when parsing xslt stylesheets
     *
     * @param string $function      The name of the function to use
     * @return bool                 true on success, false on failure (php4 always returns falses)
     */
    public function register_php_function($function) {
        return $this->xsl_wrapper->registerFunction($function);
    }

    /**
     * unregister_php_function()
     *
     * Unregister a registered PHP function with the XSLT processor
     * Again, only available for PHP5
     *
     * @access public
     * @param String $func_name     The name of the function to be unregistered
     * @return void
     */
    public function unregister_php_function($func_name) {
        $this->xsl_wrapper->unregisterFunction($func_name);
    }


    /**
     * register_prefilter()
     *
     * Registers a prefilter to be used on the template files before they
     * are processed by the XSLT processor
     * Usage:
     * <code>
     * $tpl->register_prefilter('function_name', array([$param1, [$param2, [$param3...]]]));
     * </code>
     *  OR
     * <code>
     * $tpl->register_prefilter(Array($obj, 'method_name'), array([$param1, [$param2, [$param3]]]));
     * </code>
     *
     * @access public
     * @param Mixed $function   The name of the function or an array($object, "Function_name")
     * @param Array $params         Any parameters that should be passed to the function when called
     * @returns void
     */
    public function register_prefilter($function, $params = null) {
        $name = (is_array($function)) ? $function[1]: $function;
        $this->resource_functions['pre'][$name]['function'] = $function;
        $this->resource_functions['pre'][$name]['params'] = $params;
    }

    /**
     * unregister_prefilter()
     *
     * Unregisters a registered prefilter
     *
     * @access public
     * @param String $func_name     The name of the registered function
     * @return void
     */
    public function unregister_prefilter($func_name) {
        unset($this->resource_functions['pre'][$func_name]);
    }

    /**
     * register_postfilter()
     *
     * Register a filter function to be used after the XSLT stylesheet has been processed
     * Usage instructions are identical to register_prefilter()
     * @see PhritzTPL::register_prefilter()
     * @access public
     * @param Mixed $function       String name of function or array(Object $object, String "Function_name")
     * @param Array $params         Any parameters to be passed to the function when called
     * @return void
     */
    public function register_postfilter($function, $params=null) {
        $name = is_array($function) ? $function[1] : $function;
        $this->resource_functions['post'][$name]['function'] = $function;
        $this->resource_function['post'][$name]['params'] = $params;
    }

    /**
     * register_resource()
     *
     * Register a resource function to be used to retrieve templates that are not located in a file
     * (i.e. retrieve a template stored in a database)
     *
     * @access public
     * @param String $type          A name given to the resource type (i.e. db for database)
     * @param Mixed $function       Either the function name or Array(Object $obj, String $Method);
     * @param Array $params         Any parameters that should be passed to the function when called
     * @return void
     */
    public function register_resource($type, $function, $params = null) {
        $this->resources[] = $type;
        $this->resource_functions[$type]['function'] = $function;
        $this->resource_functions[$type]['params'] = $params;
    }

    /**
     * unregister_resource()
     *
     * Unregister a registered resource function
     *
     * @param String $name          The resource name given in register_resource()
     * @return void
     */
    public function unregister_resource($name) {
        if (($ind = array_search($name, $this->resources)) !== false) {
            unset($this->resources[$ind]);
        }
        foreach($this->resource_functions[$name] AS $i=>$function) {
            unset($this->resource_functions[$name][$i]);
        }
        unset($this->resource_functions[$name]);
    }

    /**
     * display()
     *
     * If a template filename is supplied, this will either display the
     * current XML string with an xml-stylesheet processing instruction
     * for the browser to process (if xml_output is enabled)
     * or the server-side XSLT processor will process the template 
     * file and display the output.
     *
     * If no template file is provided, the raw XML string will be
     * displayed. This can be useful for AJAX applications where a raw XML
     * can be passed to the javascript functions on the client-side
     * for processing.
     *
     * @access public
     * @param String The name of the template file to be processed
     * @param String Any cache id applied to the given template
     * @return void
     */
    public function display($tpl = null, $cache_id = null) {
        header('Pragma: no-cache');
        header('Cache-conrol: no-store');
        if ($this->output_xml) {
            header('Content-type: text/xml; charset=UTF-8');
            if (!empty($tpl)) {
                $stylesheet = PHRITZ_URL.'/'.$this->tpl_dir.'/'.$tpl;
            } else {
                $stylesheet = '';
            }
            if (!$this->changed) {
                echo $this->xsl_wrapper->display($this->tree, $stylesheet);
            } else {
                echo $this->xsl_wrapper->display(XMLBuilder::compile($this->vars), $stylesheet);
            }
        } else {
            $output = $this->fetch($tpl, $cache_id, true);
            echo $output;
        }
    }

    /**
     * fetch()
     *
     * This is the method used to transform the template XML string to a named XSLT resource
     * XSLT resources can be passed in one of three ways:
     * 1) The name of the XSL template file located in the specified template directory
     * 2) The name of a registered resource function (such as a database accessor) in the format
     *    RESOURCE_NAME:FILE_NAME
     * 3) As an XSL string with XSL_STRING: preceding the actual string (i.e. XSL_STRING:<?xml ...)
     *
     * @returns string      The transformed template
     */
    public function fetch($tpl_resource, $cache_id = null, $force_compile = false, $display = false) {
        if ($cache_id === null) {
            $cache_id = $this->cache_id;
        }
        //to fetch a tpl file from a registered resource, use RESOURCE_NAME:TPL_FILE_NAME
        if (preg_match("/(\w+):([\w\d\.\-_]+||<\?xml.+)/", $tpl_resource, $match)) {
          	$ext_resource = true;
            $cache_file = $cache_id.$match[2];
        } else {
          	$ext_resource = false;
            $cache_file = $cache_id.$tpl_resource;
        }
        //if caching on this template
        if ($force_compile === false && $this->allow_caching) {
            if ($file = $this->getCachedResource($cache_file)) {
                return $file;
            }
        }
        if ($ext_resource) {
            if ($match[1] == "STRING") {        //Enable users to pass an xsl string as resource argument
                $tpl_source = $match[2];
            } else {
			    $tpl_source = $this->getFromResoure($match[1], $match[2]);
            }
        } else {
            if (file_exists($tpl_resource)) {
    		    $tpl_source = $this->getFileContents($tpl_resource);
            } else {
                $tpl_source = $this->getFileContents(PHRITZ_ROOT.DIR_SEP.$this->tpl_dir.DIR_SEP.$tpl_resource);
            }
        }
        $tpl_source = $this->runPreFilters($tpl_source);

        if (!$this->changed) {
            $compiled = $this->xsl_wrapper->process($tpl_source, $this->tree, $tpl_resource);
        } else  {
            $compiled = $this->xsl_wrapper->process($tpl_source, XMLBuilder::compile($this->vars), $tpl_resource);
        }
        $compiled = $this->runPostFilters($compiled);

        if ($force_compile === false && $this->allow_caching) {
            $comp_file = PHRITZ_ROOT.DIR_SEP.$this->cache_dir.DIR_SEP.$cache_file;
            $handle = fopen($comp_file, 'w');
            if ($handle !== false) {
                fwrite($handle, $compiled);
                fclose($handle);
            }
        }
        return $compiled;
    }

    /**
     * debug()
     *
     * Returns the current XML string
     *
     * @access public
     * @return String
     */
    public function debug() {
      return XMLBuilder::compile($this->vars);
    }

    /**
     * __getFileContents()
     *
     * Opens a template file and reads the contents into a string
     * which will be read and processed by the XSLT processor
     *
     * @access private
     * @returns String
     */
    private function getFileContents($filename) {
        $source = '';
        
        if (file_exists($filename)) {
            $source = file_get_contents($filename);
        } else {
            throw new PhritzFileException("Could not load template file.", $filename);
        }
        return $source;
    }

    /**
     * __getFromResource()
     *
     * Retrieves the template string from a registered resource
     * which will be read and processed by the XSLT processor
     *
     * @access private
     * @returns String
     */
    private function getFromResource($resource, $file) {
        $f_name = $this->resource_functions[$resource]['function'];
        $params = (!empty($this->resource_functions[$resource]['params'])) ?
                                        $this->resource_functions[$resource]['params'] : null;
        if (!empty($params)) {
             if (!is_array($params)) {        //make sure $params is an array
                settype($params, 'array');
             }
            //The first two paramaters of any resource function are the Template object & the resource name
            array_unshift($params, $this, $file);
        } else {
            $params = array($this, $file);
        }
        if (!is_callable($f_name)) {
            $this->loadPlugin($f_name);
        }
        //callback functions should return false if they can not retrieve tpl info
        if (($test = call_user_func_array($f_name, $params)) !== false) {
            return $test;
        } else {
            throw new PhritzParseException("Cannot retrieve source template from resource", CALLBACK_ERROR);
        }
    }

    /**
     * __getCachedResource()
     *
     * Opens a cache file for a given template and returns
     * the previously processed string
     *
     * @access private
     * @returns String
     */
    private function getCachedResource($tpl_name) {
        $file_name = PHRITZ_ROOT.DIR_SEP.$this->cache_dir.DIR_SEP.$tpl_name;
        if (file_exists($file_name)) {
            $age = $this->cache_time - filemtime($file_name);
            if ($age < $this->cache_life) {
                return file_get_contents($file_name);
            } else {
                unlink($file_name);
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * __runPreFilters()
     *
     * Processes the XSLT template string using any registered prefilters
     *
     * @access private
     * @param String $text      The raw XSLT stylesheet text
     * @return String
     */
    private function runPreFilters($text) {
        if (isset($this->resource_functions['pre']) && !empty($this->resource_functions['pre'])) {
            foreach($this->resource_functions['pre'] AS $id=>$array) {
                $params = (!empty($array['params'])) ? $this->resource_functions['pre'][$id]['params'] : null;
                $name = $this->resource_functions['pre'][$id]['function'];
                if (!empty($params)) {
                    if (!is_array($params)) {
                        settype($params, 'array');
                    }    //first two parameters of pre filters should be template object and the tpl_source string.
                    array_unshift($params, $this, $text);
                } else {
                    $params = array($this, $text);
                }
                if (!is_callable($name)) {
                    try {
                        $this->loadPlugin($name);
                    } catch(PhritzFileException $e) {
                      	trigger_error($e->getMessage(), E_USER_NOTICE);
                        continue;
                    }
                }
                $filtered = call_user_func_array($name, $params);
                if ($filtered !== false) {        //prefilter functions return false on failure.
                    $text = $filtered;
                }
            }
        }
        return $text;
    }

    /**
     * __runPostFilters()
     *
     * Process the processed template file using any registered postfilters
     *
     * @access private
     * @param String $text      The processed XSLT stylesheet text
     * @return String
     */
    private function runPostFilters($text) {
        if (isset($this->resource_functions['post']) && !empty($this->resource_functions['post'])) {
            foreach($this->resource_functions['post'] AS $id=>$array) {
                $params = (!empty($array['params'])) ? $this->resource_functions['pre'][$id]['params'] : null;
                $name = $this->resource_functions['post'][$id]['function'];
                if (!empty($params)) {
                    if (!is_array($params)) {
                        settype($params, 'array');
                    }
                    //first two parameters of postfilters should be template object and the tpl_source string.
                    array_unshift($params, $this, $text);
                } else {
                    $params = array($this, $text);
                }
                if (!is_callable($name)) {
                    try {
                        $this->loadPlugin($name);
                    } catch(PhritzFileException $e) {
                      	trigger_error($e->getMessage(), E_USER_NOTICE);
                        continue;
                    }
                }
                $filtered = call_user_func_array($name, $params);
                if ($filtered !== false) {        //postfilter functions return false on failure.
                    $text = $filtered;
                }
            }
        }
        return $text;
    }

    /**
     * __loadPlugin()
     *
     * Searches the plugin directory and any registered secure directories for either a file named
     * xsl_plugin.$FUNCTION_NAME.php or, if a class/method pair were passed, it will search for a file
     * named $CLASS_NAME.class.php
     *
     * @access private
     * @param Mixed $filter     String filtername or Array(String Classname, String Method)
     * @return void
     */
    private function loadPlugin($filter) {
        if (is_array($filter)) {
            if (is_string($filter[0])) {
                foreach($this->secure_dir AS $dirname) {
                    if (file_exists(PHRITZ_ROOT.DIR_SEP.$dirname.DIR_SEP.$filter[0].'.class.php')) {
                        include_once(PHRITZ_ROOT.DIR_SEP.$dirname.DIR_SEP.$filter[0].'.class.php');
                        return;
                    }
                }
            }
            throw new PhritzFileException("Could not load callback file", $filter[0]);
        } else {
          	foreach ($this->secure_dir AS $dirname) {
          	  	$fname = PHRITZ_ROOT.DIR_SEP.$dirname.'xsl_plugin'.$filter.'.php';
	            if (file_exists($fname)) {
	                include_once($fname);
	                return;
	            }
			}
            throw new PhritzFileException("Could not load callback file", $filter);
        }
    }

    /**
     * __registerDefault()
     *
     * Traverses the default plugin directory and registers functions
     *
     * @access private
     * @return void
     */
    private function registerDefault() {
        $dir = opendir($this->prefilter_dir);
        while (($file = readdir($dir)) !== false) {
            if (preg_match("/xsl_plugin.([\w\d_\-]+).php/i", $file, $filter)) {
                require_once(PHRITZ_ROOT.DIR_SEP.$this->prefilter_dir.DIR_SEP.$file);
                $this->register_prefilter($filter[1]);
            }
        }
        $dir = opendir(PHRITZ_ROOT.DIR_SEP.$this->postfilter_dir);
        while (($file = readdir(PHRITZ_ROOT.DIR_SEP.$dir)) !== false) {
            if (preg_match("/xsl_plugin.([\w\d_\-]+).php/", $file, $filter)) {
                include_once PHRITZ_ROOT.DIR_SEP.$this->postfilter_dir.DIR_SEP.$file;
                $this->register_postfilter($filter[1]);
            }
        }
    }
}
?>
Return current item: Phritz MVC framework