1<?php
2
3/*
4 * This file is part of Mustache.php.
5 *
6 * (c) 2010-2017 Justin Hileman
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12/**
13 * Abstract Mustache Template class.
14 *
15 * @abstract
16 */
17abstract class Mustache_Template
18{
19    /**
20     * @var Mustache_Engine
21     */
22    protected $mustache;
23
24    /**
25     * @var bool
26     */
27    protected $strictCallables = false;
28
29    /**
30     * Mustache Template constructor.
31     *
32     * @param Mustache_Engine $mustache
33     */
34    public function __construct(Mustache_Engine $mustache)
35    {
36        $this->mustache = $mustache;
37    }
38
39    /**
40     * Mustache Template instances can be treated as a function and rendered by simply calling them.
41     *
42     *     $m = new Mustache_Engine;
43     *     $tpl = $m->loadTemplate('Hello, {{ name }}!');
44     *     echo $tpl(array('name' => 'World')); // "Hello, World!"
45     *
46     * @see Mustache_Template::render
47     *
48     * @param mixed $context Array or object rendering context (default: array())
49     *
50     * @return string Rendered template
51     */
52    public function __invoke($context = array())
53    {
54        return $this->render($context);
55    }
56
57    /**
58     * Render this template given the rendering context.
59     *
60     * @param mixed $context Array or object rendering context (default: array())
61     *
62     * @return string Rendered template
63     */
64    public function render($context = array())
65    {
66        return $this->renderInternal(
67            $this->prepareContextStack($context)
68        );
69    }
70
71    /**
72     * Internal rendering method implemented by Mustache Template concrete subclasses.
73     *
74     * This is where the magic happens :)
75     *
76     * NOTE: This method is not part of the Mustache.php public API.
77     *
78     * @param Mustache_Context $context
79     * @param string           $indent  (default: '')
80     *
81     * @return string Rendered template
82     */
83    abstract public function renderInternal(Mustache_Context $context, $indent = '');
84
85    /**
86     * Tests whether a value should be iterated over (e.g. in a section context).
87     *
88     * In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists
89     * should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript,
90     * Java, Python, etc.
91     *
92     * PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish
93     * between between a list of things (numeric, normalized array) and a set of variables to be used as section context
94     * (associative array). In other words, this will be iterated over:
95     *
96     *     $items = array(
97     *         array('name' => 'foo'),
98     *         array('name' => 'bar'),
99     *         array('name' => 'baz'),
100     *     );
101     *
102     * ... but this will be used as a section context block:
103     *
104     *     $items = array(
105     *         1        => array('name' => 'foo'),
106     *         'banana' => array('name' => 'bar'),
107     *         42       => array('name' => 'baz'),
108     *     );
109     *
110     * @param mixed $value
111     *
112     * @return bool True if the value is 'iterable'
113     */
114    protected function isIterable($value)
115    {
116        switch (gettype($value)) {
117            case 'object':
118                return $value instanceof Traversable;
119
120            case 'array':
121                $i = 0;
122                foreach ($value as $k => $v) {
123                    if ($k !== $i++) {
124                        return false;
125                    }
126                }
127
128                return true;
129
130            default:
131                return false;
132        }
133    }
134
135    /**
136     * Helper method to prepare the Context stack.
137     *
138     * Adds the Mustache HelperCollection to the stack's top context frame if helpers are present.
139     *
140     * @param mixed $context Optional first context frame (default: null)
141     *
142     * @return Mustache_Context
143     */
144    protected function prepareContextStack($context = null)
145    {
146        $stack = new Mustache_Context();
147
148        $helpers = $this->mustache->getHelpers();
149        if (!$helpers->isEmpty()) {
150            $stack->push($helpers);
151        }
152
153        if (!empty($context)) {
154            $stack->push($context);
155        }
156
157        return $stack;
158    }
159
160    /**
161     * Resolve a context value.
162     *
163     * Invoke the value if it is callable, otherwise return the value.
164     *
165     * @param mixed            $value
166     * @param Mustache_Context $context
167     *
168     * @return string
169     */
170    protected function resolveValue($value, Mustache_Context $context)
171    {
172        if (($this->strictCallables ? is_object($value) : !is_string($value)) && is_callable($value)) {
173            return $this->mustache
174                ->loadLambda((string) call_user_func($value))
175                ->renderInternal($context);
176        }
177
178        return $value;
179    }
180}
181