<?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}]";
}
}
?>