<?php␊ |
/**␊ |
*␊ |
* @author taylor.luk␊ |
* @todo tags need more test coverage␊ |
*/␊ |
␊ |
/**␊ |
* ifchanged tag␊ |
*␊ |
* Usage:␊ |
*␊ |
* Variable mode␊ |
* {% ifchanged data.date %}...{% endifchanged %}␊ |
*␊ |
* Lazy mode *not implemented in h2o yet␊ |
* {% ifchanged %}...{{ data.date }}...{% endifchanged %}␊ |
*␊ |
*/␊ |
class IfChanged_Tag extends H2o_Node {␊ |
private $nodelist_true;␊ |
private $nodelist_false;␊ |
private $_varlist = null;␊ |
private $_last_seen = null;␊ |
␊ |
function __construct($argstring, $parser, $position = 0) {␊ |
$this->nodelist_true = $parser->parse('endifchanged', 'else');␊ |
␊ |
if ($parser->token->content === 'else')␊ |
$this->nodelist_false = $parser->parse('endifchanged');␊ |
␊ |
$this->_varlist = current(H2o_Parser::parseArguments($argstring));␊ |
␊ |
if (!$this->_varlist)␊ |
throw new TemplateSyntaxError('H2o doesn\'t support lazy ifchanged yet. Please, supply a variable.');␊ |
␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
␊ |
if ($this->_varlist) {␊ |
$compare_to = $context->resolve($this->_varlist);␊ |
} else {␊ |
/**␊ |
* @todo Rendering method $this->nodelist_true->render() should return a result.␊ |
* Further more $compare_to variable should be set to this result.␊ |
*/␊ |
$compare_to = '';␊ |
}␊ |
␊ |
if ($compare_to != $this->_last_seen) {␊ |
$this->_last_seen = $compare_to;␊ |
$this->nodelist_true->render($context, $stream);␊ |
} elseif ($this->nodelist_false) {␊ |
$this->nodelist_false->render($context, $stream);␊ |
}␊ |
␊ |
}␊ |
}␊ |
␊ |
class If_Tag extends H2o_Node {␊ |
private $body;␊ |
private $else;␊ |
private $negate;␊ |
␊ |
function __construct($argstring, $parser, $position = 0) {␊ |
if (preg_match('/\s(and|or)\s/', $argstring))␊ |
throw new TemplateSyntaxError('H2o doesn\'t support multiple expressions');␊ |
␊ |
$this->body = $parser->parse('endif', 'else');␊ |
␊ |
if ($parser->token->content === 'else')␊ |
$this->else = $parser->parse('endif');␊ |
␊ |
$this->args = H2o_Parser::parseArguments($argstring);␊ |
␊ |
$first = current($this->args);␊ |
if (isset($first['operator']) && $first['operator'] === 'not') {␊ |
array_shift($this->args);␊ |
$this->negate = true;␊ |
}␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
if ($this->test($context))␊ |
$this->body->render($context, $stream);␊ |
elseif ($this->else)␊ |
$this->else->render($context, $stream);␊ |
}␊ |
␊ |
function test($context) {␊ |
$test = Evaluator::exec($this->args, $context);␊ |
return $this->negate? !$test : $test;␊ |
}␊ |
}␊ |
␊ |
class For_Tag extends H2o_Node {␊ |
public $position;␊ |
private $iteratable, $key, $item, $body, $else, $limit, $reversed;␊ |
private $syntax = '{␊ |
([a-zA-Z][a-zA-Z0-9-_]*)(?:,\s?([a-zA-Z][a-zA-Z0-9-_]*))?␊ |
\s+in\s+␊ |
([a-zA-Z][a-zA-Z0-9-_]*(?:\.[a-zA-Z_0-9][a-zA-Z0-9_-]*)*)\s* # Iteratable name␊ |
(?:limit\s*:\s*(\d+))?\s*␊ |
(reversed)? # Reverse keyword␊ |
}x';␊ |
␊ |
function __construct($argstring, $parser, $position) {␊ |
if (!preg_match($this->syntax, $argstring, $match))␊ |
throw new TemplateSyntaxError("Invalid for loop syntax ");␊ |
␊ |
$this->body = $parser->parse('endfor', 'else');␊ |
␊ |
if ($parser->token->content === 'else')␊ |
$this->else = $parser->parse('endfor');␊ |
␊ |
$match = array_pad($match, 6, '');␊ |
list(,$this->key, $this->item, $this->iteratable, $this->limit, $this->reversed) = $match;␊ |
␊ |
if ($this->limit)␊ |
$this->limit = (int) $this->limit;␊ |
␊ |
# Swap value if no key found␊ |
if (!$this->item) {␊ |
list($this->key, $this->item) = array($this->item, $this->key);␊ |
}␊ |
$this->iteratable = symbol($this->iteratable);␊ |
$this->reversed = (bool) $this->reversed;␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
$iteratable = $context->resolve($this->iteratable);␊ |
␊ |
if ($this->reversed)␊ |
$iteratable = array_reverse($iteratable);␊ |
␊ |
if ($this->limit)␊ |
$iteratable = array_slice($iteratable, 0, $this->limit);␊ |
␊ |
$length = count($iteratable);␊ |
␊ |
if ($length) {␊ |
$parent = $context['loop'];␊ |
$context->push();␊ |
$rev_count = $is_even = $idx = 0;␊ |
foreach($iteratable as $key => $value) {␊ |
$is_even = $idx % 2;␊ |
$rev_count = $length - $idx;␊ |
␊ |
if ($this->key) {␊ |
$context[$this->key] = $key;␊ |
}␊ |
$context[$this->item] = $value;␊ |
$context['loop'] = array(␊ |
'parent' => $parent,␊ |
'first' => $idx === 0,␊ |
'last' => $rev_count === 1,␊ |
'odd' => !$is_even,␊ |
'even' => $is_even,␊ |
'length' => $length,␊ |
'counter' => $idx + 1,␊ |
'counter0' => $idx,␊ |
'revcounter' => $rev_count,␊ |
'revcounter0' => $rev_count - 1␊ |
);␊ |
$this->body->render($context, $stream);␊ |
++$idx;␊ |
}␊ |
$context->pop();␊ |
} elseif ($this->else)␊ |
$this->else->render($context, $stream);␊ |
}␊ |
}␊ |
␊ |
class Block_Tag extends H2o_Node {␊ |
public $name;␊ |
public $position;␊ |
public $stack;␊ |
private $syntax = '/^[a-zA-Z_][a-zA-Z0-9_-]*$/';␊ |
␊ |
function __construct($argstring, $parser, $position) {␊ |
if (!preg_match($this->syntax, $argstring))␊ |
throw new TemplateSyntaxError('Block tag expects a name, example: block [content]');␊ |
␊ |
$this->name = $argstring;␊ |
␊ |
if (isset($parser->storage['blocks'][$this->name]))␊ |
throw new TemplateSyntaxError('Block name exists, Please select a different block name');␊ |
␊ |
$this->filename = $parser->filename;␊ |
$this->stack = array($parser->parse('endblock', "endblock {$this->name}"));␊ |
␊ |
$parser->storage['blocks'][$this->name] = $this;␊ |
$this->position = $position;␊ |
}␊ |
␊ |
function addLayer(&$nodelist) {␊ |
$nodelist->parent = $this;␊ |
array_push($this->stack, $nodelist);␊ |
}␊ |
␊ |
function render($context, $stream, $index = 1) {␊ |
$key = count($this->stack) - $index;␊ |
␊ |
if (isset($this->stack[$key])) {␊ |
$context->push();␊ |
$context['block'] = new BlockContext($this, $context, $index);␊ |
$this->stack[$key]->render($context, $stream);␊ |
$context->pop();␊ |
}␊ |
}␊ |
}␊ |
␊ |
class Extends_Tag extends H2o_Node {␊ |
public $filename;␊ |
public $position;␊ |
public $nodelist;␊ |
private $syntax = '/^["\'](.*?)["\']$/';␊ |
␊ |
function __construct($argstring, $parser, $position = 0) {␊ |
if (!$parser->first)␊ |
throw new TemplateSyntaxError('extends must be first in file');␊ |
␊ |
if (!preg_match($this->syntax, $argstring))␊ |
throw new TemplatesyntaxError('filename must be quoted');␊ |
␊ |
$this->filename = stripcslashes(substr($argstring, 1, -1));␊ |
␊ |
# Parse the current template␊ |
$parser->parse();␊ |
␊ |
# Parse parent template␊ |
$this->nodelist = $parser->runtime->loadSubTemplate($this->filename, $parser->options);␊ |
$parser->storage['templates'] = array_merge(␊ |
$parser->storage['templates'], $this->nodelist->parser->storage['templates']␊ |
);␊ |
$parser->storage['templates'][] = $this->filename;␊ |
␊ |
if (!isset($this->nodelist->parser->storage['blocks']) || !isset($parser->storage['blocks']))␊ |
return ;␊ |
␊ |
# Blocks of parent template␊ |
$blocks =& $this->nodelist->parser->storage['blocks'];␊ |
␊ |
# Push child blocks on top of parent blocks␊ |
foreach($parser->storage['blocks'] as $name => &$block) {␊ |
if (isset($blocks[$name])) {␊ |
$blocks[$name]->addLayer($block);␊ |
}␊ |
}␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
$this->nodelist->render($context, $stream);␊ |
}␊ |
}␊ |
␊ |
/**␊ |
* include tag␊ |
*␊ |
* Usage:␊ |
*␊ |
* Simple inclusion␊ |
* {% include "./subtemplate.html" %}␊ |
*␊ |
*␊ |
* Inclusion with additional context variables passing:␊ |
* {% include "./subtemplate.html" with foo=bar spam="eggs" %}␊ |
*␊ |
* Note: Double quotes matter. In this example 'foo' template variable of subtemplate.html␊ |
* would be initialized with 'bar' variable contents (from main template context),␊ |
* while 'spam' template variable of subtemplate.html would be set to simple string ('eggs').␊ |
*␊ |
*/␊ |
class Include_Tag extends H2o_Node {␊ |
private $nodelist;␊ |
private $syntax = '/^["\'](.*?)["\'](\s+with\s+(.+))?$/';␊ |
private $_additional_context = array();␊ |
␊ |
function __construct($argstring, $parser, $position = 0) {␊ |
if (!preg_match($this->syntax, $argstring, $matches)) {␊ |
throw new TemplateSyntaxError();␊ |
}␊ |
␊ |
$matches_count = count($matches);␊ |
␊ |
if ($matches_count > 2) {␊ |
// "with" statement supplied.␊ |
$with_vars = explode(' ', $matches[3]);␊ |
foreach ($with_vars as $var_str) {␊ |
$eq_pos = strpos($var_str, '=');␊ |
$this->_additional_context[substr($var_str, 0, $eq_pos)] = substr($var_str, $eq_pos+1);␊ |
}␊ |
}␊ |
␊ |
$this->filename = stripcslashes($matches[1]);␊ |
$this->nodelist = $parser->runtime->loadSubTemplate($this->filename, $parser->options);␊ |
$parser->storage['templates'] = array_merge(␊ |
$this->nodelist->parser->storage['templates'], $parser->storage['templates']␊ |
);␊ |
$parser->storage['templates'][] = $this->filename;␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
foreach ($this->_additional_context as $key => $value) {␊ |
if (strpos($value, '"') === false) {␊ |
// Context variable supplied as value. Needs to be resolved.␊ |
$value = $context->getVariable($value);␊ |
} else {␊ |
$value = trim($value, '"');␊ |
}␊ |
$context[$key] = $value;␊ |
}␊ |
␊ |
$this->nodelist->render($context, $stream);␊ |
}␊ |
}␊ |
␊ |
class With_Tag extends H2o_Node {␊ |
public $position;␊ |
private $variable, $shortcut;␊ |
private $nodelist;␊ |
private $syntax = '/^([\w]+(:?\.[\w\d]+)*)\s+as\s+([\w]+(:?\.[\w\d]+)?)$/';␊ |
␊ |
function __construct($argstring, $parser, $position = 0) {␊ |
if (!preg_match($this->syntax, $argstring, $matches))␊ |
throw new TemplateSyntaxError('Invalid with tag syntax');␊ |
␊ |
# extract the long name and shortcut␊ |
list(,$this->variable, ,$this->shortcut) = $matches;␊ |
$this->nodelist = $parser->parse('endwith');␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
$variable = $context->getVariable($this->variable);␊ |
␊ |
$context->push(array($this->shortcut => $variable));␊ |
$this->nodelist->render($context, $stream);␊ |
$context->pop();␊ |
}␊ |
}␊ |
␊ |
class Cycle_Tag extends H2o_Node {␊ |
private $uid;␊ |
private $sequence;␊ |
␊ |
function __construct($argstring, $parser, $pos) {␊ |
$args = h2o_parser::parseArguments($argstring);␊ |
␊ |
if (count($args) < 2) {␊ |
throw new Exception('Cycle tag require more than two items');␊ |
}␊ |
$this->sequence = $args;␊ |
$this->uid = '__cycle__'.$pos;␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
if (!is_null($item = $context->getVariable($this->uid))) {␊ |
$item = ($item + 1) % count($this->sequence);␊ |
} else {␊ |
$item = 0;␊ |
}␊ |
$stream->write($context->resolve($this->sequence[$item]));␊ |
$context->set($this->uid, $item);␊ |
}␊ |
}␊ |
␊ |
class Load_Tag extends H2o_Node {␊ |
public $position;␊ |
private $searchpath = array(H2O_ROOT);␊ |
private $extension;␊ |
␊ |
function __construct($argstring, $parser, $pos = 0) {␊ |
$this->extension = stripcslashes(preg_replace("/^[\"'](.*)[\"']$/", "$1", $argstring));␊ |
␊ |
if ($parser->runtime->searchpath)␊ |
$this->appendPath($parser->runtime->searchpath);␊ |
␊ |
$parser->storage['included'][$this->extension] = $file = $this->load();␊ |
$this->position = $pos;␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
$this->load();␊ |
}␊ |
␊ |
function appendPath($path) {␊ |
$this->searchpath[] = $path;␊ |
}␊ |
␊ |
private function load() {␊ |
if (isset(h2o::$extensions[$this->extension])) {␊ |
return true;␊ |
}␊ |
foreach($this->searchpath as $path) {␊ |
$file = $path.'ext'.DS.$this->extension.'.php';␊ |
if (is_file($file)) {␊ |
h2o::load($this->extension, $file);␊ |
return $file;␊ |
}␊ |
}␊ |
throw new H2o_Error(␊ |
"Extension: {$this->extension} cannot be loaded, please confirm it exist in extension path"␊ |
);␊ |
}␊ |
}␊ |
␊ |
class Debug_Tag extends H2o_Node {␊ |
private $argument;␊ |
function __construct($argstring, $parser, $pos = 0) {␊ |
$this->argument = $argstring;␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
if ($this->argument) {␊ |
$object = $context->resolve(symbol($this->argument));␊ |
} else {␊ |
$object = $context->scopes[0];␊ |
}␊ |
$output = "<pre>" . htmlspecialchars( print_r($object, true) ) . "</pre>";␊ |
$stream->write($output);␊ |
}␊ |
}␊ |
␊ |
class Comment_Tag extends H2o_Node {␊ |
function __construct($argstring, $parser, $position) {␊ |
$parser->parse('endcomment');␊ |
}␊ |
␊ |
function render($context, $stream, $index = 1) {␊ |
}␊ |
}␊ |
␊ |
class Now_Tag extends H2o_Node {␊ |
function __construct($argstring, $parser, $pos=0) {␊ |
$this->format = $argstring;␊ |
if (!$this->format) {␊ |
$this->format = "D M j G:i:s T Y";␊ |
}␊ |
}␊ |
␊ |
function render($contxt, $stream) {␊ |
$time = date($this->format);␊ |
$stream->write($time);␊ |
}␊ |
}␊ |
␊ |
class Autoescape_Tag extends H2o_Node {␊ |
protected $enable;␊ |
␊ |
function __construct($argstring, $parser, $pos = 0) {␊ |
if ($argstring === 'on')␊ |
$this->enable = true;␊ |
elseif ($argstring === 'off')␊ |
$this->enable = false;␊ |
else throw new H2o_Error(␊ |
"Invalid syntax : autoescape on|off "␊ |
);␊ |
}␊ |
␊ |
function render($context, $stream) {␊ |
$context->autoescape = $this->enable;␊ |
}␊ |
}␊ |
␊ |
class Csrf_token_Tag extends H2o_Node {␊ |
function render($context, $stream) {␊ |
$token = "";␊ |
if (isset($_COOKIE["csrftoken"]))␊ |
$token = $_COOKIE["csrftoken"];␊ |
else {␊ |
global $SECRET_KEY;␊ |
if (defined('SECRET_KEY'))␊ |
$token = md5(mt_rand() . SECRET_KEY);␊ |
else␊ |
$token = md5(mt_rand());␊ |
}␊ |
setcookie("csrftoken", $token, time()+60*60*24*365, "/");␊ |
$stream->write("<div style='display:none'><input type=\"hidden\" value=\"$token\" name=\"csrfmiddlewaretoken\" /></div>");␊ |
}␊ |
}␊ |
␊ |
H2o::addTag(array('block', 'extends', 'include', 'if', 'ifchanged', 'for', 'with', 'cycle', 'load', 'debug', 'comment', 'now', 'autoescape', 'csrf_token'));␊ |