* @author Behrooz Shabani * @author Chris Gray * @author Ulrik Lystbaek * @author Dmitriy Simushev * @copyright 2010-2012 (c) Justin Hileman * @copyright 2012 (c) ParsPooyesh Co * @copyright 2013 (c) Behrooz Shabani * @copyright 2013 (c) f0ruD A * @license MIT * @version GIT: $Id$ * @link http://xamin.ir */ namespace Handlebars; /** * Handlebars context * Context for a template * * @category Xamin * @package Handlebars * @author fzerorubigd * @author Behrooz Shabani * @copyright 2010-2012 (c) Justin Hileman * @copyright 2012 (c) ParsPooyesh Co * @license MIT * @version Release: @package_version@ * @link http://xamin.ir */ class Context { /** * List of charcters that cannot be used in identifiers. */ const NOT_VALID_NAME_CHARS = '!"#%&\'()*+,./;<=>@[\\]^`{|}~'; /** * List of characters that cannot be used in identifiers in segment-literal * notation. */ const NOT_VALID_SEGMENT_NAME_CHARS = "]"; /** * @var array stack for context only top stack is available */ protected $stack = array(); /** * @var array index stack for sections */ protected $index = array(); /** * @var array key stack for objects */ protected $key = array(); /** * @var array Special variables stack for sections. Each stack element can * contain elements with "@index", "@key", "@first" and "@last" keys. */ protected $specialVariables = array(); /** * Mustache rendering Context constructor. * * @param mixed $context Default rendering context (default: null) */ public function __construct($context = null) { if ($context !== null) { $this->stack = array($context); } } /** * 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 array of special variables to stack. * * @param array $variables An associative array of special variables. * * @return void * * @see \Handlebars\Context::$specialVariables */ public function pushSpecialVariables($variables) { array_push($this->specialVariables, $variables); } /** * 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 special variables set from the stack. * * @return array Associative array of special variables. * * @see \Handlebars\Context::$specialVariables */ public function popSpecialVariables() { return array_pop($this->specialVariables); } /** * Get the last Context frame. * * @return mixed Last Context frame (object or array) */ public function last() { return end($this->stack); } /** * Get the last special variables set from the stack. * * @return array Associative array of special variables. * * @see \Handlebars\Context::$specialVariables */ public function lastSpecialVariables() { return end($this->specialVariables); } /** * 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 available from current context * Supported types : * variable , ../variable , variable.variable , variable.[variable] , . * * @param string $variableName variable 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 * @throws \RuntimeException if supplied argument is a malformed quoted string * @throws \InvalidArgumentException if variable name is invalid * @return mixed */ public function get($variableName, $strict = false) { if ($variableName instanceof \Handlebars\String) { return (string)$variableName; } $variableName = trim($variableName); $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; } elseif ($variableName[0] == '@') { $specialVariables = $this->lastSpecialVariables(); if (isset($specialVariables[$variableName])) { return $specialVariables[$variableName]; } elseif ($strict) { throw new \InvalidArgumentException( 'can not find variable in context' ); } else { return ''; } } else { $chunks = $this->_splitVariableName($variableName); foreach ($chunks as $chunk) { if (is_string($current) and $current == '') { return $current; } $current = $this->_findVariableInContext($current, $chunk, $strict); } } return $current; } /** * 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]) || array_key_exists($inside, $variable)) { return $variable[$inside]; } elseif ($inside == "length") { return count($variable); } } elseif (is_object($variable)) { if (isset($variable->$inside)) { return $variable->$inside; } elseif (is_callable(array($variable, $inside))) { return call_user_func(array($variable, $inside)); } } if ($strict) { throw new \InvalidArgumentException('can not find variable in context'); } return $value; } /** * Splits variable name to chunks. * * @param string $variableName Fully qualified name of a variable. * * @throws \InvalidArgumentException if variable name is invalid. * @return array */ private function _splitVariableName($variableName) { $bad_chars = preg_quote(self::NOT_VALID_NAME_CHARS, '/'); $bad_seg_chars = preg_quote(self::NOT_VALID_SEGMENT_NAME_CHARS, '/'); $name_pattern = "(?:[^" . $bad_chars . "\s]+)|(?:\[[^" . $bad_seg_chars . "]+\])"; $check_pattern = "/^((" . $name_pattern . ")\.)*(" . $name_pattern . ")\.?$/"; $get_pattern = "/(?:" . $name_pattern . ")/"; if (!preg_match($check_pattern, $variableName)) { throw new \InvalidArgumentException('variable name is invalid'); } preg_match_all($get_pattern, $variableName, $matches); $chunks = array(); foreach ($matches[0] as $chunk) { // Remove wrapper braces if needed if ($chunk[0] == '[') { $chunk = substr($chunk, 1, -1); } $chunks[] = $chunk; } return $chunks; } }