267 lines
7.9 KiB
PHP
267 lines
7.9 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Context object
|
|
* encapsulate context, resolve name
|
|
*/
|
|
class H2o_Context implements ArrayAccess {
|
|
public $safeClass = array('stdClass', 'BlockContext');
|
|
public $scopes;
|
|
public $options;
|
|
public $autoescape = true;
|
|
|
|
private $arrayMethods = array('first'=> 0, 'last'=> 1, 'length'=> 2, 'size'=> 3);
|
|
static $lookupTable = array();
|
|
|
|
function __construct($context = array(), $options = array()){
|
|
if (is_object($context))
|
|
$context = get_object_vars($context);
|
|
$this->scopes = array($context);
|
|
|
|
if (isset($options['safeClass']))
|
|
$this->safeClass = array_merge($this->safeClass, $options['safeClass']);
|
|
|
|
if (isset($options['autoescape']))
|
|
$this->autoescape = $options['autoescape'];
|
|
|
|
$this->options = $options;
|
|
}
|
|
|
|
function push($layer = array()){
|
|
return array_unshift($this->scopes, $layer);
|
|
}
|
|
|
|
/**
|
|
* pop the most recent layer
|
|
*/
|
|
function pop() {
|
|
if (!isset($this->scopes[1]))
|
|
throw new Exception('cannnot pop from empty stack');
|
|
return array_shift($this->scopes);
|
|
}
|
|
|
|
function offsetExists($offset) {
|
|
foreach ($this->scopes as $layer) {
|
|
if (isset($layer[$offset])) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function offsetGet($key) {
|
|
foreach ($this->scopes as $layer) {
|
|
if (isset($layer[$key]))
|
|
return $layer[$key];
|
|
}
|
|
return;
|
|
}
|
|
|
|
function offsetSet($key, $value) {
|
|
if (strpos($key, '.') > -1)
|
|
throw new Exception('cannot set non local variable');
|
|
return $this->scopes[0][$key] = $value;
|
|
}
|
|
|
|
function offsetUnset($key) {
|
|
foreach ($this->scopes as $layer) {
|
|
if (isset($layer[$key])) unset($layer[$key]);
|
|
}
|
|
}
|
|
|
|
function extend($context) {
|
|
$this->scopes[0] = array_merge($this->scopes[0], $context);
|
|
}
|
|
|
|
function set($key, $value) {
|
|
return $this->offsetSet($key, $value);
|
|
}
|
|
|
|
function get($key) {
|
|
return $this->offsetGet($key);
|
|
}
|
|
|
|
function isDefined($key) {
|
|
return $this->offsetExists($key);
|
|
}
|
|
/**
|
|
*
|
|
*
|
|
*
|
|
* Variable name
|
|
*
|
|
* @param $var variable name or array(0 => variable name, 'filters' => filters array)
|
|
* @return unknown_type
|
|
*/
|
|
function resolve($var) {
|
|
|
|
# if $var is array - it contains filters to apply
|
|
$filters = array();
|
|
if ( is_array($var) ) {
|
|
|
|
$name = array_shift($var);
|
|
$filters = isset($var['filters'])? $var['filters'] : array();
|
|
|
|
}
|
|
else $name = $var;
|
|
|
|
$result = null;
|
|
|
|
# Lookup basic types, null, boolean, numeric and string
|
|
# Variable starts with : (:users.name) to short-circuit lookup
|
|
if ($name[0] === ':') {
|
|
$object = $this->getVariable(substr($name, 1));
|
|
if (!is_null($object)) $result = $object;
|
|
} else {
|
|
if ($name === 'true') {
|
|
$result = true;
|
|
}
|
|
elseif ($name === 'false') {
|
|
$result = false;
|
|
}
|
|
elseif (preg_match('/^-?\d+(\.\d+)?$/', $name, $matches)) {
|
|
$result = isset($matches[1])? floatval($name) : intval($name);
|
|
}
|
|
elseif (preg_match('/^"([^"\\\\]*(?:\\.[^"\\\\]*)*)"|' .
|
|
'\'([^\'\\\\]*(?:\\.[^\'\\\\]*)*)\'$/', $name)) {
|
|
$result = stripcslashes(substr($name, 1, -1));
|
|
}
|
|
}
|
|
if (!empty(self::$lookupTable) && $result == Null) {
|
|
$result = $this->externalLookup($name);
|
|
}
|
|
$result = $this->applyFilters($result,$filters);
|
|
return $result;
|
|
}
|
|
|
|
function getVariable($name) {
|
|
# Local variables. this gives as a bit of performance improvement
|
|
if (!strpos($name, '.'))
|
|
return $this[$name];
|
|
|
|
# Prepare for Big lookup
|
|
$parts = explode('.', $name);
|
|
$object = $this[array_shift($parts)];
|
|
|
|
# Lookup context
|
|
foreach ($parts as $part) {
|
|
if (is_array($object) or $object instanceof ArrayAccess) {
|
|
if (isset($object[$part])) {
|
|
$object = $object[$part];
|
|
} elseif ($part === 'first') {
|
|
$object = isset($object[0])?$object[0]:null;
|
|
} elseif ($part === 'last') {
|
|
$last = count($object)-1;
|
|
$object = isset($object[$last])?$object[$last]:null;
|
|
} elseif ($part === 'size' or $part === 'length') {
|
|
return count($object);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
elseif (is_object($object)) {
|
|
if (isset($object->$part))
|
|
$object = $object->$part;
|
|
elseif (is_callable(array($object, $part))) {
|
|
$methodAllowed = in_array(get_class($object), $this->safeClass) ||
|
|
(isset($object->h2o_safe) && (
|
|
$object->h2o_safe === true || in_array($part, $object->h2o_safe)
|
|
)
|
|
);
|
|
$object = $methodAllowed ? $object->$part() : null;
|
|
}
|
|
else return null;
|
|
}
|
|
else return null;
|
|
}
|
|
return $object;
|
|
}
|
|
|
|
function applyFilters($object, $filters) {
|
|
|
|
foreach ($filters as $filter) {
|
|
$name = substr(array_shift($filter), 1);
|
|
$args = $filter;
|
|
|
|
if (isset(h2o::$filters[$name])) {
|
|
foreach ($args as $i => $argument) {
|
|
# name args
|
|
if (is_array($argument)) {
|
|
foreach ($argument as $n => $arg) {
|
|
$args[$i][$n] = $this->resolve($arg);
|
|
}
|
|
} else {
|
|
# resolve argument values
|
|
$args[$i] = $this->resolve($argument);
|
|
}
|
|
}
|
|
array_unshift($args, $object);
|
|
$object = call_user_func_array(h2o::$filters[$name], $args);
|
|
}
|
|
}
|
|
return $object;
|
|
}
|
|
|
|
function escape($value, $var) {
|
|
|
|
$safe = false;
|
|
$filters = (is_array($var) && isset($var['filters']))? $var['filters'] : array();
|
|
|
|
foreach ( $filters as $filter ) {
|
|
|
|
$name = substr(array_shift($filter), 1);
|
|
$safe = !$safe && ($name === 'safe');
|
|
|
|
$escaped = $name === 'escape';
|
|
}
|
|
|
|
$should_escape = $this->autoescape || isset($escaped) && $escaped;
|
|
|
|
if ( ($should_escape && !$safe)) {
|
|
$value = htmlspecialchars($value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
function externalLookup($name) {
|
|
if (!empty(self::$lookupTable)) {
|
|
foreach (self::$lookupTable as $lookup) {
|
|
$tmp = call_user_func_array($lookup, array($name, $this));
|
|
if ($tmp !== null)
|
|
return $tmp;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class BlockContext {
|
|
var $h2o_safe = array('name', 'depth', 'super');
|
|
var $block, $index;
|
|
private $context;
|
|
|
|
function __construct($block, $context, $index) {
|
|
$this->block =& $block;
|
|
$this->context = $context;
|
|
$this->index = $index;
|
|
}
|
|
|
|
function name() {
|
|
return $this->block->name;
|
|
}
|
|
|
|
function depth() {
|
|
return $this->index;
|
|
}
|
|
|
|
function super() {
|
|
$stream = new StreamWriter;
|
|
$this->block->parent->render($this->context, $stream, $this->index+1);
|
|
return $stream->close();
|
|
}
|
|
|
|
function __toString() {
|
|
return "[BlockContext : {$this->block->name}, {$this->block->filename}]";
|
|
}
|
|
}
|
|
?>
|