You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
595 lines
13 KiB
595 lines
13 KiB
<?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);
|
|
define('SAVANT2_ERROR_COMPILE_FAIL', -8);
|
|
|
|
|
|
/**
|
|
*
|
|
* 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',
|
|
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 <pmjones@ciaweb.net>.
|
|
* 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;
|
|
}
|
|
}
|
|
?>
|