xref: /template/strap/vendor/salesforce/handlebars-php/src/Handlebars/Template.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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