1*04fd306cSNickeau<?php 2*04fd306cSNickeau/** 3*04fd306cSNickeau * Handlebars base template 4*04fd306cSNickeau * contain some utility method to get context and helpers 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 RuntimeException; 22*04fd306cSNickeau 23*04fd306cSNickeauclass Template 24*04fd306cSNickeau{ 25*04fd306cSNickeau /** 26*04fd306cSNickeau * @var Handlebars 27*04fd306cSNickeau */ 28*04fd306cSNickeau protected $handlebars; 29*04fd306cSNickeau 30*04fd306cSNickeau protected $tree = []; 31*04fd306cSNickeau 32*04fd306cSNickeau protected $source = ''; 33*04fd306cSNickeau 34*04fd306cSNickeau /** 35*04fd306cSNickeau * @var array Run stack 36*04fd306cSNickeau */ 37*04fd306cSNickeau private $stack = []; 38*04fd306cSNickeau 39*04fd306cSNickeau /** 40*04fd306cSNickeau * Handlebars template constructor 41*04fd306cSNickeau * 42*04fd306cSNickeau * @param Handlebars $engine handlebar engine 43*04fd306cSNickeau * @param array $tree Parsed tree 44*04fd306cSNickeau * @param string $source Handlebars source 45*04fd306cSNickeau */ 46*04fd306cSNickeau public function __construct(Handlebars $engine, $tree, $source) 47*04fd306cSNickeau { 48*04fd306cSNickeau $this->handlebars = $engine; 49*04fd306cSNickeau $this->tree = $tree; 50*04fd306cSNickeau $this->source = $source; 51*04fd306cSNickeau array_push($this->stack, [0, $this->getTree(), false]); 52*04fd306cSNickeau } 53*04fd306cSNickeau 54*04fd306cSNickeau /** 55*04fd306cSNickeau * Get current tree 56*04fd306cSNickeau * 57*04fd306cSNickeau * @return array 58*04fd306cSNickeau */ 59*04fd306cSNickeau public function getTree() 60*04fd306cSNickeau { 61*04fd306cSNickeau return $this->tree; 62*04fd306cSNickeau } 63*04fd306cSNickeau 64*04fd306cSNickeau /** 65*04fd306cSNickeau * Get current source 66*04fd306cSNickeau * 67*04fd306cSNickeau * @return string 68*04fd306cSNickeau */ 69*04fd306cSNickeau public function getSource() 70*04fd306cSNickeau { 71*04fd306cSNickeau return $this->source; 72*04fd306cSNickeau } 73*04fd306cSNickeau 74*04fd306cSNickeau /** 75*04fd306cSNickeau * Get current engine associated with this object 76*04fd306cSNickeau * 77*04fd306cSNickeau * @return Handlebars 78*04fd306cSNickeau */ 79*04fd306cSNickeau public function getEngine() 80*04fd306cSNickeau { 81*04fd306cSNickeau return $this->handlebars; 82*04fd306cSNickeau } 83*04fd306cSNickeau 84*04fd306cSNickeau /** 85*04fd306cSNickeau * set stop token for render and discard method 86*04fd306cSNickeau * 87*04fd306cSNickeau * @param string|false $token token to set as stop token or false to remove 88*04fd306cSNickeau * 89*04fd306cSNickeau * @return void 90*04fd306cSNickeau */ 91*04fd306cSNickeau public function setStopToken($token) 92*04fd306cSNickeau { 93*04fd306cSNickeau $topStack = array_pop($this->stack); 94*04fd306cSNickeau $topStack[2] = $token; 95*04fd306cSNickeau array_push($this->stack, $topStack); 96*04fd306cSNickeau } 97*04fd306cSNickeau 98*04fd306cSNickeau /** 99*04fd306cSNickeau * get current stop token 100*04fd306cSNickeau * 101*04fd306cSNickeau * @return string|false 102*04fd306cSNickeau */ 103*04fd306cSNickeau public function getStopToken() 104*04fd306cSNickeau { 105*04fd306cSNickeau return end($this->stack)[2]; 106*04fd306cSNickeau } 107*04fd306cSNickeau 108*04fd306cSNickeau /** 109*04fd306cSNickeau * Render top tree 110*04fd306cSNickeau * 111*04fd306cSNickeau * @param mixed $context current context 112*04fd306cSNickeau * 113*04fd306cSNickeau * @throws \RuntimeException 114*04fd306cSNickeau * @return string 115*04fd306cSNickeau */ 116*04fd306cSNickeau public function render($context) 117*04fd306cSNickeau { 118*04fd306cSNickeau if (!$context instanceof Context) { 119*04fd306cSNickeau $context = new Context($context, [ 120*04fd306cSNickeau 'enableDataVariables' => $this->handlebars->isDataVariablesEnabled(), 121*04fd306cSNickeau ]); 122*04fd306cSNickeau } 123*04fd306cSNickeau $topTree = end($this->stack); // never pop a value from stack 124*04fd306cSNickeau list($index, $tree, $stop) = $topTree; 125*04fd306cSNickeau 126*04fd306cSNickeau $buffer = ''; 127*04fd306cSNickeau while (array_key_exists($index, $tree)) { 128*04fd306cSNickeau $current = $tree[$index]; 129*04fd306cSNickeau $index++; 130*04fd306cSNickeau //if the section is exactly like waitFor 131*04fd306cSNickeau if (is_string($stop) 132*04fd306cSNickeau && $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED 133*04fd306cSNickeau && $current[Tokenizer::NAME] === $stop 134*04fd306cSNickeau ) { 135*04fd306cSNickeau break; 136*04fd306cSNickeau } 137*04fd306cSNickeau switch ($current[Tokenizer::TYPE]) { 138*04fd306cSNickeau case Tokenizer::T_SECTION : 139*04fd306cSNickeau $newStack = isset($current[Tokenizer::NODES]) 140*04fd306cSNickeau ? $current[Tokenizer::NODES] : []; 141*04fd306cSNickeau array_push($this->stack, [0, $newStack, false]); 142*04fd306cSNickeau $buffer .= $this->section($context, $current); 143*04fd306cSNickeau array_pop($this->stack); 144*04fd306cSNickeau break; 145*04fd306cSNickeau case Tokenizer::T_INVERTED : 146*04fd306cSNickeau $newStack = isset($current[Tokenizer::NODES]) ? 147*04fd306cSNickeau $current[Tokenizer::NODES] : []; 148*04fd306cSNickeau array_push($this->stack, [0, $newStack, false]); 149*04fd306cSNickeau $buffer .= $this->inverted($context, $current); 150*04fd306cSNickeau array_pop($this->stack); 151*04fd306cSNickeau break; 152*04fd306cSNickeau case Tokenizer::T_COMMENT : 153*04fd306cSNickeau $buffer .= ''; 154*04fd306cSNickeau break; 155*04fd306cSNickeau case Tokenizer::T_PARTIAL: 156*04fd306cSNickeau case Tokenizer::T_PARTIAL_2: 157*04fd306cSNickeau $buffer .= $this->partial($context, $current); 158*04fd306cSNickeau break; 159*04fd306cSNickeau case Tokenizer::T_UNESCAPED: 160*04fd306cSNickeau case Tokenizer::T_UNESCAPED_2: 161*04fd306cSNickeau $buffer .= $this->variables($context, $current, false); 162*04fd306cSNickeau break; 163*04fd306cSNickeau case Tokenizer::T_ESCAPED: 164*04fd306cSNickeau $buffer .= $this->variables($context, $current, true); 165*04fd306cSNickeau break; 166*04fd306cSNickeau case Tokenizer::T_TEXT: 167*04fd306cSNickeau $buffer .= $current[Tokenizer::VALUE]; 168*04fd306cSNickeau break; 169*04fd306cSNickeau default: 170*04fd306cSNickeau throw new RuntimeException( 171*04fd306cSNickeau 'Invalid node type : ' . json_encode($current) 172*04fd306cSNickeau ); 173*04fd306cSNickeau } 174*04fd306cSNickeau } 175*04fd306cSNickeau if ($stop) { 176*04fd306cSNickeau //Ok break here, the helper should be aware of this. 177*04fd306cSNickeau $newStack = array_pop($this->stack); 178*04fd306cSNickeau $newStack[0] = $index; 179*04fd306cSNickeau $newStack[2] = false; //No stop token from now on 180*04fd306cSNickeau array_push($this->stack, $newStack); 181*04fd306cSNickeau } 182*04fd306cSNickeau 183*04fd306cSNickeau return $buffer; 184*04fd306cSNickeau } 185*04fd306cSNickeau 186*04fd306cSNickeau /** 187*04fd306cSNickeau * Discard top tree 188*04fd306cSNickeau * 189*04fd306cSNickeau * @return string 190*04fd306cSNickeau */ 191*04fd306cSNickeau public function discard() 192*04fd306cSNickeau { 193*04fd306cSNickeau $topTree = end($this->stack); //This method never pop a value from stack 194*04fd306cSNickeau list($index, $tree, $stop) = $topTree; 195*04fd306cSNickeau while (array_key_exists($index, $tree)) { 196*04fd306cSNickeau $current = $tree[$index]; 197*04fd306cSNickeau $index++; 198*04fd306cSNickeau //if the section is exactly like waitFor 199*04fd306cSNickeau if (is_string($stop) 200*04fd306cSNickeau && $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED 201*04fd306cSNickeau && $current[Tokenizer::NAME] === $stop 202*04fd306cSNickeau ) { 203*04fd306cSNickeau break; 204*04fd306cSNickeau } 205*04fd306cSNickeau } 206*04fd306cSNickeau if ($stop) { 207*04fd306cSNickeau //Ok break here, the helper should be aware of this. 208*04fd306cSNickeau $newStack = array_pop($this->stack); 209*04fd306cSNickeau $newStack[0] = $index; 210*04fd306cSNickeau $newStack[2] = false; 211*04fd306cSNickeau array_push($this->stack, $newStack); 212*04fd306cSNickeau } 213*04fd306cSNickeau 214*04fd306cSNickeau return ''; 215*04fd306cSNickeau } 216*04fd306cSNickeau 217*04fd306cSNickeau /** 218*04fd306cSNickeau * Process section nodes 219*04fd306cSNickeau * 220*04fd306cSNickeau * @param Context $context current context 221*04fd306cSNickeau * @param array $current section node data 222*04fd306cSNickeau * 223*04fd306cSNickeau * @throws \RuntimeException 224*04fd306cSNickeau * @return string the result 225*04fd306cSNickeau */ 226*04fd306cSNickeau private function section(Context $context, $current) 227*04fd306cSNickeau { 228*04fd306cSNickeau $helpers = $this->handlebars->getHelpers(); 229*04fd306cSNickeau $sectionName = $current[Tokenizer::NAME]; 230*04fd306cSNickeau if ($helpers->has($sectionName)) { 231*04fd306cSNickeau if (isset($current[Tokenizer::END])) { 232*04fd306cSNickeau $source = substr( 233*04fd306cSNickeau $this->getSource(), 234*04fd306cSNickeau $current[Tokenizer::INDEX], 235*04fd306cSNickeau $current[Tokenizer::END] - $current[Tokenizer::INDEX] 236*04fd306cSNickeau ); 237*04fd306cSNickeau } else { 238*04fd306cSNickeau $source = ''; 239*04fd306cSNickeau } 240*04fd306cSNickeau $params = [ 241*04fd306cSNickeau $this, //First argument is this template 242*04fd306cSNickeau $context, //Second is current context 243*04fd306cSNickeau $current[Tokenizer::ARGS], //Arguments 244*04fd306cSNickeau $source 245*04fd306cSNickeau ]; 246*04fd306cSNickeau 247*04fd306cSNickeau $return = call_user_func_array($helpers->$sectionName, $params); 248*04fd306cSNickeau if ($return instanceof String) { 249*04fd306cSNickeau return $this->handlebars->loadString($return)->render($context); 250*04fd306cSNickeau } else { 251*04fd306cSNickeau return $return; 252*04fd306cSNickeau } 253*04fd306cSNickeau } elseif (trim($current[Tokenizer::ARGS]) == '') { 254*04fd306cSNickeau // fallback to mustache style each/with/for just if there is 255*04fd306cSNickeau // no argument at all. 256*04fd306cSNickeau try { 257*04fd306cSNickeau $sectionVar = $context->get($sectionName, true); 258*04fd306cSNickeau } catch (InvalidArgumentException $e) { 259*04fd306cSNickeau throw new RuntimeException( 260*04fd306cSNickeau $sectionName . ' is not registered as a helper' 261*04fd306cSNickeau ); 262*04fd306cSNickeau } 263*04fd306cSNickeau $buffer = ''; 264*04fd306cSNickeau if (is_array($sectionVar) || $sectionVar instanceof \Traversable) { 265*04fd306cSNickeau foreach ($sectionVar as $index => $d) { 266*04fd306cSNickeau $context->pushIndex($index); 267*04fd306cSNickeau $context->push($d); 268*04fd306cSNickeau $buffer .= $this->render($context); 269*04fd306cSNickeau $context->pop(); 270*04fd306cSNickeau $context->popIndex(); 271*04fd306cSNickeau } 272*04fd306cSNickeau } elseif (is_object($sectionVar)) { 273*04fd306cSNickeau //Act like with 274*04fd306cSNickeau $context->push($sectionVar); 275*04fd306cSNickeau $buffer = $this->render($context); 276*04fd306cSNickeau $context->pop(); 277*04fd306cSNickeau } elseif ($sectionVar) { 278*04fd306cSNickeau $buffer = $this->render($context); 279*04fd306cSNickeau } 280*04fd306cSNickeau 281*04fd306cSNickeau return $buffer; 282*04fd306cSNickeau } else { 283*04fd306cSNickeau throw new RuntimeException( 284*04fd306cSNickeau $sectionName . ' is not registered as a helper' 285*04fd306cSNickeau ); 286*04fd306cSNickeau } 287*04fd306cSNickeau } 288*04fd306cSNickeau 289*04fd306cSNickeau /** 290*04fd306cSNickeau * Process inverted section 291*04fd306cSNickeau * 292*04fd306cSNickeau * @param Context $context current context 293*04fd306cSNickeau * @param array $current section node data 294*04fd306cSNickeau * 295*04fd306cSNickeau * @return string the result 296*04fd306cSNickeau */ 297*04fd306cSNickeau private function inverted(Context $context, $current) 298*04fd306cSNickeau { 299*04fd306cSNickeau $sectionName = $current[Tokenizer::NAME]; 300*04fd306cSNickeau $data = $context->get($sectionName); 301*04fd306cSNickeau if (!$data) { 302*04fd306cSNickeau return $this->render($context); 303*04fd306cSNickeau } else { 304*04fd306cSNickeau //No need to discard here, since it has no else 305*04fd306cSNickeau return ''; 306*04fd306cSNickeau } 307*04fd306cSNickeau } 308*04fd306cSNickeau 309*04fd306cSNickeau /** 310*04fd306cSNickeau * Process partial section 311*04fd306cSNickeau * 312*04fd306cSNickeau * @param Context $context current context 313*04fd306cSNickeau * @param array $current section node data 314*04fd306cSNickeau * 315*04fd306cSNickeau * @return string the result 316*04fd306cSNickeau */ 317*04fd306cSNickeau private function partial(Context $context, $current) 318*04fd306cSNickeau { 319*04fd306cSNickeau $partial = $this->handlebars->loadPartial($current[Tokenizer::NAME]); 320*04fd306cSNickeau 321*04fd306cSNickeau if ($current[Tokenizer::ARGS]) { 322*04fd306cSNickeau $context = $context->get($current[Tokenizer::ARGS]); 323*04fd306cSNickeau } 324*04fd306cSNickeau 325*04fd306cSNickeau return $partial->render($context); 326*04fd306cSNickeau } 327*04fd306cSNickeau 328*04fd306cSNickeau /** 329*04fd306cSNickeau * Process partial section 330*04fd306cSNickeau * 331*04fd306cSNickeau * @param Context $context current context 332*04fd306cSNickeau * @param array $current section node data 333*04fd306cSNickeau * @param boolean $escaped escape result or not 334*04fd306cSNickeau * 335*04fd306cSNickeau * @return string the result 336*04fd306cSNickeau */ 337*04fd306cSNickeau private function variables(Context $context, $current, $escaped) 338*04fd306cSNickeau { 339*04fd306cSNickeau $name = $current[Tokenizer::NAME]; 340*04fd306cSNickeau $value = $context->get($name); 341*04fd306cSNickeau 342*04fd306cSNickeau // If @data variables are enabled, use the more complex algorithm for handling the the variables otherwise 343*04fd306cSNickeau // use the previous version. 344*04fd306cSNickeau if ($this->handlebars->isDataVariablesEnabled()) { 345*04fd306cSNickeau if (substr(trim($name), 0, 1) == '@') { 346*04fd306cSNickeau $variable = $context->getDataVariable($name); 347*04fd306cSNickeau if (is_bool($variable)) { 348*04fd306cSNickeau return $variable ? 'true' : 'false'; 349*04fd306cSNickeau } 350*04fd306cSNickeau return $variable; 351*04fd306cSNickeau } 352*04fd306cSNickeau } else { 353*04fd306cSNickeau // If @data variables are not enabled, then revert back to legacy behavior 354*04fd306cSNickeau if ($name == '@index') { 355*04fd306cSNickeau return $context->lastIndex(); 356*04fd306cSNickeau } 357*04fd306cSNickeau if ($name == '@key') { 358*04fd306cSNickeau return $context->lastKey(); 359*04fd306cSNickeau } 360*04fd306cSNickeau } 361*04fd306cSNickeau 362*04fd306cSNickeau if ($escaped) { 363*04fd306cSNickeau $args = $this->handlebars->getEscapeArgs(); 364*04fd306cSNickeau array_unshift($args, $value); 365*04fd306cSNickeau $value = call_user_func_array( 366*04fd306cSNickeau $this->handlebars->getEscape(), 367*04fd306cSNickeau array_values($args) 368*04fd306cSNickeau ); 369*04fd306cSNickeau } 370*04fd306cSNickeau 371*04fd306cSNickeau return $value; 372*04fd306cSNickeau } 373*04fd306cSNickeau 374*04fd306cSNickeau public function __clone() 375*04fd306cSNickeau { 376*04fd306cSNickeau return $this; 377*04fd306cSNickeau } 378*04fd306cSNickeau} 379