Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Feature tweaks and ehancements

context.php:
    Allow accessing first, second or third elements from an array
    Access does not depend on array keys being [0,1,2,3...]
filters.php:
    Access second or third index from items
    Added json filter
    Limiting words could sometimes cut an html tag in half
tags.php:
    Allow start index in {% for %} tag
  • Loading branch information...
commit c8a879f73869d3b7cdd131729f042ad2e053f59f 1 parent 93eee9c
@IJMacD authored
Showing with 137 additions and 117 deletions.
  1. +44 −37 h2o/context.php
  2. +52 −42 h2o/filters.php
  3. +41 −38 h2o/tags.php
View
81 h2o/context.php
@@ -9,21 +9,21 @@ class H2o_Context implements ArrayAccess {
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']))
+
+ if (isset($options['safeClass']))
$this->safeClass = array_merge($this->safeClass, $options['safeClass']);
-
- if (isset($options['autoescape']))
+
+ if (isset($options['autoescape']))
$this->autoescape = $options['autoescape'];
-
+
$this->options = $options;
}
@@ -54,13 +54,13 @@ function offsetGet($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]);
@@ -83,11 +83,11 @@ function isDefined($key) {
return $this->offsetExists($key);
}
/**
- *
- *
- *
+ *
+ *
+ *
* Variable name
- *
+ *
* @param $var variable name or array(0 => variable name, 'filters' => filters array)
* @return unknown_type
*/
@@ -96,15 +96,15 @@ 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] === ':') {
@@ -116,12 +116,12 @@ function resolve($var) {
}
elseif ($name === 'false') {
$result = false;
- }
+ }
elseif (preg_match('/^-?\d+(\.\d+)?$/', $name, $matches)) {
$result = isset($matches[1])? floatval($name) : intval($name);
}
elseif (preg_match('/^"([^"\\\\]*(?:\\.[^"\\\\]*)*)"|' .
- '\'([^\'\\\\]*(?:\\.[^\'\\\\]*)*)\'$/', $name)) {
+ '\'([^\'\\\\]*(?:\\.[^\'\\\\]*)*)\'$/', $name)) {
$result = stripcslashes(substr($name, 1, -1));
}
}
@@ -131,7 +131,7 @@ function resolve($var) {
$result = $this->applyFilters($result,$filters);
return $result;
}
-
+
function getVariable($name) {
# Local variables. this gives as a bit of performance improvement
if (!strpos($name, '.'))
@@ -147,7 +147,14 @@ function getVariable($name) {
if (isset($object[$part])) {
$object = $object[$part];
} elseif ($part === 'first') {
- $object = isset($object[0])?$object[0]:null;
+ $o = array_slice($object, 0, 1);

While the rest of the commits is great, I don't think second and third attribute s a good idea, may be a array[2] like interface has better value

{{ items[2] }}

If you update the pull request without the (second, third) and I am more than happy to merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $object = count($o) ? $o[0] : null;
+ } elseif ($part === 'second') {
+ $o = array_slice($object, 1, 1);
+ $object = count($o) ? $o[0] : null;
+ } elseif ($part === 'third') {
+ $o = array_slice($object, 2, 1);
+ $object = count($o) ? $o[0] : null;
} elseif ($part === 'last') {
$last = count($object)-1;
$object = isset($object[$last])?$object[$last]:null;
@@ -161,7 +168,7 @@ function getVariable($name) {
if (isset($object->$part))
$object = $object->$part;
elseif (is_callable(array($object, $part))) {
- $methodAllowed = in_array(get_class($object), $this->safeClass) ||
+ $methodAllowed = in_array(get_class($object), $this->safeClass) ||
(isset($object->h2o_safe) && (
$object->h2o_safe === true || in_array($part, $object->h2o_safe)
)
@@ -176,12 +183,12 @@ function getVariable($name) {
}
function applyFilters($object, $filters) {
-
+
foreach ($filters as $filter) {
$name = substr(array_shift($filter), 1);
$args = $filter;
-
- if (isset(h2o::$filters[$name])) {
+
+ if (isset(h2o::$filters[$name])) {
foreach ($args as $i => $argument) {
# name args
if (is_array($argument)) {
@@ -201,26 +208,26 @@ function applyFilters($object, $filters) {
}
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)) {
@@ -238,7 +245,7 @@ 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;
@@ -256,9 +263,9 @@ function depth() {
function super() {
$stream = new StreamWriter;
$this->block->parent->render($this->context, $stream, $this->index+1);
- return $stream->close();
+ return $stream->close();
}
-
+
function __toString() {
return "[BlockContext : {$this->block->name}, {$this->block->filename}]";
}
View
94 h2o/filters.php
@@ -7,11 +7,17 @@ class CoreFilters extends FilterCollection {
static function first($value) {
return $value[0];
}
-
+ static function second($value) {
+ return isset($value[1]) ? $value[1] : null;
+ }
+ static function third($value) {
+ return isset($value[2]) ? $value[2] : null;
+ }
+
static function last($value) {
return $value[count($value) - 1];
}
-
+
static function join($value, $delimiter = ', ') {
return join($delimiter, $value);
}
@@ -19,7 +25,7 @@ static function join($value, $delimiter = ', ') {
static function length($value) {
return count($value);
}
-
+
static function urlencode($data) {
if (is_array($data)) {
$result;
@@ -32,13 +38,13 @@ static function urlencode($data) {
return urlencode($data);
}
}
-
+
static function hyphenize ($string) {
$rules = array('/[^\w\s-]+/'=>'','/\s+/'=>'-', '/-{2,}/'=>'-');
$string = preg_replace(array_keys($rules), $rules, trim($string));
return $string = trim(strtolower($string));
}
-
+
static function urlize( $string, $truncate = false ) {
$reg_exUrl = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/";
preg_match_all($reg_exUrl, $string, $matches);
@@ -61,7 +67,10 @@ static function urlize( $string, $truncate = false ) {
}
return $string;
}
-
+ static function json($object)
+ {
+ return json_encode($object);
+ }
static function set_default($object, $default) {
return !$object ? $default : $object;
}
@@ -73,24 +82,24 @@ static function humanize($string) {
$string = preg_replace('/\s+/', ' ', trim(preg_replace('/[^A-Za-z0-9()!,?$]+/', ' ', $string)));
return capfirst($string);
}
-
+
static function capitalize($string) {
return ucwords(strtolower($string)) ;
}
-
+
static function titlize($string) {
return self::capitalize($string);
}
-
+
static function capfirst($string) {
$string = strtolower($string);
return strtoupper($string{0}). substr($string, 1, strlen($string));
}
-
+
static function tighten_space($value) {
return preg_replace("/\s{2,}/", ' ', $value);
}
-
+
static function escape($value, $attribute = false) {
return htmlspecialchars($value, $attribute ? ENT_QUOTES : ENT_NOQUOTES);
}
@@ -100,26 +109,27 @@ static function escapejson($value) {
// This function encodes the entire data structure, and strings get quotes around them.
return json_encode($value);
}
-
+
static function force_escape($value, $attribute = false) {
return self::escape($value, $attribute);
}
-
+
static function e($value, $attribute = false) {
return self::escape($value, $attribute);
}
-
+
static function safe($value) {
return $value;
}
-
+
static function truncate ($string, $max = 50, $ends = '...') {
- return (strlen($string) > $max ? substr($string, 0, $max).$ends : $string);
+ return (strlen($string) > $max ? substr($string, 0, $max).$ends : $string);
}
-
+
static function limitwords($text, $limit = 50, $ends = '...') {
if (strlen($text) > $limit) {
- $words = str_word_count($text, 2);
+ // html tags can sometimes get cut up, so include '</>' as word characters
@speedmax
speedmax added a note

This is a good idea, I am not sure how well this would work though. so tag delimiter is now considered word?

@IJMacD Owner
IJMacD added a note

The problem I had was that if html is passed through this filter it can lead to unexpected results. Start and end tags can be split yielding invalid html:

{{  "<a>One Two Three Four</a>" | limitwords 5 }} -> <a>One Two Three Four</...

which can break following html.

The first option I considered was to strip html tags first, then should they be reapplied?

A simpler solution I found was this one, considering '<', '/' and '>' as word characters yields better results without too much recoding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $words = str_word_count($text, 2, '</>');
$pos = array_keys($words);
if (isset($pos[$limit])) {
@@ -136,7 +146,7 @@ static function filesize ($bytes, $round = 1) {
return '0 bytes';
elseif ($bytes === 1)
return '1 byte';
-
+
$units = array(
'bytes' => pow(2, 0), 'kB' => pow(2, 10),
'BM' => pow(2, 20), 'GB' => pow(2, 30),
@@ -157,14 +167,14 @@ static function filesize ($bytes, $round = 1) {
static function currency($amount, $currency = 'USD', $precision = 2, $negateWithParentheses = false) {
$definition = array(
- 'EUR' => array('','.',','), 'GBP' => '', 'JPY' => '',
+ 'EUR' => array('','.',','), 'GBP' => '£', 'JPY' => '¥',
'USD'=>'$', 'AU' => '$', 'CAN' => '$'
);
$negative = false;
$separator = ',';
$decimals = '.';
$currency = strtoupper($currency);
-
+
// Is negative
if (strpos('-', $amount) !== false) {
$negative = true;
@@ -184,7 +194,7 @@ static function currency($amount, $currency = 'USD', $precision = 2, $negateWith
if (round($amount, $precision) === $zero) {
$amount = $zero;
}
-
+
if (isset($definition[$currency])) {
$symbol = $definition[$currency];
if (is_array($symbol))
@@ -202,11 +212,11 @@ class HtmlFilters extends FilterCollection {
static function base_url($url, $options = array()) {
return $url;
}
-
+
static function asset_url($url, $options = array()) {
return self::base_url($url, $options);
}
-
+
static function image_tag($url, $options = array()) {
$attr = self::htmlAttribute(array('alt','width','height','border'), $options);
return sprintf('<img src="%s" %s/>', $url, $attr);
@@ -220,17 +230,17 @@ static function css_tag($url, $options = array()) {
static function script_tag($url, $options = array()) {
return sprintf('<script src="%s" type="text/javascript"></script>', $url);
}
-
+
static function links_to($text, $url, $options = array()) {
$attrs = self::htmlAttribute(array('ref'), $options);
$url = self::base_url($url, $options);
return sprintf('<a href="%s" %s>%s</a>', $url, $attrs, $text);
}
-
+
static function links_with ($url, $text, $options = array()) {
return self::links_to($text, $url, $options);
}
-
+
static function strip_tags($text) {
$text = preg_replace(array('/</', '/>/'), array(' <', '> '),$text);
return strip_tags($text);
@@ -241,11 +251,11 @@ static function linebreaks($value, $format = 'p') {
return HtmlFilters::nl2br($value);
return HtmlFilters::nl2pbr($value);
}
-
+
static function nl2br($value) {
return str_replace("\n", "<br />\n", $value);
}
-
+
static function nl2pbr($value) {
$result = array();
$parts = preg_split('/(\r?\n){2,}/m', $value);
@@ -257,7 +267,7 @@ static function nl2pbr($value) {
protected static function htmlAttribute($attrs = array(), $data = array()) {
$attrs = self::extract(array_merge(array('id', 'class', 'title', "style"), $attrs), $data);
-
+
$result = array();
foreach ($attrs as $name => $value) {
$result[] = "{$name}=\"{$value}\"";
@@ -277,24 +287,24 @@ protected static function extract($attrs = array(), $data=array()) {
class DatetimeFilters extends FilterCollection {
static function date($time, $format = 'jS F Y H:i') {
- if ($time instanceof DateTime)
+ if ($time instanceof DateTime)
$time = (int) $time->format('U');
- if (!is_numeric($time))
+ if (!is_numeric($time))
$time = strtotime($time);
-
+
return date($format, $time);
}
static function relative_time($timestamp, $format = 'g:iA') {
- if ($timestamp instanceof DateTime)
+ if ($timestamp instanceof DateTime)
$timestamp = (int) $timestamp->format('U');
$timestamp = is_numeric($timestamp) ? $timestamp: strtotime($timestamp);
-
+
$time = mktime(0, 0, 0);
$delta = time() - $timestamp;
$string = '';
-
+
if ($timestamp < $time - 86400) {
return date("F j, Y, g:i a", $timestamp);
}
@@ -309,7 +319,7 @@ static function relative_time($timestamp, $format = 'g:iA') {
else if ($delta >= 3600)
$string .= "1 hour ";
$delta %= 3600;
-
+
if ($delta > 60)
$string .= floor($delta / 60) . " minutes ";
else
@@ -318,13 +328,13 @@ static function relative_time($timestamp, $format = 'g:iA') {
}
static function relative_date($time) {
- if ($time instanceof DateTime)
+ if ($time instanceof DateTime)
$time = (int) $time->format('U');
$time = is_numeric($time) ? $time: strtotime($time);
$today = strtotime(date('M j, Y'));
$reldays = ($time - $today)/86400;
-
+
if ($reldays >= 0 && $reldays < 1)
return 'today';
else if ($reldays >= 1 && $reldays < 2)
@@ -346,13 +356,13 @@ static function relative_date($time) {
else
return date('l, F j, Y',$time ? $time : time());
}
-
+
static function relative_datetime($time) {
$date = self::relative_date($time);
-
+
if ($date === 'today')
return self::relative_time($time);
-
+
return $date;
}
}
View
79 h2o/tags.php
@@ -1,6 +1,6 @@
<?php
/**
- *
+ *
* @author taylor.luk
* @todo tags need more test coverage
*/
@@ -62,13 +62,13 @@ 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))
+ 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');
@@ -82,7 +82,7 @@ function __construct($argstring, $parser, $position = 0) {
}
function render($context, $stream) {
- if ($this->test($context))
+ if ($this->test($context))
$this->body->render($context, $stream);
elseif ($this->else)
$this->else->render($context, $stream);
@@ -96,11 +96,12 @@ function test($context) {
class For_Tag extends H2o_Node {
public $position;
- private $iteratable, $key, $item, $body, $else, $limit, $reversed;
+ private $iteratable, $key, $item, $body, $else, $start = 0, $limit, $reversed;
@speedmax
speedmax added a note

A start location on iteratable is great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
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
+ (?:start\s*:\s*(\d+))?\s*
(?:limit\s*:\s*(\d+))?\s*
(reversed)? # Reverse keyword
}x';
@@ -108,17 +109,17 @@ class For_Tag extends H2o_Node {
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;
+ $match = array_pad($match, 7, '');
+ list(,$this->key, $this->item, $this->iteratable, $this->start, $this->limit, $this->reversed) = $match;
+
+ $this->start = (int) $this->start;
+ $this->limit = (int) $this->limit;
# Swap value if no key found
if (!$this->item) {
@@ -140,11 +141,13 @@ function render($context, $stream) {
if ($this->reversed)
$iteratable = array_reverse($iteratable);
- if ($this->limit)
- $iteratable = array_slice($iteratable, 0, $this->limit);
+ if($this->limit)
+ $iteratable = array_slice($iteratable, $this->start, $this->limit);
+ else
+ $iteratable = array_slice($iteratable, $this->start);
$length = count($iteratable);
-
+
if ($length) {
$parent = $context['loop'];
$context->push();
@@ -152,14 +155,14 @@ function render($context, $stream) {
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,
+ 'first' => $idx === 0,
'last' => $rev_count === 1,
'odd' => !$is_even,
'even' => $is_even,
@@ -170,7 +173,7 @@ function render($context, $stream) {
'revcounter0' => $rev_count - 1
);
$this->body->render($context, $stream);
- ++$idx;
+ ++$idx;
}
$context->pop();
} elseif ($this->else)
@@ -183,7 +186,7 @@ class Block_Tag extends H2o_Node {
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]');
@@ -222,7 +225,7 @@ class Extends_Tag extends H2o_Node {
public $position;
public $nodelist;
private $syntax = '/^["\'](.*?)["\']$/';
-
+
function __construct($argstring, $parser, $position = 0) {
if (!$parser->first)
throw new TemplateSyntaxError('extends must be first in file');
@@ -241,7 +244,7 @@ function __construct($argstring, $parser, $position = 0) {
$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 ;
@@ -255,7 +258,7 @@ function __construct($argstring, $parser, $position = 0) {
}
}
}
-
+
function render($context, $stream) {
$this->nodelist->render($context, $stream);
}
@@ -327,19 +330,19 @@ class With_Tag extends H2o_Node {
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();
@@ -349,17 +352,17 @@ function render($context, $stream) {
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->sequence = $args;
$this->uid = '__cycle__'.$pos;
}
-
+
function render($context, $stream) {
if (!is_null($item = $context->getVariable($this->uid))) {
$item = ($item + 1) % count($this->sequence);
@@ -378,10 +381,10 @@ class Load_Tag extends H2o_Node {
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;
}
@@ -393,7 +396,7 @@ function render($context, $stream) {
function appendPath($path) {
$this->searchpath[] = $path;
}
-
+
private function load() {
if (isset(h2o::$extensions[$this->extension])) {
return true;
@@ -416,7 +419,7 @@ class Debug_Tag extends H2o_Node {
function __construct($argstring, $parser, $pos = 0) {
$this->argument = $argstring;
}
-
+
function render($context, $stream) {
if ($this->argument) {
$object = $context->resolve(symbol($this->argument));
@@ -444,7 +447,7 @@ function __construct($argstring, $parser, $pos=0) {
$this->format = "D M j G:i:s T Y";
}
}
-
+
function render($contxt, $stream) {
$time = date($this->format);
$stream->write($time);
@@ -453,7 +456,7 @@ function render($contxt, $stream) {
class Autoescape_Tag extends H2o_Node {
protected $enable;
-
+
function __construct($argstring, $parser, $pos = 0) {
if ($argstring === 'on')
$this->enable = true;
@@ -463,7 +466,7 @@ function __construct($argstring, $parser, $pos = 0) {
"Invalid syntax : autoescape on|off "
);
}
-
+
function render($context, $stream) {
$context->autoescape = $this->enable;
}
Please sign in to comment.
Something went wrong with that request. Please try again.