'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', SAVANT2_ERROR_COMPILE_FAIL => 'template source failed to compile' ); } class Savant2_Error { var $code = null; var $info = array(); var $text = null; var $backtrace = null; function Savant2_Error($conf = array()) { // set public properties foreach ($conf as $key => $val) { $this->$key = $val; } // generate a backtrace if (function_exists('debug_backtrace')) { $this->backtrace = debug_backtrace(); } // extended behaviors $this->_genError(); } function _genError() { } } /** * * This is totally based on Savent by Paul M. Jones . * But is made much smaller... */ class SavantSmall { var $_error = null; var $_escape = array('htmlspecialchars'); var $_output = null; var $_path = array( 'resource' => array(), 'template' => array() ); var $_restrict = false; var $_template = null; // ----------------------------------------------------------------- // // Constructor and general property setters // // ----------------------------------------------------------------- function __construct($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 error class if (isset($conf['error'])) { $this->setError($conf['error']); } // set the restrict flag if (isset($conf['restrict'])) { $this->setRestrict($conf['restrict']); } // set the default template if (isset($conf['template'])) { $this->setTemplate($conf['template']); } // set the output escaping callbacks if (isset($config['escape'])) { call_user_func_array( array($this, 'setEscape'), (array) $config['escape'] ); } } function setError($error) { if (! $error) { $this->_error = null; } else { $this->_error = $error; } } function setRestrict($flag = false) { if ($flag) { $this->_restrict = true; } else { $this->_restrict = false; } } function setTemplate($template) { $this->_template = $template; } // ----------------------------------------------------------------- // // Output escaping and management. // // ----------------------------------------------------------------- function setEscape() { $this->_escape = func_get_args(); } function addEscape() { $args = func_get_args(); $this->_escape = array_merge($this->_escape, $args); } function getEscape() { return $this->_escape; } function escape($value) { // if value is not set return if (! isset ($value)) return $value; // were custom callbacks passed? if (func_num_args() == 1) { // no, only a value was passed. // loop through the predefined callbacks. foreach ($this->_escape as $func) { $value = call_user_func($func, $value); } } else { // yes, use the custom callbacks instead. $callbacks = func_get_args(); // drop $value array_shift($callbacks); // loop through custom callbacks. foreach ($callbacks as $func) { $value = call_user_func($func, $value); } } return $value; } function eprint($value) { $args = func_get_args(); echo call_user_func_array( array($this, 'escape'), $args ); } function _($value) { $args = func_get_args(); return call_user_func_array( array($this, 'eprint'), $args ); } // ----------------------------------------------------------------- // // Path management and file finding // // ----------------------------------------------------------------- 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); } } 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); } function getPath($type = null) { if (! $type) { return $this->_path; } else { return $this->_path[$type]; } } function findFile($type, $file) { // get the set of paths $set = $this->getPath($type); // start looping through them foreach ($set as $path) { // get the path to the file $fullname = $path . $file; // are we doing path checks? if (! $this->_restrict) { // no. this is faster but less secure. if (file_exists($fullname) && is_readable($fullname)) { return $fullname; } } else { // yes. this is slower, but attempts to restrict // access only to defined paths. // 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 // // ----------------------------------------------------------------- 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->_genError(SAVANT2_ERROR_ASSIGN, $arg); } } function assignRef($key, &$val) { if (is_string($key) && substr($key, 0, 1) != '_') { $this->$key =& $val; } else { return $this->_genError( SAVANT2_ERROR_ASSIGNREF, array('key' => $key, 'val' => $val) ); } } 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); } } } 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 // // ----------------------------------------------------------------- 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->_genError( SAVANT2_ERROR_NOTEMPLATE, array('template' => $tpl) ); } // no compiling requested, return the source path $this->_script = $file; return $file; } function findTemplate($tpl = null) { return $this->loadTemplate($tpl, false); } 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); } // 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->_output; } function display($tpl = null) { $result = $this->fetch($tpl); if ($this->isError($result)) { return $result; } else { echo $result; } } // ----------------------------------------------------------------- // // Error handling // // ----------------------------------------------------------------- function &_genError($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__) . '/Savant2/Error.php'; } // include the error class include_once $result; } // instantiate and return the error class $err = new $class($conf); return $err; } function isError(&$obj) { if (is_object($obj)) { if ($obj instanceof Savant2_Error || is_subclass_of($obj, 'Savant2_Error')) { return true; } } return false; } } ?>