added in vendor for prod

This commit is contained in:
2025-05-06 08:24:59 -07:00
parent 6207303b1c
commit db52cd6933
91 changed files with 13191 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
<?php
/**
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2014 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
class Autoloader
{
private $_baseDir;
/**
* Autoloader constructor.
*
* @param string $baseDir Handlebars library base directory default is
* __DIR__.'/..'
*/
protected function __construct($baseDir = null)
{
if ($baseDir === null) {
$this->_baseDir = realpath(__DIR__ . '/..');
} else {
$this->_baseDir = rtrim($baseDir, '/');
}
}
/**
* Register a new instance as an SPL autoloader.
*
* @param string $baseDir Handlebars library base directory, default is
* __DIR__.'/..'
*
* @return \Handlebars\Autoloader Registered Autoloader instance
*/
public static function register($baseDir = null)
{
$loader = new self($baseDir);
spl_autoload_register(array($loader, 'autoload'));
return $loader;
}
/**
* Autoload Handlebars classes.
*
* @param string $class class to load
*
* @return void
*/
public function autoload($class)
{
if ($class[0] === '\\') {
$class = substr($class, 1);
}
if (strpos($class, 'Handlebars') !== 0) {
return;
}
$file = sprintf('%s/%s.php', $this->_baseDir, str_replace('\\', '/', $class));
if (is_file($file)) {
include $file;
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* Cache Interface
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
interface Cache
{
/**
* Get cache for $name if exist.
*
* @param string $name Cache id
*
* @return mixed data on hit, boolean false on cache not found
*/
public function get($name);
/**
* Set a cache
*
* @param string $name cache id
* @param mixed $value data to store
*
* @return void
*/
public function set($name, $value);
/**
* Remove cache
*
* @param string $name Cache id
*
* @return void
*/
public function remove($name);
}

View File

@@ -0,0 +1,60 @@
<?php
/**
*
* @category Xamin
* @package Handlebars
* @author Joey Baker <joey@byjoeybaker.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @copyright 2013 (c) Meraki, LLP
* @copyright 2013 (c) Behrooz Shabani
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars\Cache;
use Handlebars\Cache;
class APC implements Cache
{
/**
* Get cache for $name if exist.
*
* @param string $name Cache id
*
* @return mixed data on hit, boolean false on cache not found
*/
public function get($name)
{
if (apc_exists($name)) {
return apc_fetch($name);
}
return false;
}
/**
* Set a cache
*
* @param string $name cache id
* @param mixed $value data to store
*
* @return void
*/
public function set($name, $value)
{
apc_store($name, $value);
}
/**
* Remove cache
*
* @param string $name Cache id
*
* @return void
*/
public function remove($name)
{
apc_delete($name);
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* A flat-file filesystem cache.
*
* @category Xamin
* @package Handlebars
* @author Alex Soncodi <alex@brokerloop.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2013 (c) Brokerloop, Inc.
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2013 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars\Cache;
use Handlebars\Cache;
use InvalidArgumentException;
use RuntimeException;
class Disk implements Cache
{
private $path = '';
private $prefix = '';
private $suffix = '';
/**
* Construct the disk cache.
*
* @param string $path Filesystem path to the disk cache location
* @param string $prefix optional file prefix, defaults to empty string
* @param string $suffix optional file extension, defaults to empty string
*
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function __construct($path, $prefix = '', $suffix = '')
{
if (empty($path)) {
throw new InvalidArgumentException('Must specify disk cache path');
} elseif (!is_dir($path)) {
@mkdir($path, 0777, true);
if (!is_dir($path)) {
throw new RuntimeException('Could not create cache file path');
}
}
$this->path = $path;
$this->prefix = $prefix;
$this->suffix = $suffix;
}
/**
* Gets the full disk path for a given cache item's file,
* taking into account the cache path, optional prefix,
* and optional extension.
*
* @param string $name Name of the cache item
*
* @return string full disk path of cached item
*/
private function getPath($name)
{
return $this->path . DIRECTORY_SEPARATOR .
$this->prefix . $name . $this->suffix;
}
/**
* Get cache for $name if it exists.
*
* @param string $name Cache id
*
* @return mixed data on hit, boolean false on cache not found
*/
public function get($name)
{
$path = $this->getPath($name);
return (file_exists($path)) ?
unserialize(file_get_contents($path)) : false;
}
/**
* Set a cache
*
* @param string $name cache id
* @param mixed $value data to store
*
* @return void
*/
public function set($name, $value)
{
$path = $this->getPath($name);
file_put_contents($path, serialize($value));
}
/**
* Remove cache
*
* @param string $name Cache id
*
* @return void
*/
public function remove($name)
{
$path = $this->getPath($name);
unlink($path);
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* A dummy array cache
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars\Cache;
use Handlebars\Cache;
class Dummy implements Cache
{
private $cache = [];
/**
* Get cache for $name if exist.
*
* @param string $name Cache id
*
* @return mixed data on hit, boolean false on cache not found
*/
public function get($name)
{
if (array_key_exists($name, $this->cache)) {
return $this->cache[$name];
}
return false;
}
/**
* Set a cache
*
* @param string $name cache id
* @param mixed $value data to store
*
* @return void
*/
public function set($name, $value)
{
$this->cache[$name] = $value;
}
/**
* Remove cache
*
* @param string $name Cache id
*
* @return void
*/
public function remove($name)
{
unset($this->cache[$name]);
}
}

View File

@@ -0,0 +1,382 @@
<?php
/**
* Handlebars context
* Context for a template
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2013 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
use InvalidArgumentException;
use LogicException;
class Context
{
const DATA_KEY = 'key';
const DATA_INDEX = 'index';
const DATA_FIRST = 'first';
const DATA_LAST = 'last';
/**
* @var array stack for context only top stack is available
*/
protected $stack = [];
/**
* @var array index stack for sections
*/
protected $index = [];
/**
* @var array dataStack stack for data within sections
*/
protected $dataStack = [];
/**
* @var array key stack for objects
*/
protected $key = [];
/**
* @var bool enableDataVariables true if @data variables should be used.
*/
protected $enableDataVariables = false;
/**
* Mustache rendering Context constructor.
*
* @param mixed $context Default rendering context (default: null)
* @param array $options Options for the context. It may contain the following: (default: empty array)
* enableDataVariables => Boolean, Enables @data variables (default: false)
*
* @throws InvalidArgumentException when calling this method when enableDataVariables is not a boolean.
*/
public function __construct($context = null, $options = [])
{
if ($context !== null) {
$this->stack = [$context];
}
if (isset($options[Handlebars::OPTION_ENABLE_DATA_VARIABLES])) {
if (!is_bool($options[Handlebars::OPTION_ENABLE_DATA_VARIABLES])) {
throw new InvalidArgumentException(
'Context Constructor "' . Handlebars::OPTION_ENABLE_DATA_VARIABLES . '" option must be a boolean'
);
}
$this->enableDataVariables = $options[Handlebars::OPTION_ENABLE_DATA_VARIABLES];
}
}
/**
* Push a new Context frame onto the stack.
*
* @param mixed $value Object or array to use for context
*
* @return void
*/
public function push($value)
{
array_push($this->stack, $value);
}
/**
* Push an Index onto the index stack
*
* @param integer $index Index of the current section item.
*
* @return void
*/
public function pushIndex($index)
{
array_push($this->index, $index);
}
/**
* Pushes data variables onto the stack. This is used to support @data variables.
* @param array $data Associative array where key is the name of the @data variable and value is the value.
* @throws LogicException when calling this method without having enableDataVariables.
*/
public function pushData($data)
{
if (!$this->enableDataVariables) {
throw new LogicException('Data variables are not supported due to the enableDataVariables configuration. Remove the call to data variables or change the setting.');
}
array_push($this->dataStack, $data);
}
/**
* Push a Key onto the key stack
*
* @param string $key Key of the current object property.
*
* @return void
*/
public function pushKey($key)
{
array_push($this->key, $key);
}
/**
* Pop the last Context frame from the stack.
*
* @return mixed Last Context frame (object or array)
*/
public function pop()
{
return array_pop($this->stack);
}
/**
* Pop the last index from the stack.
*
* @return int Last index
*/
public function popIndex()
{
return array_pop($this->index);
}
/**
* Pop the last section data from the stack.
*
* @return array Last data
* @throws LogicException when calling this method without having enableDataVariables.
*/
public function popData()
{
if (!$this->enableDataVariables) {
throw new LogicException('Data variables are not supported due to the enableDataVariables configuration. Remove the call to data variables or change the setting.');
}
return array_pop($this->dataStack);
}
/**
* Pop the last key from the stack.
*
* @return string Last key
*/
public function popKey()
{
return array_pop($this->key);
}
/**
* Get the last Context frame.
*
* @return mixed Last Context frame (object or array)
*/
public function last()
{
return end($this->stack);
}
/**
* Get the index of current section item.
*
* @return mixed Last index
*/
public function lastIndex()
{
return end($this->index);
}
/**
* Get the key of current object property.
*
* @return mixed Last key
*/
public function lastKey()
{
return end($this->key);
}
/**
* Change the current context to one of current context members
*
* @param string $variableName name of variable or a callable on current context
*
* @return mixed actual value
*/
public function with($variableName)
{
$value = $this->get($variableName);
$this->push($value);
return $value;
}
/**
* Get a avariable from current context
* Supported types :
* variable , ../variable , variable.variable , .
*
* @param string $variableName variavle name to get from current context
* @param boolean $strict strict search? if not found then throw exception
*
* @throws InvalidArgumentException in strict mode and variable not found
* @return mixed
*/
public function get($variableName, $strict = false)
{
//Need to clean up
$variableName = trim($variableName);
//Handle data variables (@index, @first, @last, etc)
if ($this->enableDataVariables && substr($variableName, 0, 1) == '@') {
return $this->getDataVariable($variableName, $strict);
}
$level = 0;
while (substr($variableName, 0, 3) == '../') {
$variableName = trim(substr($variableName, 3));
$level++;
}
if (count($this->stack) < $level) {
if ($strict) {
throw new InvalidArgumentException(
'can not find variable in context'
);
}
return '';
}
end($this->stack);
while ($level) {
prev($this->stack);
$level--;
}
$current = current($this->stack);
if (!$variableName) {
if ($strict) {
throw new InvalidArgumentException(
'can not find variable in context'
);
}
return '';
} elseif ($variableName == '.' || $variableName == 'this') {
return $current;
} else {
$chunks = explode('.', $variableName);
foreach ($chunks as $chunk) {
if (is_string($current) and $current == '') {
return $current;
}
$current = $this->findVariableInContext($current, $chunk, $strict);
}
}
return $current;
}
/**
* Given a data variable, retrieves the value associated.
*
* @param $variableName
* @param bool $strict
* @return mixed
* @throws LogicException when calling this method without having enableDataVariables.
*/
public function getDataVariable($variableName, $strict = false)
{
if (!$this->enableDataVariables) {
throw new LogicException('Data variables are not supported due to the enableDataVariables configuration. Remove the call to data variables or change the setting.');
}
$variableName = trim($variableName);
// make sure we get an at-symbol prefix
if (substr($variableName, 0, 1) != '@') {
if ($strict) {
throw new InvalidArgumentException(
'Can not find variable in context'
);
}
return '';
}
// Remove the at-symbol prefix
$variableName = substr($variableName, 1);
// determine the level of relative @data variables
$level = 0;
while (substr($variableName, 0, 3) == '../') {
$variableName = trim(substr($variableName, 3));
$level++;
}
// make sure the stack actually has the specified number of levels
if (count($this->dataStack) < $level) {
if ($strict) {
throw new InvalidArgumentException(
'Can not find variable in context'
);
}
return '';
}
// going from the top of the stack to the bottom, traverse the number of levels specified
end($this->dataStack);
while ($level) {
prev($this->dataStack);
$level--;
}
/** @var array $current */
$current = current($this->dataStack);
if (!array_key_exists($variableName, $current)) {
if ($strict) {
throw new InvalidArgumentException(
'Can not find variable in context'
);
}
return '';
}
return $current[$variableName];
}
/**
* Check if $variable->$inside is available
*
* @param mixed $variable variable to check
* @param string $inside property/method to check
* @param boolean $strict strict search? if not found then throw exception
*
* @throws \InvalidArgumentException in strict mode and variable not found
* @return boolean true if exist
*/
private function findVariableInContext($variable, $inside, $strict = false)
{
$value = '';
if (($inside !== '0' && empty($inside)) || ($inside == 'this')) {
return $variable;
} elseif (is_array($variable)) {
if (isset($variable[$inside])) {
$value = $variable[$inside];
}
} elseif (is_object($variable)) {
if (isset($variable->$inside)) {
$value = $variable->$inside;
} elseif (is_callable(array($variable, $inside))) {
$value = call_user_func(array($variable, $inside));
}
} elseif ($inside === '.') {
$value = $variable;
} elseif ($strict) {
throw new InvalidArgumentException('can not find variable in context');
}
return $value;
}
}

View File

@@ -0,0 +1,533 @@
<?php
/**
* Handlebars
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2014 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
use Handlebars\Loader\StringLoader;
use Handlebars\Cache\Dummy;
use InvalidArgumentException;
class Handlebars
{
private static $instance = null;
const VERSION = '2.2';
const OPTION_ENABLE_DATA_VARIABLES = 'enableDataVariables';
/**
* factory method
*
* @param array $options see __construct's options parameter
*
* @return Handlebars
*/
public static function factory($options = array())
{
if (! self::$instance) {
self::$instance = new self($options);
}
return self::$instance;
}
/**
* @var Tokenizer
*/
private $tokenizer;
/**
* @var Parser
*/
private $parser;
/**
* @var Helpers
*/
private $helpers;
/**
* @var Loader
*/
private $loader;
/**
* @var Loader
*/
private $partialsLoader;
/**
* @var Cache
*/
private $cache;
/**
* @var callable escape function to use
*/
private $escape = 'htmlspecialchars';
/**
* @var array parametes to pass to escape function
*/
private $escapeArgs = array(
ENT_COMPAT,
'UTF-8'
);
private $aliases = array();
/**
* @var bool Enable @data variables
*/
private $enableDataVariables = false;
/**
* Handlebars engine constructor
* $options array can contain :
* helpers => Helpers object
* escape => a callable function to escape values
* escapeArgs => array to pass as extra parameter to escape function
* loader => Loader object
* partials_loader => Loader object
* cache => Cache object
* enableDataVariables => boolean. Enables @data variables (default: false)
*
* @param array $options array of options to set
*
* @throws \InvalidArgumentException
*/
public function __construct(Array $options = [])
{
if (isset($options['helpers'])) {
$this->setHelpers($options['helpers']);
}
if (isset($options['loader'])) {
$this->setLoader($options['loader']);
}
if (isset($options['partials_loader'])) {
$this->setPartialsLoader($options['partials_loader']);
}
if (isset($options['cache'])) {
$this->setCache($options['cache']);
}
if (isset($options['escape'])) {
if (!is_callable($options['escape'])) {
throw new InvalidArgumentException(
'Handlebars Constructor "escape" option must be callable'
);
}
$this->escape = $options['escape'];
}
if (isset($options['escapeArgs'])) {
if (!is_array($options['escapeArgs'])) {
$options['escapeArgs'] = array($options['escapeArgs']);
}
$this->escapeArgs = $options['escapeArgs'];
}
if (isset($options['partials_alias'])
&& is_array($options['partials_alias'])
) {
$this->aliases = $options['partials_alias'];
}
if (isset($options[self::OPTION_ENABLE_DATA_VARIABLES])) {
if (!is_bool($options[self::OPTION_ENABLE_DATA_VARIABLES])) {
throw new InvalidArgumentException(
'Handlebars Constructor "' . self::OPTION_ENABLE_DATA_VARIABLES . '" option must be a boolean'
);
}
$this->enableDataVariables = $options[self::OPTION_ENABLE_DATA_VARIABLES];
}
}
/**
* Shortcut 'render' invocation.
*
* Equivalent to calling `$handlebars->loadTemplate($template)->render($data);`
*
* @param string $template template name
* @param mixed $data data to use as context
* @return string Rendered template
*/
public function render($template, $data)
{
return $this->loadTemplate($template)->render($data);
}
/**
* To invoke when this object is called as a function
*
* @param string $template template name
* @param mixed $data data to use as context
* @return string Rendered template
*/
public function __invoke($template, $data)
{
return $this->render($template, $data);
}
/**
* Set helpers for current enfine
*
* @param Helpers $helpers handlebars helper
*
* @return void
*/
public function setHelpers(Helpers $helpers)
{
$this->helpers = $helpers;
}
/**
* Get helpers, or create new one if ther is no helper
*
* @return Helpers
*/
public function getHelpers()
{
if (!isset($this->helpers)) {
$this->helpers = new Helpers();
}
return $this->helpers;
}
/**
* Add a new helper.
*
* @param string $name helper name
* @param mixed $helper helper callable
*
* @return void
*/
public function addHelper($name, $helper)
{
$this->getHelpers()->add($name, $helper);
}
/**
* Get a helper by name.
*
* @param string $name helper name
* @return callable Helper
*/
public function getHelper($name)
{
return $this->getHelpers()->__get($name);
}
/**
* Check whether this instance has a helper.
*
* @param string $name helper name
* @return boolean True if the helper is present
*/
public function hasHelper($name)
{
return $this->getHelpers()->has($name);
}
/**
* Remove a helper by name.
*
* @param string $name helper name
* @return void
*/
public function removeHelper($name)
{
$this->getHelpers()->remove($name);
}
/**
* Set current loader
*
* @param Loader $loader handlebars loader
* @return void
*/
public function setLoader(Loader $loader)
{
$this->loader = $loader;
}
/**
* get current loader
*
* @return Loader
*/
public function getLoader()
{
if (! isset($this->loader)) {
$this->loader = new StringLoader();
}
return $this->loader;
}
/**
* Set current partials loader
*
* @param Loader $loader handlebars loader
* @return void
*/
public function setPartialsLoader(Loader $loader)
{
$this->partialsLoader = $loader;
}
/**
* get current partials loader
*
* @return Loader
*/
public function getPartialsLoader()
{
if (!isset($this->partialsLoader)) {
$this->partialsLoader = new StringLoader();
}
return $this->partialsLoader;
}
/**
* Set cache for current engine
*
* @param Cache $cache handlebars cache
* @return void
*/
public function setCache(Cache $cache)
{
$this->cache = $cache;
}
/**
* Get cache
*
* @return Cache
*/
public function getCache()
{
if (!isset($this->cache)) {
$this->cache = new Dummy();
}
return $this->cache;
}
/**
* Get current escape function
*
* @return callable
*/
public function getEscape()
{
return $this->escape;
}
/**
* Set current escape function
*
* @param callable $escape function
* @throws \InvalidArgumentException
* @return void
*/
public function setEscape($escape)
{
if (!is_callable($escape)) {
throw new InvalidArgumentException(
'Escape function must be a callable'
);
}
$this->escape = $escape;
}
/**
* Get current escape function
*
* @return array
*/
public function getEscapeArgs()
{
return $this->escapeArgs;
}
/**
* Set current escape function
*
* @param array $escapeArgs arguments to pass as extra arg to function
* @return void
*/
public function setEscapeArgs($escapeArgs)
{
if (! is_array($escapeArgs)) {
$escapeArgs = array($escapeArgs);
}
$this->escapeArgs = $escapeArgs;
}
/**
* Set the Handlebars Tokenizer instance.
*
* @param Tokenizer $tokenizer tokenizer
* @return void
*/
public function setTokenizer(Tokenizer $tokenizer)
{
$this->tokenizer = $tokenizer;
}
/**
* Get the current Handlebars Tokenizer instance.
*
* If no Tokenizer instance has been explicitly specified, this method will
* instantiate and return a new one.
*
* @return Tokenizer
*/
public function getTokenizer()
{
if (! isset($this->tokenizer)) {
$this->tokenizer = new Tokenizer();
}
return $this->tokenizer;
}
/**
* Set the Handlebars Parser instance.
*
* @param Parser $parser parser object
* @return void
*/
public function setParser(Parser $parser)
{
$this->parser = $parser;
}
/**
* Get the current Handlebars Parser instance.
*
* If no Parser instance has been explicitly specified, this method will
* instantiate and return a new one.
*
* @return Parser
*/
public function getParser()
{
if (! isset($this->parser)) {
$this->parser = new Parser();
}
return $this->parser;
}
/**
* Determines if the @data variables are enabled.
* @return bool
*/
public function isDataVariablesEnabled()
{
return $this->enableDataVariables;
}
/**
* Load a template by name with current template loader
*
* @param string $name template name
*
* @return Template
*/
public function loadTemplate($name)
{
$source = $this->getLoader()->load($name);
$tree = $this->tokenize($source);
return new Template($this, $tree, $source);
}
/**
* Load a partial by name with current partial loader
*
* @param string $name partial name
*
* @return Template
*/
public function loadPartial($name)
{
if (isset($this->aliases[$name])) {
$name = $this->aliases[$name];
}
$source = $this->getPartialsLoader()->load($name);
$tree = $this->tokenize($source);
return new Template($this, $tree, $source);
}
/**
* Register partial alias
*
* @param string $alias Partial alias
* @param string $content The real value
* @return void
*/
public function registerPartial($alias, $content)
{
$this->aliases[$alias] = $content;
}
/**
* Un-register partial alias
*
* @param string $alias Partial alias
* @return void
*/
public function unRegisterPartial($alias)
{
if (isset($this->aliases[$alias])) {
unset($this->aliases[$alias]);
}
}
/**
* Load string into a template object
*
* @param string $source string to load
* @return Template
*/
public function loadString($source)
{
$tree = $this->tokenize($source);
return new Template($this, $tree, $source);
}
/**
* try to tokenize source, or get them from cache if available
*
* @param string $source handlebars source code
* @return array handlebars parsed data into array
*/
private function tokenize($source)
{
$hash = md5(sprintf('version: %s, data : %s', self::VERSION, $source));
$tree = $this->getCache()->get($hash);
if ($tree === false) {
$tokens = $this->getTokenizer()->scan($source);
$tree = $this->getParser()->parse($tokens);
$this->getCache()->set($hash, $tree);
}
return $tree;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Handlebars string
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2013 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
class HandlebarsString
{
private $string = "";
/**
* Create new string
*
* @param string $string input source
*/
public function __construct($string)
{
$this->setString($string);
}
/**
* To String
*
* @return string
*/
public function __toString()
{
return $this->getString();
}
/**
* Get string
*
* @return string
*/
public function getString()
{
return $this->string;
}
/**
* Create new string
*
* @param string $string input source
*
* @return void
*/
public function setString($string)
{
$this->string = $string;
}
}

View File

@@ -0,0 +1,733 @@
<?php
/**
* Helpers
*
* a collection of helper function. normally a function like
* function ($sender, $name, $arguments) $arguments is unscaped arguments and
* is a string, not array
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2014 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
use DateTime;
use InvalidArgumentException;
use Traversable;
use LogicException;
class Helpers
{
/**
* @var array array of helpers
*/
protected $helpers = [];
private $tpl = [];
protected $builtinHelpers = [
"if",
"each",
"with",
"unless",
"bindAttr",
"upper", // Put all chars in uppercase
"lower", // Put all chars in lowercase
"capitalize", // Capitalize just the first word
"capitalize_words", // Capitalize each words
"reverse", // Reverse a string
"format_date", // Format a date
"inflect", // Inflect the wording based on count ie. 1 album, 10 albums
"default", // If a variable is null, it will use the default instead
"truncate", // Truncate section
"raw", // Return the source as is without converting
"repeat", // Repeat a section
"define", // Define a block to be used using "invoke"
"invoke", // Invoke a block that was defined with "define"
];
/**
* Create new helper container class
*
* @param array $helpers array of name=>$value helpers
* @throws \InvalidArgumentException when $helpers is not an array
* (or traversable) or helper is not a callable
*/
public function __construct($helpers = null)
{
foreach($this->builtinHelpers as $helper) {
$helperName = $this->underscoreToCamelCase($helper);
$this->add($helper, [$this, "helper{$helperName}"]);
}
if ($helpers != null) {
if (!is_array($helpers) && !$helpers instanceof Traversable) {
throw new InvalidArgumentException(
'HelperCollection constructor expects an array of helpers'
);
}
foreach ($helpers as $name => $helper) {
$this->add($name, $helper);
}
}
}
/**
* Add a new helper to helpers
*
* @param string $name helper name
* @param callable $helper a function as a helper
*
* @throws \InvalidArgumentException if $helper is not a callable
* @return void
*/
public function add($name, $helper)
{
if (!is_callable($helper)) {
throw new InvalidArgumentException("$name Helper is not a callable.");
}
$this->helpers[$name] = $helper;
}
/**
* Check if $name helper is available
*
* @param string $name helper name
*
* @return boolean
*/
public function has($name)
{
return array_key_exists($name, $this->helpers);
}
/**
* Get a helper. __magic__ method :)
*
* @param string $name helper name
*
* @throws \InvalidArgumentException if $name is not available
* @return callable helper function
*/
public function __get($name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException('Unknown helper :' . $name);
}
return $this->helpers[$name];
}
/**
* Check if $name helper is available __magic__ method :)
*
* @param string $name helper name
*
* @return boolean
* @see Handlebras_Helpers::has
*/
public function __isset($name)
{
return $this->has($name);
}
/**
* Add a new helper to helpers __magic__ method :)
*
* @param string $name helper name
* @param callable $helper a function as a helper
*
* @return void
*/
public function __set($name, $helper)
{
$this->add($name, $helper);
}
/**
* Unset a helper
*
* @param string $name helper name to remove
* @return void
*/
public function __unset($name)
{
unset($this->helpers[$name]);
}
/**
* Check whether a given helper is present in the collection.
*
* @param string $name helper name
* @throws \InvalidArgumentException if the requested helper is not present.
* @return void
*/
public function remove($name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException('Unknown helper: ' . $name);
}
unset($this->helpers[$name]);
}
/**
* Clear the helper collection.
*
* Removes all helpers from this collection
*
* @return void
*/
public function clear()
{
$this->helpers = [];
}
/**
* Check whether the helper collection is empty.
*
* @return boolean True if the collection is empty
*/
public function isEmpty()
{
return empty($this->helpers);
}
/**
* Create handler for the 'if' helper.
*
* {{#if condition}}
* Something here
* {{else}}
* something else here
* {{/if}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperIf($template, $context, $args, $source)
{
$tmp = $context->get($args);
if ($tmp) {
$template->setStopToken('else');
$buffer = $template->render($context);
$template->setStopToken(false);
$template->discard();
return $buffer;
} else {
return $this->renderElse($template, $context);
}
}
/**
* Create handler for the 'each' helper.
* example {{#each people}} {{name}} {{/each}}
* example with slice: {{#each people[0:10]}} {{name}} {{/each}}
* example with else
* {{#each Array}}
* {{.}}
* {{else}}
* Nothing found
* {{/each}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperEach($template, $context, $args, $source)
{
list($keyname, $slice_start, $slice_end) = $this->extractSlice($args);
$tmp = $context->get($keyname);
if (is_array($tmp) || $tmp instanceof Traversable) {
$tmp = array_slice($tmp, $slice_start, $slice_end);
$buffer = '';
$islist = array_values($tmp) === $tmp;
if (is_array($tmp) && ! count($tmp)) {
return $this->renderElse($template, $context);
} else {
$itemCount = -1;
if ($islist) {
$itemCount = count($tmp);
}
foreach ($tmp as $key => $var) {
$tpl = clone $template;
if ($islist) {
$context->pushIndex($key);
// If data variables are enabled, push the data related to this #each context
if ($template->getEngine()->isDataVariablesEnabled()) {
$context->pushData([
Context::DATA_KEY => $key,
Context::DATA_INDEX => $key,
Context::DATA_LAST => $key == ($itemCount - 1),
Context::DATA_FIRST => $key == 0,
]);
}
} else {
$context->pushKey($key);
// If data variables are enabled, push the data related to this #each context
if ($template->getEngine()->isDataVariablesEnabled()) {
$context->pushData([
Context::DATA_KEY => $key,
]);
}
}
$context->push($var);
$tpl->setStopToken('else');
$buffer .= $tpl->render($context);
$context->pop();
if ($islist) {
$context->popIndex();
} else {
$context->popKey();
}
if ($template->getEngine()->isDataVariablesEnabled()) {
$context->popData();
}
}
return $buffer;
}
} else {
return $this->renderElse($template, $context);
}
}
/**
* Applying the DRY principle here.
* This method help us render {{else}} portion of a block
* @param \Handlebars\Template $template
* @param \Handlebars\Context $context
* @return string
*/
private function renderElse($template, $context)
{
$template->setStopToken('else');
$template->discard();
$template->setStopToken(false);
return $template->render($context);
}
/**
* Create handler for the 'unless' helper.
* {{#unless condition}}
* Something here
* {{else}}
* something else here
* {{/unless}}
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperUnless($template, $context, $args, $source)
{
$tmp = $context->get($args);
if (!$tmp) {
$template->setStopToken('else');
$buffer = $template->render($context);
$template->setStopToken(false);
$template->discard();
return $buffer;
} else {
return $this->renderElse($template, $context);
}
}
/**
* Create handler for the 'with' helper.
* Needed for compatibility with PHP 5.2 since it doesn't support anonymous
* functions.
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperWith($template, $context, $args, $source)
{
$tmp = $context->get($args);
$context->push($tmp);
$buffer = $template->render($context);
$context->pop();
return $buffer;
}
/**
* Create handler for the 'bindAttr' helper.
* Needed for compatibility with PHP 5.2 since it doesn't support anonymous
* functions.
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperBindAttr($template, $context, $args, $source)
{
return $args;
}
/**
* To uppercase string
*
* {{#upper data}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperUpper($template, $context, $args, $source)
{
return strtoupper($context->get($args));
}
/**
* To lowercase string
*
* {{#lower data}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperLower($template, $context, $args, $source)
{
return strtolower($context->get($args));
}
/**
* to capitalize first letter
*
* {{#capitalize}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperCapitalize($template, $context, $args, $source)
{
return ucfirst($context->get($args));
}
/**
* To capitalize first letter in each word
*
* {{#capitalize_words data}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperCapitalizeWords($template, $context, $args, $source)
{
return ucwords($context->get($args));
}
/**
* To reverse a string
*
* {{#reverse data}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperReverse($template, $context, $args, $source)
{
return strrev($context->get($args));
}
/**
* Format a date
*
* {{#format_date date 'Y-m-d @h:i:s'}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperFormatDate($template, $context, $args, $source)
{
preg_match("/(.*?)\s+(?:(?:\"|\')(.*?)(?:\"|\'))/", $args, $m);
$keyname = $m[1];
$format = $m[2];
$date = $context->get($keyname);
if ($format) {
$dt = new DateTime;
if (is_numeric($date)) {
$dt = (new DateTime)->setTimestamp($date);
} else {
$dt = new DateTime($date);
}
return $dt->format($format);
} else {
return $date;
}
}
/**
* {{inflect count 'album' 'albums'}}
* {{inflect count '%d album' '%d albums'}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperInflect($template, $context, $args, $source)
{
preg_match("/(.*?)\s+(?:(?:\"|\')(.*?)(?:\"|\'))\s+(?:(?:\"|\')(.*?)(?:\"|\'))/", $args, $m);
$keyname = $m[1];
$singular = $m[2];
$plurial = $m[3];
$value = $context->get($keyname);
$inflect = ($value <= 1) ? $singular : $plurial;
return sprintf($inflect, $value);
}
/**
* Provide a default fallback
*
* {{default title "No title available"}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperDefault($template, $context, $args, $source)
{
preg_match("/(.*?)\s+(?:(?:\"|\')(.*?)(?:\"|\'))/", trim($args), $m);
$keyname = $m[1];
$default = $m[2];
$value = $context->get($keyname);
return ($value) ?: $default;
}
/**
* Truncate a string to a length, and append and ellipsis if provided
* {{#truncate content 5 "..."}}
*
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperTruncate($template, $context, $args, $source)
{
preg_match("/(.*?)\s+(.*?)\s+(?:(?:\"|\')(.*?)(?:\"|\'))/", trim($args), $m);
$keyname = $m[1];
$limit = $m[2];
$ellipsis = $m[3];
$value = substr($context->get($keyname), 0, $limit);
if ($ellipsis && strlen($context->get($keyname)) > $limit) {
$value .= $ellipsis;
}
return $value;
}
/**
* Return the data source as is
*
* {{#raw}} {{/raw}}
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return mixed
*/
public function helperRaw($template, $context, $args, $source)
{
return $source;
}
/**
* Repeat section $x times.
*
* {{#repeat 10}}
* This section will be repeated 10 times
* {{/repeat}}
*
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return string
*/
public function helperRepeat($template, $context, $args, $source)
{
$buffer = $template->render($context);
return str_repeat($buffer, intval($args));
}
/**
* Define a section to be used later by using 'invoke'
*
* --> Define a section: hello
* {{#define hello}}
* Hello World!
*
* How is everything?
* {{/define}}
*
* --> This is how it is called
* {{#invoke hello}}
*
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return null
*/
public function helperDefine($template, $context, $args, $source)
{
$this->tpl["DEFINE"][$args] = clone($template);
}
/**
* Invoke a section that was created using 'define'
*
* --> Define a section: hello
* {{#define hello}}
* Hello World!
*
* How is everything?
* {{/define}}
*
* --> This is how it is called
* {{#invoke hello}}
*
*
* @param \Handlebars\Template $template template that is being rendered
* @param \Handlebars\Context $context context object
* @param array $args passed arguments to helper
* @param string $source part of template that is wrapped
* within helper
*
* @return null
*/
public function helperInvoke($template, $context, $args, $source)
{
if (! isset($this->tpl["DEFINE"][$args])) {
throw new LogicException("Can't INVOKE '{$args}'. '{$args}' was not DEFINE ");
}
return $this->tpl["DEFINE"][$args]->render($context);
}
/**
* Change underscore helper name to CamelCase
*
* @param string $string
* @return string
*/
private function underscoreToCamelCase($string)
{
return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
}
/**
* slice
* Allow to split the data that will be returned
* #loop[start:end] => starts at start trhough end -1
* #loop[start:] = Starts at start though the rest of the array
* #loop[:end] = Starts at the beginning through end -1
* #loop[:] = A copy of the whole array
*
* #loop[-1]
* #loop[-2:] = Last two items
* #loop[:-2] = Everything except last two items
*
* @param string $string
* @return Array [tag_name, slice_start, slice_end]
*/
private function extractSlice($string)
{
preg_match("/^([\w\._\-]+)(?:\[([\-0-9]*?:[\-0-9]*?)\])?/i", $string, $m);
$slice_start = $slice_end = null;
if (isset($m[2])) {
list($slice_start, $slice_end) = explode(":", $m[2]);
$slice_start = (int) $slice_start;
$slice_end = $slice_end ? (int) $slice_end : null;
}
return [$m[1], $slice_start, $slice_end];
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Handlebars loader interface
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
interface Loader
{
/**
* Load a Template by name.
*
* @param string $name template name to load
*
* @return String
*/
public function load($name);
}

View File

@@ -0,0 +1,144 @@
<?php
/**
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Craig Bass <craig@clearbooks.co.uk>
* @author ^^ <craig@devls.co.uk>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars\Loader;
use Handlebars\Loader;
use Handlebars\HandlebarsString;
class FilesystemLoader implements Loader
{
private $_baseDir;
private $_extension = '.handlebars';
private $_prefix = '';
private $_templates = array();
/**
* Handlebars filesystem Loader constructor.
*
* $options array allows overriding certain Loader options during instantiation:
*
* $options = array(
* // extension used for Handlebars templates. Defaults to '.handlebars'
* 'extension' => '.other',
* );
*
* @param string|array $baseDirs A path contain template files or array of paths
* @param array $options Array of Loader options (default: array())
*
* @throws \RuntimeException if $baseDir does not exist.
*/
public function __construct($baseDirs, Array $options = [])
{
if (is_string($baseDirs)) {
$baseDirs = array(rtrim(realpath($baseDirs), '/'));
} else {
foreach ($baseDirs as &$dir) {
$dir = rtrim(realpath($dir), '/');
} unset( $dir );
}
$this->_baseDir = $baseDirs;
foreach ($this->_baseDir as $dir) {
if (!is_dir($dir)) {
throw new \RuntimeException(
'FilesystemLoader baseDir must be a directory: ' . $dir
);
}
}
if (isset($options['extension'])) {
$this->_extension = '.' . ltrim($options['extension'], '.');
}
if (isset($options['prefix'])) {
$this->_prefix = $options['prefix'];
}
}
/**
* Load a Template by name.
*
* $loader = new FilesystemLoader(dirname(__FILE__).'/views');
* // loads "./views/admin/dashboard.handlebars";
* $loader->load('admin/dashboard');
*
* @param string $name template name
*
* @return HandlebarsString Handlebars Template source
*/
public function load($name)
{
if (!isset($this->_templates[$name])) {
$this->_templates[$name] = $this->loadFile($name);
}
return new HandlebarsString($this->_templates[$name]);
}
/**
* Helper function for loading a Handlebars file by name.
*
* @param string $name template name
*
* @throws \InvalidArgumentException if a template file is not found.
* @return string Handlebars Template source
*/
protected function loadFile($name)
{
$fileName = $this->getFileName($name);
if ($fileName === false) {
throw new \InvalidArgumentException('Template ' . $name . ' not found.');
}
return file_get_contents($fileName);
}
/**
* Helper function for getting a Handlebars template file name.
*
* @param string $name template name
*
* @return string Template file name
*/
protected function getFileName($name)
{
foreach ($this->_baseDir as $baseDir) {
$fileName = $baseDir . '/';
$fileParts = explode('/', $name);
$file = array_pop($fileParts);
if (substr($file, strlen($this->_prefix)) !== $this->_prefix) {
$file = $this->_prefix . $file;
}
$fileParts[] = $file;
$fileName .= implode('/', $fileParts);
$lastCharacters = substr($fileName, 0 - strlen($this->_extension));
if ($lastCharacters !== $this->_extension) {
$fileName .= $this->_extension;
}
if (file_exists($fileName)) {
return $fileName;
}
}
return false;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Handlebars Template string Loader implementation.
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2013 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars\Loader;
use Handlebars\Loader;
use Handlebars\HandlebarsString;
class StringLoader implements Loader
{
/**
* Load a Template by source.
*
* @param string $name Handlebars Template source
*
* @return HandlebarsString Handlebars Template source
*/
public function load($name)
{
return new HandlebarsString($name);
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* Handlebars parser (based on mustache)
*
* This class is responsible for turning raw template source into a set of
* Handlebars tokens.
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2013 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
use ArrayIterator;
use LogicException;
class Parser
{
/**
* Process array of tokens and convert them into parse tree
*
* @param array $tokens Set of
*
* @return array Token parse tree
*/
public function parse(Array $tokens = [])
{
return $this->buildTree(new ArrayIterator($tokens));
}
/**
* Helper method for recursively building a parse tree.
*
* @param \ArrayIterator $tokens Stream of tokens
*
* @throws \LogicException when nesting errors or mismatched section tags
* are encountered.
* @return array Token parse tree
*
*/
private function buildTree(ArrayIterator $tokens)
{
$stack = [];
do {
$token = $tokens->current();
$tokens->next();
if ($token === null) {
continue;
} else {
switch ($token[Tokenizer::TYPE]) {
case Tokenizer::T_END_SECTION:
$newNodes = [];
do {
$result = array_pop($stack);
if ($result === null) {
throw new LogicException(
'Unexpected closing tag: /' . $token[Tokenizer::NAME]
);
}
if (!array_key_exists(Tokenizer::NODES, $result)
&& isset($result[Tokenizer::NAME])
&& $result[Tokenizer::NAME] == $token[Tokenizer::NAME]
) {
$result[Tokenizer::NODES] = $newNodes;
$result[Tokenizer::END] = $token[Tokenizer::INDEX];
array_push($stack, $result);
break 2;
} else {
array_unshift($newNodes, $result);
}
} while (true);
break;
default:
array_push($stack, $token);
}
}
} while ($tokens->valid());
return $stack;
}
}

View File

@@ -0,0 +1,386 @@
<?php
/**
* Handlebars base template
* contain some utility method to get context and helpers
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2013 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
use InvalidArgumentException;
use RuntimeException;
class Template
{
/**
* @var Handlebars
*/
protected $handlebars;
protected $tree = [];
protected $source = '';
/**
* @var array Run stack
*/
private $stack = [];
private $_stack = [];
/**
* Handlebars template constructor
*
* @param Handlebars $engine handlebar engine
* @param array $tree Parsed tree
* @param string $source Handlebars source
*/
public function __construct(Handlebars $engine, $tree, $source)
{
$this->handlebars = $engine;
$this->tree = $tree;
$this->source = $source;
array_push($this->stack, [0, $this->getTree(), false]);
}
/**
* Get current tree
*
* @return array
*/
public function getTree()
{
return $this->tree;
}
/**
* Get current source
*
* @return string
*/
public function getSource()
{
return $this->source;
}
/**
* Get current engine associated with this object
*
* @return Handlebars
*/
public function getEngine()
{
return $this->handlebars;
}
/**
* set stop token for render and discard method
*
* @param string $token token to set as stop token or false to remove
*
* @return void
*/
public function setStopToken($token)
{
$this->_stack = $this->stack;
$topStack = array_pop($this->stack);
$topStack[2] = $token;
array_push($this->stack, $topStack);
}
/**
* get current stop token
*
* @return string|bool
*/
public function getStopToken()
{
return end($this->stack)[2];
}
/**
* Render top tree
*
* @param mixed $context current context
*
* @throws \RuntimeException
* @return string
*/
public function render($context)
{
if (!$context instanceof Context) {
$context = new Context($context, [
'enableDataVariables' => $this->handlebars->isDataVariablesEnabled(),
]);
}
$topTree = end($this->stack); // never pop a value from stack
list($index, $tree, $stop) = $topTree;
$buffer = '';
while (array_key_exists($index, $tree)) {
$current = $tree[$index];
$index++;
//if the section is exactly like waitFor
if (is_string($stop)
&& $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED
&& $current[Tokenizer::NAME] === $stop
) {
break;
}
switch ($current[Tokenizer::TYPE]) {
case Tokenizer::T_SECTION :
$newStack = isset($current[Tokenizer::NODES])
? $current[Tokenizer::NODES] : [];
array_push($this->stack, [0, $newStack, false]);
$buffer .= $this->section($context, $current);
array_pop($this->stack);
break;
case Tokenizer::T_INVERTED :
$newStack = isset($current[Tokenizer::NODES]) ?
$current[Tokenizer::NODES] : [];
array_push($this->stack, [0, $newStack, false]);
$buffer .= $this->inverted($context, $current);
array_pop($this->stack);
break;
case Tokenizer::T_COMMENT :
$buffer .= '';
break;
case Tokenizer::T_PARTIAL:
case Tokenizer::T_PARTIAL_2:
$buffer .= $this->partial($context, $current);
break;
case Tokenizer::T_UNESCAPED:
case Tokenizer::T_UNESCAPED_2:
$buffer .= $this->variables($context, $current, false);
break;
case Tokenizer::T_ESCAPED:
$buffer .= $this->variables($context, $current, true);
break;
case Tokenizer::T_TEXT:
$buffer .= $current[Tokenizer::VALUE];
break;
default:
throw new RuntimeException(
'Invalid node type : ' . json_encode($current)
);
}
}
if ($stop) {
//Ok break here, the helper should be aware of this.
$newStack = array_pop($this->stack);
$newStack[0] = $index;
$newStack[2] = false; //No stop token from now on
array_push($this->stack, $newStack);
}
return $buffer;
}
/**
* Discard top tree
*
* @param mixed $context current context
*
* @return string
*/
public function discard()
{
$topTree = end($this->stack); //This method never pop a value from stack
list($index, $tree, $stop) = $topTree;
while (array_key_exists($index, $tree)) {
$current = $tree[$index];
$index++;
//if the section is exactly like waitFor
if (is_string($stop)
&& $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED
&& $current[Tokenizer::NAME] === $stop
) {
break;
}
}
if ($stop) {
//Ok break here, the helper should be aware of this.
$newStack = array_pop($this->stack);
$newStack[0] = $index;
$newStack[2] = false;
array_push($this->stack, $newStack);
}
return '';
}
/**
* Process section nodes
*
* @param Context $context current context
* @param array $current section node data
*
* @throws \RuntimeException
* @return string the result
*/
private function section(Context $context, $current)
{
$helpers = $this->handlebars->getHelpers();
$sectionName = $current[Tokenizer::NAME];
if ($helpers->has($sectionName)) {
if (isset($current[Tokenizer::END])) {
$source = substr(
$this->getSource(),
$current[Tokenizer::INDEX],
$current[Tokenizer::END] - $current[Tokenizer::INDEX]
);
} else {
$source = '';
}
$params = [
$this, //First argument is this template
$context, //Second is current context
$current[Tokenizer::ARGS], //Arguments
$source
];
$return = call_user_func_array($helpers->$sectionName, $params);
if ($return instanceof String) {
return $this->handlebars->loadString($return)->render($context);
} else {
return $return;
}
} elseif (trim($current[Tokenizer::ARGS]) == '') {
// fallback to mustache style each/with/for just if there is
// no argument at all.
try {
$sectionVar = $context->get($sectionName, true);
} catch (InvalidArgumentException $e) {
throw new RuntimeException(
$sectionName . ' is not registered as a helper'
);
}
$buffer = '';
if (is_array($sectionVar) || $sectionVar instanceof \Traversable) {
foreach ($sectionVar as $index => $d) {
$context->pushIndex($index);
$context->push($d);
$buffer .= $this->render($context);
$context->pop();
$context->popIndex();
}
} elseif (is_object($sectionVar)) {
//Act like with
$context->push($sectionVar);
$buffer = $this->render($context);
$context->pop();
} elseif ($sectionVar) {
$buffer = $this->render($context);
}
return $buffer;
} else {
throw new RuntimeException(
$sectionName . ' is not registered as a helper'
);
}
}
/**
* Process inverted section
*
* @param Context $context current context
* @param array $current section node data
*
* @return string the result
*/
private function inverted(Context $context, $current)
{
$sectionName = $current[Tokenizer::NAME];
$data = $context->get($sectionName);
if (!$data) {
return $this->render($context);
} else {
//No need to discard here, since it has no else
return '';
}
}
/**
* Process partial section
*
* @param Context $context current context
* @param array $current section node data
*
* @return string the result
*/
private function partial(Context $context, $current)
{
$partial = $this->handlebars->loadPartial($current[Tokenizer::NAME]);
if ($current[Tokenizer::ARGS]) {
$context = $context->get($current[Tokenizer::ARGS]);
}
return $partial->render($context);
}
/**
* Process partial section
*
* @param Context $context current context
* @param array $current section node data
* @param boolean $escaped escape result or not
*
* @return string the result
*/
private function variables(Context $context, $current, $escaped)
{
$name = $current[Tokenizer::NAME];
$value = $context->get($name);
// If @data variables are enabled, use the more complex algorithm for handling the the variables otherwise
// use the previous version.
if ($this->handlebars->isDataVariablesEnabled()) {
if (substr(trim($name), 0, 1) == '@') {
$variable = $context->getDataVariable($name);
if (is_bool($variable)) {
return $variable ? 'true' : 'false';
}
return $variable;
}
} else {
// If @data variables are not enabled, then revert back to legacy behavior
if ($name == '@index') {
return $context->lastIndex();
}
if ($name == '@key') {
return $context->lastKey();
}
}
if ($escaped) {
$args = $this->handlebars->getEscapeArgs();
array_unshift($args, $value);
$value = call_user_func_array(
$this->handlebars->getEscape(),
array_values($args)
);
}
return $value;
}
public function __clone()
{
return $this;
}
}

View File

@@ -0,0 +1,353 @@
<?php
/**
* Handlebars tokenizer (based on mustache)
*
* @category Xamin
* @package Handlebars
* @author Justin Hileman <dontknow@example.org>
* @author fzerorubigd <fzerorubigd@gmail.com>
* @author Behrooz Shabani <everplays@gmail.com>
* @author Mardix <https://github.com/mardix>
* @copyright 2012 (c) ParsPooyesh Co
* @copyright 2013 (c) Behrooz Shabani
* @copyright 2013 (c) Mardix
* @license MIT
* @link http://voodoophp.org/docs/handlebars
*/
namespace Handlebars;
class Tokenizer
{
// Finite state machine states
const IN_TEXT = 0;
const IN_TAG_TYPE = 1;
const IN_TAG = 2;
// Token types
const T_SECTION = '#';
const T_INVERTED = '^';
const T_END_SECTION = '/';
const T_COMMENT = '!';
// XXX: remove partials support from tokenizer and make it a helper?
const T_PARTIAL = '>';
const T_PARTIAL_2 = '<';
const T_DELIM_CHANGE = '=';
const T_ESCAPED = '_v';
const T_UNESCAPED = '{';
const T_UNESCAPED_2 = '&';
const T_TEXT = '_t';
// Valid token types
private $tagTypes = [
self::T_SECTION => true,
self::T_INVERTED => true,
self::T_END_SECTION => true,
self::T_COMMENT => true,
self::T_PARTIAL => true,
self::T_PARTIAL_2 => true,
self::T_DELIM_CHANGE => true,
self::T_ESCAPED => true,
self::T_UNESCAPED => true,
self::T_UNESCAPED_2 => true,
];
// Interpolated tags
private $interpolatedTags = [
self::T_ESCAPED => true,
self::T_UNESCAPED => true,
self::T_UNESCAPED_2 => true,
];
// Token properties
const TYPE = 'type';
const NAME = 'name';
const OTAG = 'otag';
const CTAG = 'ctag';
const INDEX = 'index';
const END = 'end';
const INDENT = 'indent';
const NODES = 'nodes';
const VALUE = 'value';
const ARGS = 'args';
protected $state;
protected $tagType;
protected $tag;
protected $buffer;
protected $tokens;
protected $seenTag;
protected $lineStart;
protected $otag;
protected $ctag;
/**
* Scan and tokenize template source.
*
* @param string $text Mustache template source to tokenize
* @param string $delimiters Optional, pass opening and closing delimiters
*
* @return array Set of Mustache tokens
*/
public function scan($text, $delimiters = null)
{
if ($text instanceof HandlebarsString) {
$text = $text->getString();
}
$this->reset();
if ($delimiters = trim($delimiters)) {
list($otag, $ctag) = explode(' ', $delimiters);
$this->otag = $otag;
$this->ctag = $ctag;
}
$openingTagLength = strlen($this->otag);
$closingTagLength = strlen($this->ctag);
$firstOpeningTagCharacter = $this->otag[0];
$firstClosingTagCharacter = $this->ctag[0];
$len = strlen($text);
for ($i = 0; $i < $len; $i++) {
$character = $text[$i];
switch ($this->state) {
case self::IN_TEXT:
if ($character === $firstOpeningTagCharacter && $this->tagChange($this->otag, $text, $i, $openingTagLength)
) {
$i--;
$this->flushBuffer();
$this->state = self::IN_TAG_TYPE;
} else {
if ($character == "\n") {
$this->filterLine();
} else {
$this->buffer .= $character;
}
}
break;
case self::IN_TAG_TYPE:
$i += $openingTagLength - 1;
if (isset($this->tagTypes[$text[$i + 1]])) {
$tag = $text[$i + 1];
$this->tagType = $tag;
} else {
$tag = null;
$this->tagType = self::T_ESCAPED;
}
if ($this->tagType === self::T_DELIM_CHANGE) {
$i = $this->changeDelimiters($text, $i);
$openingTagLength = strlen($this->otag);
$closingTagLength = strlen($this->ctag);
$firstOpeningTagCharacter = $this->otag[0];
$firstClosingTagCharacter = $this->ctag[0];
$this->state = self::IN_TEXT;
} else {
if ($tag !== null) {
$i++;
}
$this->state = self::IN_TAG;
}
$this->seenTag = $i;
break;
default:
if ($character === $firstClosingTagCharacter && $this->tagChange($this->ctag, $text, $i, $closingTagLength)) {
// Sections (Helpers) can accept parameters
// Same thing for Partials (little known fact)
if (in_array($this->tagType, [
self::T_SECTION,
self::T_PARTIAL,
self::T_PARTIAL_2]
)) {
$newBuffer = explode(' ', trim($this->buffer), 2);
$args = '';
if (count($newBuffer) == 2) {
$args = $newBuffer[1];
}
$this->buffer = $newBuffer[0];
}
$t = [
self::TYPE => $this->tagType,
self::NAME => trim($this->buffer),
self::OTAG => $this->otag,
self::CTAG => $this->ctag,
self::INDEX => ($this->tagType == self::T_END_SECTION) ?
$this->seenTag - $openingTagLength :
$i + strlen($this->ctag),
];
if (isset($args)) {
$t[self::ARGS] = $args;
}
$this->tokens[] = $t;
unset($t);
unset($args);
$this->buffer = '';
$i += strlen($this->ctag) - 1;
$this->state = self::IN_TEXT;
if ($this->tagType == self::T_UNESCAPED) {
if ($this->ctag == '}}') {
$i++;
} else {
// Clean up `{{{ tripleStache }}}` style tokens.
$lastIndex = count($this->tokens) - 1;
$lastName = $this->tokens[$lastIndex][self::NAME];
if (substr($lastName, -1) === '}') {
$this->tokens[$lastIndex][self::NAME] = trim(
substr($lastName, 0, -1)
);
}
}
}
} else {
$this->buffer .= $character;
}
break;
}
}
$this->filterLine(true);
return $this->tokens;
}
/**
* Helper function to reset tokenizer internal state.
*
* @return void
*/
protected function reset()
{
$this->state = self::IN_TEXT;
$this->tagType = null;
$this->tag = null;
$this->buffer = '';
$this->tokens = [];
$this->seenTag = false;
$this->lineStart = 0;
$this->otag = '{{';
$this->ctag = '}}';
}
/**
* Flush the current buffer to a token.
*
* @return void
*/
protected function flushBuffer()
{
if (!empty($this->buffer)) {
$this->tokens[] = [
self::TYPE => self::T_TEXT,
self::VALUE => $this->buffer
];
$this->buffer = '';
}
}
/**
* Test whether the current line is entirely made up of whitespace.
*
* @return boolean True if the current line is all whitespace
*/
protected function lineIsWhitespace()
{
$tokensCount = count($this->tokens);
for ($j = $this->lineStart; $j < $tokensCount; $j++) {
$token = $this->tokens[$j];
if (isset($this->tagTypes[$token[self::TYPE]])) {
if (isset($this->interpolatedTags[$token[self::TYPE]])) {
return false;
}
} elseif ($token[self::TYPE] == self::T_TEXT) {
if (preg_match('/\S/', $token[self::VALUE])) {
return false;
}
}
}
return true;
}
/**
* Filter out whitespace-only lines and store indent levels for partials.
*
* @param bool $noNewLine Suppress the newline? (default: false)
*
* @return void
*/
protected function filterLine($noNewLine = false)
{
$this->flushBuffer();
if ($this->seenTag && $this->lineIsWhitespace()) {
$tokensCount = count($this->tokens);
for ($j = $this->lineStart; $j < $tokensCount; $j++) {
if ($this->tokens[$j][self::TYPE] == self::T_TEXT) {
if (isset($this->tokens[$j + 1])
&& $this->tokens[$j + 1][self::TYPE] == self::T_PARTIAL
) {
$this->tokens[$j + 1][self::INDENT]
= $this->tokens[$j][self::VALUE];
}
$this->tokens[$j] = null;
}
}
} elseif (!$noNewLine) {
$this->tokens[] = [self::TYPE => self::T_TEXT, self::VALUE => "\n"];
}
$this->seenTag = false;
$this->lineStart = count($this->tokens);
}
/**
* Change the current Mustache delimiters. Set new `otag` and `ctag` values.
*
* @param string $text Mustache template source
* @param int $index Current tokenizer index
*
* @return int New index value
*/
protected function changeDelimiters($text, $index)
{
$startIndex = strpos($text, '=', $index) + 1;
$close = '=' . $this->ctag;
$closeIndex = strpos($text, $close, $index);
list($otag, $ctag) = explode(
' ',
trim(substr($text, $startIndex, $closeIndex - $startIndex))
);
$this->otag = $otag;
$this->ctag = $ctag;
return $closeIndex + strlen($close) - 1;
}
/**
* Test whether it's time to change tags.
*
* @param string $tag Current tag name
* @param string $text Mustache template source
* @param int $index Current tokenizer index
* @param int $tagLength Length of the opening/closing tag string
*
* @return boolean True if this is a closing section tag
*/
protected function tagChange($tag, $text, $index, $tagLength)
{
return substr($text, $index, $tagLength) === $tag;
}
}