* @copyright Copyright (c) 2008, Jordi Boggiano
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @link http://dwoo.org/
* @version 0.3.4
* @date 2008-04-09
* @package Dwoo
*/
class Dwoo
{
/**
* current version number
*
* @var string
*/
const VERSION = "0.3.4";
/**
* unique number of this dwoo release
*
* this can be used by templates classes to check whether the compiled template
* has been compiled before this release or not, so that old templates are
* recompiled automatically when Dwoo is updated
*/
const RELEASE_TAG = 8;
/**#@+
* constants that represents all plugin types
*
* these are bitwise-operation-safe values to allow multiple types
* on a single plugin
*
* @var int
*/
const CLASS_PLUGIN = 1;
const FUNC_PLUGIN = 2;
const NATIVE_PLUGIN = 4;
const BLOCK_PLUGIN = 8;
const COMPILABLE_PLUGIN = 16;
const CUSTOM_PLUGIN = 32;
const SMARTY_MODIFIER = 64;
const SMARTY_BLOCK = 128;
const SMARTY_FUNCTION = 256;
/**#@-*/
/**
* character set of the template, used by string manipulation plugins
*
* it must be lowercase, but setCharset() will take care of that
*
* @see setCharset
* @see getCharset
* @var string
*/
protected $charset = 'utf-8';
/**
* global variables that are accessible through $dwoo.* in the templates
*
* default values include:
*
* $dwoo.version - current version number
* $dwoo.ad - a Powered by Dwoo link pointing to dwoo.org
* $dwoo.now - the current time
* $dwoo.template - the current template filename
* $dwoo.charset - the character set used by the template
*
* on top of that, foreach and other plugins can store special values in there,
* see their documentation for more details.
*
* @var array
*/
protected $globals;
/**
* directory where the compiled templates are stored
*
* defaults to DWOO_COMPILEDIR (= DWOO_DIRECTORY/compiled by default)
*
* @var string
*/
protected $compileDir;
/**
* directory where the cached templates are stored
*
* defaults to DWOO_CACHEDIR (= DWOO_DIRECTORY/cache by default)
*
* @var string
*/
protected $cacheDir;
/**
* defines how long (in seconds) the cached files must remain valid
*
* can be overriden on a per-template basis
*
* -1 = never delete
* 0 = disabled
* >0 = duration in seconds
*
* @var int
*/
protected $cacheTime = 0;
/**
* security policy object
*
* @var DwooSecurityPolicy
*/
protected $securityPolicy = null;
/**
* stores the custom plugins callbacks
*
* @see addPlugin
* @see removePlugin
* @var array
*/
protected $plugins = array();
/**
* stores the filter callbacks
*
* @see addFilter
* @see removeFilter
* @var array
*/
protected $filters = array();
/**
* stores the resource types and associated
* classes / compiler classes
*
* @var array
*/
protected $resources = array
(
'file' => array
(
'class' => 'DwooTemplateFile',
'compiler' => null
),
'string' => array
(
'class' => 'DwooTemplateString',
'compiler' => null
)
);
/**
* currently rendered template, set to null when not-rendering
*
* @var DwooITemplate
*/
protected $template = null;
/**
* stores the instances of the class plugins during template runtime
*
* @var array
*/
protected $runtimePlugins;
/**
* stores the data during template runtime
*
* @var array
*/
protected $data;
/**
* stores the current scope during template runtime
*
* @var mixed
*/
protected $scope;
/**
* stores the scope tree during template runtime
*
* @var array
*/
protected $scopeTree;
/**
* stores the block plugins stack during template runtime
*
* @var array
*/
protected $stack;
/**
* stores the current block plugin at the top of the stack during template runtime
*
* @var DwooBlockPlugin
*/
protected $curBlock;
/**
* constructor, sets the cache and compile dir to the default values
*/
public function __construct()
{
$this->cacheDir = DWOO_CACHE_DIRECTORY.DIRECTORY_SEPARATOR;
$this->compileDir = DWOO_COMPILE_DIRECTORY.DIRECTORY_SEPARATOR;
}
/**
* resets some runtime variables to allow a cloned object to be used to render sub-templates
*/
public function __clone()
{
$this->template = null;
unset($this->scope);
unset($this->data);
}
/**
* outputs the given template using the provided data and optional compiler
*
* @param mixed $tpl template, can either be a DwooITemplate object (i.e. DwooTemplateFile), a valid path to a template, or
* a template as a string it is recommended to provide a DwooITemplate as it will probably make things faster,
* especially if you render a template multiple times
* @param mixed $data the data to use, can either be a DwooIDataProvider object (i.e. DwooData) or an associative array. if you're
* rendering the template from cache, it can be left null
* @param DwooICompiler $compiler the compiler that must be used to compile the template, if left empty a default
* DwooCompiler will be used.
* @param bool $return flag that defines whether the function returns the output of the template (true) or echoes it directly (false)
* @return string the template output or nothing if $return is false
*/
public function output($tpl, $data = array(), DwooICompiler $compiler = null, $return = false)
{
// a render call came from within a template, so we need a new dwoo instance in order to avoid breaking this one
if($this->template instanceof DwooITemplate)
{
$proxy = clone $this;
return $proxy->output($tpl, $data, $compiler, $return);
}
// auto-create template if required
if($tpl instanceof DwooITemplate)
{}
elseif(is_string($tpl) && file_exists($tpl))
$tpl = new DwooTemplateFile($tpl);
elseif(is_string($tpl))
$tpl = new DwooTemplateString($tpl);
else
throw new DwooException('Dwoo->get/Dwoo->output\'s first argument must be a DwooITemplate (i.e. DwooTemplateFile) or a valid path to a template file', E_USER_NOTICE);
$this->template = $tpl;
// load data
if($data instanceof DwooIDataProvider)
$this->data = $data->getData();
elseif(is_array($data))
$this->data = $data;
else
throw new DwooException('Dwoo->get/Dwoo->output\'s data argument must be a DwooIDataProvider object (i.e. DwooData) or an associative array', E_USER_NOTICE);
$this->initGlobals($tpl);
$this->initRuntimeVars($tpl);
// try to get cached template
$file = $tpl->getCachedTemplate($this);
$doCache = $file === true;
$cacheLoaded = $doCache === false && is_string($file);
// cache is present, run it
if($cacheLoaded === true)
{
if($return === false)
include $file;
else
{
ob_start();
include $file;
$this->template = null;
return ob_get_clean();
}
}
// no cache present
else
{
$file = $tpl->getCompiledTemplate($this, $compiler);
// building cache
if($doCache)
{
$output = include $file;
foreach($this->filters as $filter)
{
if(is_array($filter) && $filter[0] instanceof DwooFilter)
$output = call_user_func($filter, $output);
else
$output = call_user_func($filter, $this, $output);
}
$this->template = null;
$tpl->cache($this, $output);
if($return === false)
echo $output;
else
return $output;
}
// no need to build cache
else
{
$output = include $file;
$this->template = null;
foreach($this->filters as $filter)
{
if(is_array($filter) && $filter[0] instanceof DwooFilter)
$output = call_user_func($filter, $output);
else
$output = call_user_func($filter, $this, $output);
}
if($return === false)
echo $output;
else
return $output;
}
}
}
/**
* returns the template instead of outputting it, this is basically a shortcut for output(*, *, *, true)
* @see output
*/
public function get($tpl, $data = array(), $compiler = null)
{
return $this->output($tpl, $data, $compiler, true);
}
/**
* re-initializes the globals array before each template run
*
* @param DwooITemplate $tpl the template that is going to be rendered
*/
protected function initGlobals(DwooITemplate $tpl)
{
$this->globals = array
(
'version' => self::VERSION,
'ad' => 'Powered by Dwoo',
'now' => $_SERVER['REQUEST_TIME'],
'template' => $tpl->getName(),
'charset' => $this->charset,
//'config' => $this->config,
);
}
/**
* re-initializes the runtime variables before each template run
*
* @param DwooITemplate $tpl the template that is going to be rendered
*/
protected function initRuntimeVars(DwooITemplate $tpl)
{
$this->runtimePlugins = array();
$this->scope =& $this->data;
$this->scopeTree = array();
$this->stack = array();
$this->curBlock = null;
}
/*
* --------- settings functions ---------
*/
/**
* adds a custom plugin that is not in one of the plugin directories
*
* @param string $name the plugin name to be used in the templates
* @param callback $callback the plugin callback, either a function name,
* a class name or an array containing an object
* or class name and a method name
*/
public function addPlugin($name, $callback)
{
if(is_array($callback))
{
if(is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'DwooBlockPlugin'))
$this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0]));
else
$this->plugins[$name] = array('type'=>self::CLASS_PLUGIN, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0]), 'function'=>$callback[1]);
}
elseif(class_exists($callback, false))
{
if(is_subclass_of($callback, 'DwooBlockPlugin'))
$this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN, 'callback'=>$callback, 'class'=>$callback);
else
$this->plugins[$name] = array('type'=>self::CLASS_PLUGIN, 'callback'=>$callback, 'class'=>$callback, 'function'=>'process');
}
elseif(function_exists($callback))
{
$this->plugins[$name] = array('type'=>self::FUNC_PLUGIN, 'callback'=>$callback);
}
else
{
throw new DwooException('Callback could not be processed correctly, please check that the function/class you used exists');
}
}
/**
* removes a custom plugin
*
* @param string $name the plugin name
*/
public function removePlugin($name)
{
if(isset($this->plugins[$name]))
unset($this->plugins[$name]);
}
/**
* adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this instance
*
* @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory
* @param bool $autoload if true, the first parameter must be a filter name from one of the plugin directories
*/
public function addFilter($callback, $autoload = false)
{
if($autoload)
{
$name = str_replace('DwooFilter_', '', $callback);
$class = 'DwooFilter_'.$name;
if(!class_exists($class, false) && !function_exists($class))
DwooLoader::loadPlugin($name);
if(class_exists($class, false))
$callback = array(new $class($this), 'process');
elseif(function_exists($class))
$callback = $class;
else
$this->triggerError('Wrong filter name, when using autoload the filter must be in one of your plugin dir as "name.php" containg a class or function named "DwooFilter_name"', E_USER_ERROR);
$this->filters[] = $callback;
}
else
{
$this->filters[] = $callback;
}
}
/**
* removes a filter
*
* @param mixed $callback callback or filter name if it was autoloaded
*/
public function removeFilter($callback)
{
if(($index = array_search($callback, $this->filters, true)) !== false)
unset($this->filters[$index]);
elseif(($index = array_search('DwooFilter_'.str_replace('DwooFilter_', '', $callback), $this->filters, true)) !== false)
unset($this->filters[$index]);
else
{
$class = 'DwooFilter_' . str_replace('DwooFilter_', '', $callback);
foreach($this->filters as $index=>$filter)
{
if(is_array($filter) && $filter[0] instanceof $class)
{
unset($this->filters[$index]);
break;
}
}
}
}
/**
* adds a resource or overrides a default one
*
* @param string $name the resource name
* @param string $class the resource class (which must implement DwooITemplate)
* @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance used to compile this resource, if none is provided. by default it will produce a DwooCompiler object
*/
public function addResource($name, $class, $compilerFactory = null)
{
if(strlen($name) < 2)
throw new DwooException('Resource names must be at least two-character long to avoid conflicts with Windows paths');
$interfaces = class_implements($class, false);
if(in_array('DwooITemplate', $interfaces) === false)
throw new DwooException('Resource class must implement DwooITemplate');
$this->resources[$name] = array('class'=>$class, 'compiler'=>$compilerFactory);
}
/**
* removes a custom resource
*
* @param string $name the resource name
*/
public function removeResource($name)
{
unset($this->resources[$name]);
if($name==='file')
$this->resources['file'] = array('class'=>'DwooTemplateFile', 'compiler'=>null);
}
/* public function addConfig(array $config)
{
// TODO build
}
public function loadConfig($name, $section = null, $scope = 'global')
{
// TODO check
$config = parse_ini_file($this->configDir . $name .'.conf', true);
switch($scope)
{
case 'global':
$this->config = array_merge($config, $this->config);
if($this->template !== null)
$this->globals['config'] = array_merge($config, $this->globals['config']);
break;
case 'local':
$this->config = array_merge($config, $this->config);
$this->globals['config'] = array_merge($config, $this->globals['config']);
break;
case 'parent':
$this->config = array_merge($config, $this->config);
if($this->parent !== null)
$this->parent->addConfig($config);
break;
default:
$this->triggerError('scope must be "global", "parent" or "local", '.$scope.' provided.', E_USER_ERROR);
}
}*/
/*
* --------- getters and setters ---------
*/
/**
* returns the custom plugins loaded
*
* used by the DwooITemplate classes to pass the custom plugins to their DwooICompiler instance
*
* @return array
*/
public function getCustomPlugins()
{
return $this->plugins;
}
/**
* returns the cache directory with a trailing DIRECTORY_SEPARATOR
*
* @return string
*/
public function getCacheDir()
{
return $this->cacheDir;
}
/**
* sets the cache directory and automatically appends a DIRECTORY_SEPARATOR
*
* @param string $dir the cache directory
*/
public function setCacheDir($dir)
{
$this->cacheDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR;
}
/**
* returns the compile directory with a trailing DIRECTORY_SEPARATOR
*
* @return string
*/
public function getCompileDir()
{
return $this->compileDir;
}
/**
* sets the compile directory and automatically appends a DIRECTORY_SEPARATOR
*
* @param string $dir the compile directory
*/
public function setCompileDir($dir)
{
$this->compileDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR;
}
/**
* returns the default cache time that is used with templates that do not have a cache time set
*
* @return int the duration in seconds
*/
public function getCacheTime()
{
return $this->cacheTime;
}
/**
* sets the default cache time to use with templates that do not have a cache time set
*
* @param int $seconds the duration in seconds
*/
public function setCacheTime($seconds)
{
$this->cacheTime = (int) $seconds;
}
/**
* returns the character set used by the string manipulation plugins
*
* @return string
*/
public function getCharset()
{
return $this->charset;
}
/**
* sets the character set used by the string manipulation plugins
*
* the charset will be automatically lowercased
*
* @param string $charset the character set
*/
public function setCharset($charset)
{
$this->charset = strtolower((string) $charset);
}
/**
* returns the current template being rendered, if applicable, or null
*
* @return DwooITemplate|null
*/
public function getCurrentTemplate()
{
return $this->template;
}
/**
* sets the default compiler factory function for the given resource name
*
* a compiler factory must return a DwooICompiler object pre-configured to fit your needs
*
* @param string $resourceName the resource name (i.e. file, string)
* @param callback $compilerFactory the compiler factory callback
*/
public function setDefaultCompilerFactory($resourceName, $compilerFactory)
{
$this->resources[$resourceName]['compiler'] = $compilerFactory;
}
/**
* returns the default compiler factory function for the given resource name
*
* @param string $resourceName the resource name
* @return callback the compiler factory callback
*/
public function getDefaultCompilerFactory($resourceName)
{
return $this->resources[$resourceName]['compiler'];
}
/**
* sets the security policy object to enforce some php security settings
*
* use this if untrusted persons can modify templates
*
* @param DwooSecurityPolicy $policy the security policy object
*/
public function setSecurityPolicy(DwooSecurityPolicy $policy = null)
{
$this->securityPolicy = $policy;
}
/**
* returns the current security policy object or null by default
*
* @return DwooSecurityPolicy|null the security policy object if any
*/
public function getSecurityPolicy()
{
return $this->securityPolicy;
}
/*
* --------- util functions ---------
*/
/**
* [util function] checks whether the given template is cached or not
*
* @param DwooITemplate $tpl the template object
* @return bool
*/
public function isCached(DwooITemplate $tpl)
{
return is_string($tpl->getCachedTemplate($this));
}
/**
* [util function] clears the cached templates if they are older than the given time
*
* @param int $olderThan minimum time (in seconds) required for a cached template to be cleared
* @return int the amount of templates cleared
*/
public function clearCache($olderThan=0)
{
$cacheDirs = new RecursiveDirectoryIterator($this->cacheDir);
$cache = new RecursiveIteratorIterator($cacheDirs);
$expired = time() - $olderThan;
$count = 0;
foreach($cache as $file)
{
if($cache->isDot() || $cache->isDir())
continue;
if($cache->getCTime() < $expired)
$count += unlink((string) $file) ? 1 : 0;
}
return $count;
}
/**
* [util function] fetches a template object of the given resource
*
* @param string $resourceName the resource name (i.e. file, string)
* @param string $resourceId the resource identifier (i.e. file path)
* @param int $cacheTime the cache time setting for this resource
* @param string $cacheId the unique cache identifier
* @param string $compileId the unique compiler identifier
* @return DwooITemplate
*/
public function getTemplate($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null)
{
if(isset($this->resources[$resourceName]))
return call_user_func(array($this->resources[$resourceName]['class'], 'templateFactory'), $this, $resourceId, $cacheTime, $cacheId, $compileId);
else
throw new DwooException('Unknown resource type : '.$resourceName);
}
/**
* [util function] checks if the input is an array or an iterator object, optionally it can also check if it's empty
*
* @param mixed $value the variable to check
* @param bool $checkIsEmpty if true, the function will also check if the array is empty
* @return bool true if it's an array (and not empty) or false if it's not an array (or if it's empty)
*/
public function isArray($value, $checkIsEmpty=false)
{
if(is_array($value) === true || ($value instanceof Iterator && $value instanceof ArrayAccess))
{
if($checkIsEmpty)
return !empty($value);
else
return true;
}
return false;
}
/**
* [util function] triggers a dwoo error
*
* @param string $message the error message
* @param int $level the error level, one of the PHP's E_* constants
*/
public function triggerError($message, $level=E_USER_NOTICE)
{
trigger_error('Dwoo error: '.$message, $level);
}
/*
* --------- runtime functions ---------
*/
/**
* [runtime function] adds a block to the block stack
*
* @param string $blockName the block name (without DwooPlugin_ prefix)
* @param array $args the arguments to be passed to the block's init() function
* @return DwooBlockPlugin the newly created block
*/
public function addStack($blockName, array $args=array())
{
if(isset($this->plugins[$blockName]))
$class = $this->plugins[$blockName]['class'];
else
$class = 'DwooPlugin_'.$blockName;
if($this->curBlock !== null)
{
$this->curBlock->buffer(ob_get_contents());
ob_clean();
}
$block = new $class($this);
$cnt = count($args);
if($cnt===0)
$block->init();
elseif($cnt===1)
$block->init($args[0]);
elseif($cnt===2)
$block->init($args[0], $args[1]);
elseif($cnt===3)
$block->init($args[0], $args[1], $args[2]);
elseif($cnt===4)
$block->init($args[0], $args[1], $args[2], $args[3]);
else
call_user_func_array(array($block,'init'), $args);
$this->stack[] = $this->curBlock = $block;
return $block;
}
/**
* [runtime function] removes the plugin at the top of the block stack
*
* calls the block buffer() function, followed by a call to end()
* and finally a call to process()
*/
public function delStack()
{
$args = func_get_args();
$this->curBlock->buffer(ob_get_contents());
ob_clean();
$cnt = count($args);
if($cnt===0)
$this->curBlock->end();
elseif($cnt===1)
$this->curBlock->end($args[0]);
elseif($cnt===2)
$this->curBlock->end($args[0], $args[1]);
elseif($cnt===3)
$this->curBlock->end($args[0], $args[1], $args[2]);
elseif($cnt===4)
$this->curBlock->end($args[0], $args[1], $args[2], $args[3]);
else
call_user_func_array(array($this->curBlock, 'end'), $args);
$tmp = array_pop($this->stack);
if(count($this->stack) > 0)
{
$this->curBlock = end($this->stack);
$this->curBlock->buffer($tmp->process());
}
else
{
echo $tmp->process();
}
}
/**
* [runtime function] returns the parent block of the given block
*
* @param DwooBlockPlugin $block
* @return DwooBlockPlugin or false if the given block isn't in the stack
*/
public function getParentBlock(DwooBlockPlugin $block)
{
$index = array_search($block, $this->stack, true);
if($index !== false && $index > 0)
{
return $this->stack[$index-1];
}
return false;
}
/**
* [runtime function] finds the closest block of the given type, starting at the top of the stack
*
* @param string $type the type of plugin you want to find
* @return DwooBlockPlugin or false if no plugin of such type is in the stack
*/
public function findBlock($type)
{
if(isset($this->plugins[$type]))
$type = $this->plugins[$type]['class'];
else
$type = 'DwooPlugin_'.str_replace('DwooPlugin_','',$type);
$keys = array_keys($this->stack);
while(($key = array_pop($keys)) !== false)
if($this->stack[$key] instanceof $type)
return $this->stack[$key];
return false;
}
/**
* [runtime function] returns a DwooPlugin of the given class
*
* this is so a single instance of every class plugin is created at each template run,
* allowing class plugins to have "per-template-run" static variables
*
* @param string $class the class name
* @return mixed an object of the given class
*/
protected function getObjectPlugin($class)
{
if(isset($this->runtimePlugins[$class]))
return $this->runtimePlugins[$class];
return $this->runtimePlugins[$class] = new $class($this);
}
/**
* [runtime function] calls the process() method of the given class-plugin name
*
* @param string $plugName the class plugin name (without DwooPlugin_ prefix)
* @param array $params an array of parameters to send to the process() method
* @return string the process() return value
*/
public function classCall($plugName, array $params = array())
{
$class = 'DwooPlugin_'.$plugName;
$plugin = $this->getObjectPlugin($class);
$cnt = count($params);
if($cnt===0)
return $plugin->process();
elseif($cnt===1)
return $plugin->process($params[0]);
elseif($cnt===2)
return $plugin->process($params[0], $params[1]);
elseif($cnt===3)
return $plugin->process($params[0], $params[1], $params[2]);
elseif($cnt===4)
return $plugin->process($params[0], $params[1], $params[2], $params[3]);
else
return call_user_func_array(array($plugin, 'process'), $params);
}
/**
* [runtime function] calls a php function
*
* @param string $callback the function to call
* @param array $params an array of parameters to send to the function
* @return mixed the return value of the called function
*/
public function arrayMap($callback, array $params)
{
if($params[0] === $this)
{
$addThis = true;
array_shift($params);
}
if((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess)))
{
if(empty($params[0]))
return $params[0];
// array map
$out = array();
$cnt = count($params);
if(isset($addThis))
{
array_unshift($params, $this);
$items = $params[1];
$keys = array_keys($items);
if(is_string($callback) === false)
while(($i = array_shift($keys)) !== null)
$out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params);
elseif($cnt===1)
while(($i = array_shift($keys)) !== null)
$out[] = $callback($this, $items[$i]);
elseif($cnt===2)
while(($i = array_shift($keys)) !== null)
$out[] = $callback($this, $items[$i], $params[2]);
elseif($cnt===3)
while(($i = array_shift($keys)) !== null)
$out[] = $callback($this, $items[$i], $params[2], $params[3]);
else
while(($i = array_shift($keys)) !== null)
$out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params);
}
else
{
$items = $params[0];
$keys = array_keys($items);
if(is_string($callback) === false)
while(($i = array_shift($keys)) !== null)
$out[] = call_user_func_array($callback, array($items[$i]) + $params);
elseif($cnt===1)
while(($i = array_shift($keys)) !== null)
$out[] = $callback($items[$i]);
elseif($cnt===2)
while(($i = array_shift($keys)) !== null)
$out[] = $callback($items[$i], $params[1]);
elseif($cnt===3)
while(($i = array_shift($keys)) !== null)
$out[] = $callback($items[$i], $params[1], $params[2]);
elseif($cnt===4)
while(($i = array_shift($keys)) !== null)
$out[] = $callback($items[$i], $params[1], $params[2], $params[3]);
else
while(($i = array_shift($keys)) !== null)
$out[] = call_user_func_array($callback, array($items[$i]) + $params);
}
return $out;
}
else
{
return $params[0];
}
}
/**
* [runtime function] reads a variable into the given data array
*
* @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
* @param mixed $data the data array or object to read from
* @return mixed
*/
public function readVarInto($varstr, $data)
{
if($data === null)
return null;
if(is_array($varstr) === false)
preg_match_all('#(\[|->|\.)?([a-z0-9_]+)\]?#i', $varstr, $m);
else
$m = $varstr;
unset($varstr);
while(list($k, $sep) = each($m[1]))
{
if($sep === '.' || $sep === '[' || $sep === '')
{
if((is_array($data) || ($data instanceof Iterator && $data instanceof ArrayAccess)) && isset($data[$m[2][$k]]))
$data = $data[$m[2][$k]];
else
return null;
}
else
{
if(is_object($data) && property_exists($data, $m[2][$k]))
$data = $data->$m[2][$k];
else
return null;
}
}
return $data;
}
/**
* [runtime function] reads a variable into the parent scope
*
* @param int $parentLevels the amount of parent levels to go from the current scope
* @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
* @return mixed
*/
public function readParentVar($parentLevels, $varstr = null)
{
$tree = $this->scopeTree;
$cur = $this->data;
while($parentLevels--!==0)
{
array_pop($tree);
}
while(($i = array_shift($tree)) !== null)
{
if(is_object($cur))
$cur = $cur->$i;
else
$cur = $cur[$i];
}
if($varstr!==null)
return $this->readVarInto($varstr, $cur);
else
return $cur;
}
/**
* [runtime function] reads a variable into the current scope
*
* @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
* @return mixed
*/
public function readVar($varstr)
{
if(is_array($varstr)===true)
{
$m = $varstr;
unset($varstr);
}
else
{
if(strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false)
{
if($varstr === 'dwoo')
{
return $this->globals;
}
elseif($varstr === '_root')
{
return $this->data;
$varstr = substr($varstr, 6);
}
elseif($varstr === '_parent')
{
$varstr = '.'.$varstr;
$tree = $this->scopeTree;
$cur = $this->data;
array_pop($tree);
while(($i = array_shift($tree)) !== null)
{
if(is_object($cur))
$cur = $cur->$i;
else
$cur = $cur[$i];
}
return $cur;
}
$cur = $this->scope;
if(isset($cur[$varstr]))
return $cur[$varstr];
else
return null;
}
preg_match_all('#(\[|->|\.)?([a-z0-9_]+)\]?#i', $varstr, $m);
}
$i = $m[2][0];
if($i === 'dwoo')
{
$cur = $this->globals;
array_shift($m[2]);
array_shift($m[1]);
switch($m[2][0])
{
case 'get':
$cur = $_GET;
break;
case 'post':
$cur = $_POST;
break;
case 'session':
$cur = $_SESSION;
break;
case 'cookies':
case 'cookie':
$cur = $_COOKIE;
break;
case 'server':
$cur = $_SERVER;
break;
case 'env':
$cur = $_ENV;
break;
case 'request':
$cur = $_REQUEST;
break;
case 'const':
array_shift($m[2]);
if(defined($m[2][0]))
return constant($m[2][0]);
else
return null;
}
if($cur !== $this->globals)
{
array_shift($m[2]);
array_shift($m[1]);
}
}
elseif($i === '_root')
{
$cur = $this->data;
array_shift($m[2]);
array_shift($m[1]);
}
elseif($i === '_parent')
{
$tree = $this->scopeTree;
$cur = $this->data;
while(true)
{
array_pop($tree);
array_shift($m[2]);
array_shift($m[1]);
if(current($m[2]) === '_parent')
continue;
while(($i = array_shift($tree)) !== null)
{
if(is_object($cur))
$cur = $cur->$i;
else
$cur = $cur[$i];
}
break;
}
}
else
$cur = $this->scope;
while(list($k, $sep) = each($m[1]))
{
if($sep === '.' || $sep === '[' || $sep === '')
{
if((is_array($cur) || ($cur instanceof Iterator && $cur instanceof ArrayAccess)) && isset($cur[$m[2][$k]]))
$cur = $cur[$m[2][$k]];
else
return null;
}
elseif($sep === '->')
{
if(is_object($cur) && property_exists($cur, $m[2][$k]))
$cur = $cur->$m[2][$k];
else
return null;
}
else
return null;
}
return $cur;
}
/**
* [runtime function] assign the value to the given variable
*
* @param mixed $value the value to assign
* @param string $scope the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
* @return bool true if assigned correctly or false if a problem occured while parsing the var string
*/
public function assignInScope($value, $scope)
{
$tree =& $this->scopeTree;
$data =& $this->data;
if(strstr($scope, '.') === false && strstr($scope, '->') === false)
{
$this->scope[$scope] = $value;
}
else
{
// TODO handle _root/_parent scopes ?
preg_match_all('#(\[|->|\.)?([a-z0-9_]+)\]?#i', $scope, $m);
$cur =& $this->scope;
$last = array(array_pop($m[1]), array_pop($m[2]));
while(list($k, $sep) = each($m[1]))
{
if($sep === '.' || $sep === '[' || $sep === '')
{
if(is_array($cur) === false)
$cur = array();
$cur =& $cur[$m[2][$k]];
}
elseif($sep === '->')
{
if(is_object($cur) === false)
$cur = new stdClass;
$cur =& $cur->$m[2][$k];
}
else
return false;
}
if($last[0] === '.' || $last[0] === '[' || $last[0] === '')
{
if(is_array($cur) === false)
$cur = array();
$cur[$last[1]] = $value;
}
elseif($last[0] === '->')
{
if(is_object($cur) === false)
$cur = new stdClass;
$cur->$last[1] = $value;
}
else
return false;
}
}
/**
* [runtime function] sets the scope to the given scope string
*
* @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
* @return array the current scope
*/
public function setScope($scope)
{
$old = $this->scopeTree;
if(empty($scope))
return $old;
if(is_array($scope)===false)
$scope = explode('.', $scope);
while($bit = array_shift($scope))
{
if($bit === '_parent')
{
array_pop($this->scopeTree);
reset($this->scopeTree);
$this->scope =& $this->data;
$cnt = count($this->scopeTree);
for($i=0;$i<$cnt;$i++)
$this->scope =& $this->scope[$this->scopeTree[$i]];
}
elseif($bit === '_root')
{
$this->scope =& $this->data;
$this->scopeTree = array();
}
elseif(isset($this->scope[$bit]))
{
$this->scope =& $this->scope[$bit];
$this->scopeTree[] = $bit;
}
else
{
unset($this->scope);
$this->scope = null;
}
}
return $old;
}
/**
* [runtime function] returns the entire data array
*
* @return array
*/
public function getData()
{
return $this->data;
}
/**
* [runtime function] returns a reference to the current scope
*
* @return &mixed
*/
public function &getScope()
{
return $this->scope;
}
/**
* [runtime function] forces an absolute scope
*
* @see setScope
* @param mixed $scope a scope as a string or array
* @return array the current scope tree
*/
public function forceScope($scope)
{
$prev = $this->setScope(array('_root'));
$this->setScope($scope);
return $prev;
}
}
/**
* handles plugin loading and caching of plugins names/paths relationships
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
*
* This file is released under the LGPL
* "GNU Lesser General Public License"
* More information can be found here:
* {@link http://www.gnu.org/copyleft/lesser.html}
*
* @author Jordi Boggiano
* @copyright Copyright (c) 2008, Jordi Boggiano
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @link http://dwoo.org/
* @version 0.3.4
* @date 2008-04-09
* @package Dwoo
*/
class DwooLoader
{
/**
* stores the plugin directories
*
* @see addDirectory
* @var array
*/
protected static $paths = array();
/**
* stores the plugins names/paths relationships
* don't edit this on your own, use addDirectory
*
* @see addDirectory
* @var array
* @access protected
*/
public static $classpath = array();
/**
* rebuilds class paths, scans the given directory recursively and saves all paths in the given file
*
* @param string $path the plugin path to scan
* @param string $cacheFile the file where to store the plugin paths cache, it will be overwritten
* @access protected
*/
public static function rebuildClassPathCache($path, $cacheFile)
{
if($cacheFile!==false)
{
$tmp = self::$classpath;
self::$classpath = array();
}
// iterates over all files/folders
foreach(glob($path.DIRECTORY_SEPARATOR.'*') as $f)
{
if(is_dir($f))
self::rebuildClassPathCache($f, false);
elseif(basename($f) !== 'classpath.cache')
self::$classpath[str_replace(array('function.','block.','modifier.','outputfilter.','filter.','prefilter.','postfilter.','pre.','post.','output.','shared.','helper.'), '', basename($f,'.php'))] = $f;
}
// save in file if it's the first call (not recursed)
if($cacheFile!==false)
{
if(!file_put_contents($cacheFile, ''))
throw new DwooException('Could not write into '.$cacheFile.', either because the folder is not there (create it) or because of the chmod configuration (please ensure this directory is writable by php)');
self::$classpath += $tmp;
}
}
/**
* loads a plugin file
*
* @param string $class the plugin name, without the DwooPlugin_ prefix
*/
public static function loadPlugin($class)
{
// a new class was added or the include failed so we rebuild the cache
if(!isset(self::$classpath[$class]) || !include self::$classpath[$class])
{
self::rebuildClassPathCache(DWOO_DIRECTORY . 'plugins', DWOO_COMPILE_DIRECTORY . DIRECTORY_SEPARATOR . 'classpath.cache.php');
foreach(self::$paths as $path)
self::rebuildClassPathCache($path[0], $path[1]);
if(isset(self::$classpath[$class]))
include self::$classpath[$class];
else
throw new DwooException('Plugin '.$class.' can not be found, maybe you forgot to bind it if it\'s a custom plugin ?', E_USER_NOTICE);
}
}
/**
* adds a plugin directory
*
* @param string $pluginDir the plugin path to scan
*/
public static function addDirectory($pluginDir)
{
$cacheFile = DWOO_COMPILE_DIRECTORY . DIRECTORY_SEPARATOR . 'classpath-'.substr(strtr($pluginDir, ':/\\.', '----'), strlen($pluginDir) > 80 ? -80 : 0).'.cache.php';
self::$paths[] = array($pluginDir, $cacheFile);
if(file_exists($cacheFile))
include $cacheFile;
else
DwooLoader::rebuildClassPathCache($pluginDir, $cacheFile);
}
}
/**
* main dwoo exception class
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
*
* This file is released under the LGPL
* "GNU Lesser General Public License"
* More information can be found here:
* {@link http://www.gnu.org/copyleft/lesser.html}
*
* @author Jordi Boggiano
* @copyright Copyright (c) 2008, Jordi Boggiano
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @link http://dwoo.org/
* @version 0.3.4
* @date 2008-04-09
* @package Dwoo
*/
class DwooException extends Exception
{
}
/**
* represents the security settings of a dwoo instance, it can be passed around to different dwoo instances
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
*
* This file is released under the LGPL
* "GNU Lesser General Public License"
* More information can be found here:
* {@link http://www.gnu.org/copyleft/lesser.html}
*
* @author Jordi Boggiano
* @copyright Copyright (c) 2008, Jordi Boggiano
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @link http://dwoo.org/
* @version 0.3.4
* @date 2008-04-09
* @package Dwoo
*/
class DwooSecurityPolicy
{
/**#@+
* php handling constants, defaults to PHP_REMOVE
*
* PHP_REMOVE : remove all (+ short tags if your short tags option is on) from the input template
* PHP_ALLOW : leave them as they are
* PHP_ENCODE : run htmlentities over them
*
* @var int
*/
const PHP_ENCODE = 1;
const PHP_REMOVE = 2;
const PHP_ALLOW = 3;
/**#@-*/
/**#@+
* constant handling constants, defaults to CONST_DISALLOW
*
* CONST_DISALLOW : throw an error if {$dwoo.const.*} is used in the template
* CONST_ALLOW : allow {$dwoo.const.*} calls
*/
const CONST_DISALLOW = false;
const CONST_ALLOW = true;
/**#@-*/
/**
* php functions that are allowed to be used within the template
*
* @var array
*/
protected $allowedPhpFunctions = array
(
'str_repeat', 'count', 'number_format', 'htmlentities', 'htmlspecialchars',
'long2ip', 'strlen', 'list', 'empty', 'count', 'sizeof', 'in_array', 'is_array',
);
/**
* paths that are safe to use with include or other file-access plugins
*
* @var array
*/
protected $allowedDirectories = array();
/**
* stores the php handling level
*
* defaults to DwooSecurityPolicy::PHP_REMOVE
*
* @var int
*/
protected $phpHandling = self::PHP_REMOVE;
/**
* stores the constant handling level
*
* defaults to DwooSecurityPolicy::CONST_DISALLOW
*
* @var bool
*/
protected $constHandling = self::CONST_DISALLOW;
/**
* adds a php function to the allowed list
*
* @param mixed $func function name or array of function names
*/
public function allowPhpFunction($func)
{
if(is_array($func))
foreach($func as $fname)
$this->allowedPhpFunctions[strtolower($fname)] = true;
else
$this->allowedPhpFunctions[strtolower($func)] = true;
}
/**
* removes a php function from the allowed list
*
* @param mixed $func function name or array of function names
*/
public function disallowPhpFunction($func)
{
if(is_array($func))
foreach($func as $fname)
unset($this->allowedPhpFunctions[strtolower($fname)]);
else
unset($this->allowedPhpFunctions[strtolower($func)]);
}
/**
* returns the list of php functions allowed to run, note that the function names
* are stored in the array keys and not values
*
* @return array
*/
public function getAllowedPhpFunctions()
{
return $this->allowedPhpFunctions;
}
/**
* adds a directory to the safelist for includes and other file-access plugins
*
* @param mixed $path a path name or an array of paths
*/
public function allowDirectory($path)
{
if(is_array($path))
foreach($path as $dir)
$this->allowedDirectories[realpath($dir)] = true;
else
$this->allowedDirectories[realpath($path)] = true;
}
/**
* removes a directory from the safelist
*
* @param mixed $path a path name or an array of paths
*/
public function disallowDirectory($path)
{
if(is_array($path))
foreach($path as $dir)
unset($this->allowedDirectories[realpath($dir)]);
else
unset($this->allowedDirectories[realpath($path)]);
}
/**
* returns the list of safe paths, note that the paths are stored in the array
* keys and not values
*
* @return array
*/
public function getAllowedDirectories()
{
return $this->allowedPHPFunc;
}
/**
* sets the php handling level, defaults to REMOVE
*
* @param int $level one of the DwooSecurityPolicy::PHP_* constants
*/
public function setPhpHandling($level = self::PHP_REMOVE)
{
$this->phpHandling = $level;
}
/**
* returns the php handling level
*
* @return int the current level, one of the DwooSecurityPolicy::PHP_* constants
*/
public function getPhpHandling()
{
return $this->phpHandling;
}
/**
* sets the constant handling level, defaults to CONST_DISALLOW
*
* @param bool $level one of the DwooSecurityPolicy::CONST_* constants
*/
public function setConstantHandling($level = self::CONST_DISALLOW)
{
$this->constHandling = $level;
}
/**
* returns the constant handling level
*
* @return bool the current level, one of the DwooSecurityPolicy::CONST_* constants
*/
public function getConstantHandling()
{
return $this->constHandling;
}
}
?>