1*04fd306cSNickeau<?php 2*04fd306cSNickeau/** 3*04fd306cSNickeau * Handlebars context 4*04fd306cSNickeau * Context for a template 5*04fd306cSNickeau * 6*04fd306cSNickeau * @category Xamin 7*04fd306cSNickeau * @package Handlebars 8*04fd306cSNickeau * @author fzerorubigd <fzerorubigd@gmail.com> 9*04fd306cSNickeau * @author Behrooz Shabani <everplays@gmail.com> 10*04fd306cSNickeau * @author Mardix <https://github.com/mardix> 11*04fd306cSNickeau * @copyright 2012 (c) ParsPooyesh Co 12*04fd306cSNickeau * @copyright 2013 (c) Behrooz Shabani 13*04fd306cSNickeau * @copyright 2013 (c) Mardix 14*04fd306cSNickeau * @license MIT 15*04fd306cSNickeau * @link http://voodoophp.org/docs/handlebars 16*04fd306cSNickeau */ 17*04fd306cSNickeau 18*04fd306cSNickeaunamespace Handlebars; 19*04fd306cSNickeau 20*04fd306cSNickeauuse InvalidArgumentException; 21*04fd306cSNickeauuse LogicException; 22*04fd306cSNickeau 23*04fd306cSNickeauclass Context 24*04fd306cSNickeau{ 25*04fd306cSNickeau const DATA_KEY = 'key'; 26*04fd306cSNickeau const DATA_INDEX = 'index'; 27*04fd306cSNickeau const DATA_FIRST = 'first'; 28*04fd306cSNickeau const DATA_LAST = 'last'; 29*04fd306cSNickeau 30*04fd306cSNickeau /** 31*04fd306cSNickeau * @var array stack for context only top stack is available 32*04fd306cSNickeau */ 33*04fd306cSNickeau protected $stack = []; 34*04fd306cSNickeau 35*04fd306cSNickeau /** 36*04fd306cSNickeau * @var array index stack for sections 37*04fd306cSNickeau */ 38*04fd306cSNickeau protected $index = []; 39*04fd306cSNickeau 40*04fd306cSNickeau /** 41*04fd306cSNickeau * @var array dataStack stack for data within sections 42*04fd306cSNickeau */ 43*04fd306cSNickeau protected $dataStack = []; 44*04fd306cSNickeau 45*04fd306cSNickeau /** 46*04fd306cSNickeau * @var array key stack for objects 47*04fd306cSNickeau */ 48*04fd306cSNickeau protected $key = []; 49*04fd306cSNickeau 50*04fd306cSNickeau /** 51*04fd306cSNickeau * @var bool enableDataVariables true if @data variables should be used. 52*04fd306cSNickeau */ 53*04fd306cSNickeau protected $enableDataVariables = false; 54*04fd306cSNickeau 55*04fd306cSNickeau /** 56*04fd306cSNickeau * Mustache rendering Context constructor. 57*04fd306cSNickeau * 58*04fd306cSNickeau * @param mixed $context Default rendering context (default: null) 59*04fd306cSNickeau * @param array $options Options for the context. It may contain the following: (default: empty array) 60*04fd306cSNickeau * enableDataVariables => Boolean, Enables @data variables (default: false) 61*04fd306cSNickeau * 62*04fd306cSNickeau * @throws InvalidArgumentException when calling this method when enableDataVariables is not a boolean. 63*04fd306cSNickeau */ 64*04fd306cSNickeau public function __construct($context = null, $options = []) 65*04fd306cSNickeau { 66*04fd306cSNickeau if ($context !== null) { 67*04fd306cSNickeau $this->stack = [$context]; 68*04fd306cSNickeau } 69*04fd306cSNickeau 70*04fd306cSNickeau if (isset($options[Handlebars::OPTION_ENABLE_DATA_VARIABLES])) { 71*04fd306cSNickeau if (!is_bool($options[Handlebars::OPTION_ENABLE_DATA_VARIABLES])) { 72*04fd306cSNickeau throw new InvalidArgumentException( 73*04fd306cSNickeau 'Context Constructor "' . Handlebars::OPTION_ENABLE_DATA_VARIABLES . '" option must be a boolean' 74*04fd306cSNickeau ); 75*04fd306cSNickeau } 76*04fd306cSNickeau $this->enableDataVariables = $options[Handlebars::OPTION_ENABLE_DATA_VARIABLES]; 77*04fd306cSNickeau } 78*04fd306cSNickeau } 79*04fd306cSNickeau 80*04fd306cSNickeau /** 81*04fd306cSNickeau * Push a new Context frame onto the stack. 82*04fd306cSNickeau * 83*04fd306cSNickeau * @param mixed $value Object or array to use for context 84*04fd306cSNickeau * 85*04fd306cSNickeau * @return void 86*04fd306cSNickeau */ 87*04fd306cSNickeau public function push($value) 88*04fd306cSNickeau { 89*04fd306cSNickeau array_push($this->stack, $value); 90*04fd306cSNickeau } 91*04fd306cSNickeau 92*04fd306cSNickeau /** 93*04fd306cSNickeau * Push an Index onto the index stack 94*04fd306cSNickeau * 95*04fd306cSNickeau * @param integer $index Index of the current section item. 96*04fd306cSNickeau * 97*04fd306cSNickeau * @return void 98*04fd306cSNickeau */ 99*04fd306cSNickeau public function pushIndex($index) 100*04fd306cSNickeau { 101*04fd306cSNickeau array_push($this->index, $index); 102*04fd306cSNickeau } 103*04fd306cSNickeau 104*04fd306cSNickeau /** 105*04fd306cSNickeau * Pushes data variables onto the stack. This is used to support @data variables. 106*04fd306cSNickeau * @param array $data Associative array where key is the name of the @data variable and value is the value. 107*04fd306cSNickeau * @throws LogicException when calling this method without having enableDataVariables. 108*04fd306cSNickeau */ 109*04fd306cSNickeau public function pushData($data) 110*04fd306cSNickeau { 111*04fd306cSNickeau if (!$this->enableDataVariables) { 112*04fd306cSNickeau throw new LogicException('Data variables are not supported due to the enableDataVariables configuration. Remove the call to data variables or change the setting.'); 113*04fd306cSNickeau } 114*04fd306cSNickeau array_push($this->dataStack, $data); 115*04fd306cSNickeau } 116*04fd306cSNickeau 117*04fd306cSNickeau /** 118*04fd306cSNickeau * Push a Key onto the key stack 119*04fd306cSNickeau * 120*04fd306cSNickeau * @param string $key Key of the current object property. 121*04fd306cSNickeau * 122*04fd306cSNickeau * @return void 123*04fd306cSNickeau */ 124*04fd306cSNickeau public function pushKey($key) 125*04fd306cSNickeau { 126*04fd306cSNickeau array_push($this->key, $key); 127*04fd306cSNickeau } 128*04fd306cSNickeau 129*04fd306cSNickeau /** 130*04fd306cSNickeau * Pop the last Context frame from the stack. 131*04fd306cSNickeau * 132*04fd306cSNickeau * @return mixed Last Context frame (object or array) 133*04fd306cSNickeau */ 134*04fd306cSNickeau public function pop() 135*04fd306cSNickeau { 136*04fd306cSNickeau return array_pop($this->stack); 137*04fd306cSNickeau } 138*04fd306cSNickeau 139*04fd306cSNickeau /** 140*04fd306cSNickeau * Pop the last index from the stack. 141*04fd306cSNickeau * 142*04fd306cSNickeau * @return int Last index 143*04fd306cSNickeau */ 144*04fd306cSNickeau public function popIndex() 145*04fd306cSNickeau { 146*04fd306cSNickeau return array_pop($this->index); 147*04fd306cSNickeau } 148*04fd306cSNickeau 149*04fd306cSNickeau /** 150*04fd306cSNickeau * Pop the last section data from the stack. 151*04fd306cSNickeau * 152*04fd306cSNickeau * @return array Last data 153*04fd306cSNickeau * @throws LogicException when calling this method without having enableDataVariables. 154*04fd306cSNickeau */ 155*04fd306cSNickeau public function popData() 156*04fd306cSNickeau { 157*04fd306cSNickeau if (!$this->enableDataVariables) { 158*04fd306cSNickeau throw new LogicException('Data variables are not supported due to the enableDataVariables configuration. Remove the call to data variables or change the setting.'); 159*04fd306cSNickeau } 160*04fd306cSNickeau return array_pop($this->dataStack); 161*04fd306cSNickeau } 162*04fd306cSNickeau 163*04fd306cSNickeau /** 164*04fd306cSNickeau * Pop the last key from the stack. 165*04fd306cSNickeau * 166*04fd306cSNickeau * @return string Last key 167*04fd306cSNickeau */ 168*04fd306cSNickeau public function popKey() 169*04fd306cSNickeau { 170*04fd306cSNickeau return array_pop($this->key); 171*04fd306cSNickeau } 172*04fd306cSNickeau 173*04fd306cSNickeau /** 174*04fd306cSNickeau * Get the last Context frame. 175*04fd306cSNickeau * 176*04fd306cSNickeau * @return mixed Last Context frame (object or array) 177*04fd306cSNickeau */ 178*04fd306cSNickeau public function last() 179*04fd306cSNickeau { 180*04fd306cSNickeau return end($this->stack); 181*04fd306cSNickeau } 182*04fd306cSNickeau 183*04fd306cSNickeau /** 184*04fd306cSNickeau * Get the index of current section item. 185*04fd306cSNickeau * 186*04fd306cSNickeau * @return mixed Last index 187*04fd306cSNickeau */ 188*04fd306cSNickeau public function lastIndex() 189*04fd306cSNickeau { 190*04fd306cSNickeau return end($this->index); 191*04fd306cSNickeau } 192*04fd306cSNickeau 193*04fd306cSNickeau /** 194*04fd306cSNickeau * Get the key of current object property. 195*04fd306cSNickeau * 196*04fd306cSNickeau * @return mixed Last key 197*04fd306cSNickeau */ 198*04fd306cSNickeau public function lastKey() 199*04fd306cSNickeau { 200*04fd306cSNickeau return end($this->key); 201*04fd306cSNickeau } 202*04fd306cSNickeau 203*04fd306cSNickeau /** 204*04fd306cSNickeau * Change the current context to one of current context members 205*04fd306cSNickeau * 206*04fd306cSNickeau * @param string $variableName name of variable or a callable on current context 207*04fd306cSNickeau * 208*04fd306cSNickeau * @return mixed actual value 209*04fd306cSNickeau */ 210*04fd306cSNickeau public function with($variableName) 211*04fd306cSNickeau { 212*04fd306cSNickeau $value = $this->get($variableName); 213*04fd306cSNickeau $this->push($value); 214*04fd306cSNickeau 215*04fd306cSNickeau return $value; 216*04fd306cSNickeau } 217*04fd306cSNickeau 218*04fd306cSNickeau /** 219*04fd306cSNickeau * Get a avariable from current context 220*04fd306cSNickeau * Supported types : 221*04fd306cSNickeau * variable , ../variable , variable.variable , . 222*04fd306cSNickeau * 223*04fd306cSNickeau * @param string $variableName variavle name to get from current context 224*04fd306cSNickeau * @param boolean $strict strict search? if not found then throw exception 225*04fd306cSNickeau * 226*04fd306cSNickeau * @throws InvalidArgumentException in strict mode and variable not found 227*04fd306cSNickeau * @return mixed 228*04fd306cSNickeau */ 229*04fd306cSNickeau public function get($variableName, $strict = false) 230*04fd306cSNickeau { 231*04fd306cSNickeau //Need to clean up 232*04fd306cSNickeau $variableName = trim($variableName); 233*04fd306cSNickeau 234*04fd306cSNickeau //Handle data variables (@index, @first, @last, etc) 235*04fd306cSNickeau if ($this->enableDataVariables && substr($variableName, 0, 1) == '@') { 236*04fd306cSNickeau return $this->getDataVariable($variableName, $strict); 237*04fd306cSNickeau } 238*04fd306cSNickeau 239*04fd306cSNickeau $level = 0; 240*04fd306cSNickeau while (substr($variableName, 0, 3) == '../') { 241*04fd306cSNickeau $variableName = trim(substr($variableName, 3)); 242*04fd306cSNickeau $level++; 243*04fd306cSNickeau } 244*04fd306cSNickeau if (count($this->stack) < $level) { 245*04fd306cSNickeau if ($strict) { 246*04fd306cSNickeau throw new InvalidArgumentException( 247*04fd306cSNickeau 'can not find variable in context' 248*04fd306cSNickeau ); 249*04fd306cSNickeau } 250*04fd306cSNickeau 251*04fd306cSNickeau return ''; 252*04fd306cSNickeau } 253*04fd306cSNickeau end($this->stack); 254*04fd306cSNickeau while ($level) { 255*04fd306cSNickeau prev($this->stack); 256*04fd306cSNickeau $level--; 257*04fd306cSNickeau } 258*04fd306cSNickeau $current = current($this->stack); 259*04fd306cSNickeau if (!$variableName) { 260*04fd306cSNickeau if ($strict) { 261*04fd306cSNickeau throw new InvalidArgumentException( 262*04fd306cSNickeau 'can not find variable in context' 263*04fd306cSNickeau ); 264*04fd306cSNickeau } 265*04fd306cSNickeau return ''; 266*04fd306cSNickeau } elseif ($variableName == '.' || $variableName == 'this') { 267*04fd306cSNickeau return $current; 268*04fd306cSNickeau } else { 269*04fd306cSNickeau $chunks = explode('.', $variableName); 270*04fd306cSNickeau foreach ($chunks as $chunk) { 271*04fd306cSNickeau if (is_string($current) and $current == '') { 272*04fd306cSNickeau return $current; 273*04fd306cSNickeau } 274*04fd306cSNickeau $current = $this->findVariableInContext($current, $chunk, $strict); 275*04fd306cSNickeau } 276*04fd306cSNickeau } 277*04fd306cSNickeau return $current; 278*04fd306cSNickeau } 279*04fd306cSNickeau 280*04fd306cSNickeau /** 281*04fd306cSNickeau * Given a data variable, retrieves the value associated. 282*04fd306cSNickeau * 283*04fd306cSNickeau * @param $variableName 284*04fd306cSNickeau * @param bool $strict 285*04fd306cSNickeau * @return mixed 286*04fd306cSNickeau * @throws LogicException when calling this method without having enableDataVariables. 287*04fd306cSNickeau */ 288*04fd306cSNickeau public function getDataVariable($variableName, $strict = false) 289*04fd306cSNickeau { 290*04fd306cSNickeau if (!$this->enableDataVariables) { 291*04fd306cSNickeau throw new LogicException('Data variables are not supported due to the enableDataVariables configuration. Remove the call to data variables or change the setting.'); 292*04fd306cSNickeau } 293*04fd306cSNickeau 294*04fd306cSNickeau $variableName = trim($variableName); 295*04fd306cSNickeau 296*04fd306cSNickeau // make sure we get an at-symbol prefix 297*04fd306cSNickeau if (substr($variableName, 0, 1) != '@') { 298*04fd306cSNickeau if ($strict) { 299*04fd306cSNickeau throw new InvalidArgumentException( 300*04fd306cSNickeau 'Can not find variable in context' 301*04fd306cSNickeau ); 302*04fd306cSNickeau } 303*04fd306cSNickeau return ''; 304*04fd306cSNickeau } 305*04fd306cSNickeau 306*04fd306cSNickeau // Remove the at-symbol prefix 307*04fd306cSNickeau $variableName = substr($variableName, 1); 308*04fd306cSNickeau 309*04fd306cSNickeau // determine the level of relative @data variables 310*04fd306cSNickeau $level = 0; 311*04fd306cSNickeau while (substr($variableName, 0, 3) == '../') { 312*04fd306cSNickeau $variableName = trim(substr($variableName, 3)); 313*04fd306cSNickeau $level++; 314*04fd306cSNickeau } 315*04fd306cSNickeau 316*04fd306cSNickeau // make sure the stack actually has the specified number of levels 317*04fd306cSNickeau if (count($this->dataStack) < $level) { 318*04fd306cSNickeau if ($strict) { 319*04fd306cSNickeau throw new InvalidArgumentException( 320*04fd306cSNickeau 'Can not find variable in context' 321*04fd306cSNickeau ); 322*04fd306cSNickeau } 323*04fd306cSNickeau 324*04fd306cSNickeau return ''; 325*04fd306cSNickeau } 326*04fd306cSNickeau 327*04fd306cSNickeau // going from the top of the stack to the bottom, traverse the number of levels specified 328*04fd306cSNickeau end($this->dataStack); 329*04fd306cSNickeau while ($level) { 330*04fd306cSNickeau prev($this->dataStack); 331*04fd306cSNickeau $level--; 332*04fd306cSNickeau } 333*04fd306cSNickeau 334*04fd306cSNickeau /** @var array $current */ 335*04fd306cSNickeau $current = current($this->dataStack); 336*04fd306cSNickeau 337*04fd306cSNickeau if (!array_key_exists($variableName, $current)) { 338*04fd306cSNickeau if ($strict) { 339*04fd306cSNickeau throw new InvalidArgumentException( 340*04fd306cSNickeau 'Can not find variable in context' 341*04fd306cSNickeau ); 342*04fd306cSNickeau } 343*04fd306cSNickeau 344*04fd306cSNickeau return ''; 345*04fd306cSNickeau } 346*04fd306cSNickeau 347*04fd306cSNickeau return $current[$variableName]; 348*04fd306cSNickeau } 349*04fd306cSNickeau 350*04fd306cSNickeau /** 351*04fd306cSNickeau * Check if $variable->$inside is available 352*04fd306cSNickeau * 353*04fd306cSNickeau * @param mixed $variable variable to check 354*04fd306cSNickeau * @param string $inside property/method to check 355*04fd306cSNickeau * @param boolean $strict strict search? if not found then throw exception 356*04fd306cSNickeau * 357*04fd306cSNickeau * @throws \InvalidArgumentException in strict mode and variable not found 358*04fd306cSNickeau * @return boolean true if exist 359*04fd306cSNickeau */ 360*04fd306cSNickeau private function findVariableInContext($variable, $inside, $strict = false) 361*04fd306cSNickeau { 362*04fd306cSNickeau $value = ''; 363*04fd306cSNickeau if (($inside !== '0' && empty($inside)) || ($inside == 'this')) { 364*04fd306cSNickeau return $variable; 365*04fd306cSNickeau } elseif (is_array($variable)) { 366*04fd306cSNickeau if (isset($variable[$inside])) { 367*04fd306cSNickeau $value = $variable[$inside]; 368*04fd306cSNickeau } 369*04fd306cSNickeau } elseif (is_object($variable)) { 370*04fd306cSNickeau if (isset($variable->$inside)) { 371*04fd306cSNickeau $value = $variable->$inside; 372*04fd306cSNickeau } elseif (is_callable(array($variable, $inside))) { 373*04fd306cSNickeau $value = call_user_func(array($variable, $inside)); 374*04fd306cSNickeau } 375*04fd306cSNickeau } elseif ($inside === '.') { 376*04fd306cSNickeau $value = $variable; 377*04fd306cSNickeau } elseif ($strict) { 378*04fd306cSNickeau throw new InvalidArgumentException('can not find variable in context'); 379*04fd306cSNickeau } 380*04fd306cSNickeau return $value; 381*04fd306cSNickeau } 382*04fd306cSNickeau} 383