1<?php
2
3/**
4 * Inheritance Runtime Methods processBlock, endChild, init
5 *
6 * @package    Smarty
7 * @subpackage PluginsInternal
8 * @author     Uwe Tews
9 **/
10class Smarty_Internal_Runtime_Inheritance
11{
12    /**
13     * State machine
14     * - 0 idle next extends will create a new inheritance tree
15     * - 1 processing child template
16     * - 2 wait for next inheritance template
17     * - 3 assume parent template, if child will loaded goto state 1
18     *     a call to a sub template resets the state to 0
19     *
20     * @var int
21     */
22    public $state = 0;
23
24    /**
25     * Array of root child {block} objects
26     *
27     * @var Smarty_Internal_Block[]
28     */
29    public $childRoot = array();
30
31    /**
32     * inheritance template nesting level
33     *
34     * @var int
35     */
36    public $inheritanceLevel = 0;
37
38    /**
39     * inheritance template index
40     *
41     * @var int
42     */
43    public $tplIndex = -1;
44
45    /**
46     * Array of template source objects
47     *
48     * @var Smarty_Template_Source[]
49     */
50    public $sources = array();
51
52    /**
53     * Stack of source objects while executing block code
54     *
55     * @var Smarty_Template_Source[]
56     */
57    public $sourceStack = array();
58
59    /**
60     * Initialize inheritance
61     *
62     * @param \Smarty_Internal_Template $tpl        template object of caller
63     * @param bool                      $initChild  if true init for child template
64     * @param array                     $blockNames outer level block name
65     */
66    public function init(Smarty_Internal_Template $tpl, $initChild, $blockNames = array())
67    {
68        // if called while executing parent template it must be a sub-template with new inheritance root
69        if ($initChild && $this->state === 3 && (strpos($tpl->template_resource, 'extendsall') === false)) {
70            $tpl->inheritance = new Smarty_Internal_Runtime_Inheritance();
71            $tpl->inheritance->init($tpl, $initChild, $blockNames);
72            return;
73        }
74        ++$this->tplIndex;
75        $this->sources[ $this->tplIndex ] = $tpl->source;
76        // start of child sub template(s)
77        if ($initChild) {
78            $this->state = 1;
79            if (!$this->inheritanceLevel) {
80                //grab any output of child templates
81                ob_start();
82            }
83            ++$this->inheritanceLevel;
84            //           $tpl->startRenderCallbacks[ 'inheritance' ] = array($this, 'subTemplateStart');
85            //           $tpl->endRenderCallbacks[ 'inheritance' ] = array($this, 'subTemplateEnd');
86        }
87        // if state was waiting for parent change state to parent
88        if ($this->state === 2) {
89            $this->state = 3;
90        }
91    }
92
93    /**
94     * End of child template(s)
95     * - if outer level is reached flush output buffer and switch to wait for parent template state
96     *
97     * @param \Smarty_Internal_Template $tpl
98     * @param null|string               $template optional name of inheritance parent template
99     * @param null|string               $uid      uid of inline template
100     * @param null|string               $func     function call name of inline template
101     *
102     * @throws \Exception
103     * @throws \SmartyException
104     */
105    public function endChild(Smarty_Internal_Template $tpl, $template = null, $uid = null, $func = null)
106    {
107        --$this->inheritanceLevel;
108        if (!$this->inheritanceLevel) {
109            ob_end_clean();
110            $this->state = 2;
111        }
112        if (isset($template) && (($tpl->parent->_isTplObj() && $tpl->parent->source->type !== 'extends')
113                                 || $tpl->smarty->extends_recursion)
114        ) {
115            $tpl->_subTemplateRender(
116                $template,
117                $tpl->cache_id,
118                $tpl->compile_id,
119                $tpl->caching ? 9999 : 0,
120                $tpl->cache_lifetime,
121                array(),
122                2,
123                false,
124                $uid,
125                $func
126            );
127        }
128    }
129
130    /**
131     * Smarty_Internal_Block constructor.
132     * - if outer level {block} of child template ($state === 1) save it as child root block
133     * - otherwise process inheritance and render
134     *
135     * @param \Smarty_Internal_Template $tpl
136     * @param                           $className
137     * @param string                    $name
138     * @param int|null                  $tplIndex index of outer level {block} if nested
139     *
140     * @throws \SmartyException
141     */
142    public function instanceBlock(Smarty_Internal_Template $tpl, $className, $name, $tplIndex = null)
143    {
144        $block = new $className($name, isset($tplIndex) ? $tplIndex : $this->tplIndex);
145        if (isset($this->childRoot[ $name ])) {
146            $block->child = $this->childRoot[ $name ];
147        }
148        if ($this->state === 1) {
149            $this->childRoot[ $name ] = $block;
150            return;
151        }
152        // make sure we got child block of child template of current block
153        while ($block->child && $block->child->child && $block->tplIndex <= $block->child->tplIndex) {
154            $block->child = $block->child->child;
155        }
156        $this->process($tpl, $block);
157    }
158
159    /**
160     * Goto child block or render this
161     *
162     * @param \Smarty_Internal_Template   $tpl
163     * @param \Smarty_Internal_Block      $block
164     * @param \Smarty_Internal_Block|null $parent
165     *
166     * @throws \SmartyException
167     */
168    public function process(
169        Smarty_Internal_Template $tpl,
170        Smarty_Internal_Block $block,
171        Smarty_Internal_Block $parent = null
172    ) {
173        if ($block->hide && !isset($block->child)) {
174            return;
175        }
176        if (isset($block->child) && $block->child->hide && !isset($block->child->child)) {
177            $block->child = null;
178        }
179        $block->parent = $parent;
180        if ($block->append && !$block->prepend && isset($parent)) {
181            $this->callParent($tpl, $block, '\'{block append}\'');
182        }
183        if ($block->callsChild || !isset($block->child) || ($block->child->hide && !isset($block->child->child))) {
184            $this->callBlock($block, $tpl);
185        } else {
186            $this->process($tpl, $block->child, $block);
187        }
188        if ($block->prepend && isset($parent)) {
189            $this->callParent($tpl, $block, '{block prepend}');
190            if ($block->append) {
191                if ($block->callsChild || !isset($block->child)
192                    || ($block->child->hide && !isset($block->child->child))
193                ) {
194                    $this->callBlock($block, $tpl);
195                } else {
196                    $this->process($tpl, $block->child, $block);
197                }
198            }
199        }
200        $block->parent = null;
201    }
202
203    /**
204     * Render child on \$smarty.block.child
205     *
206     * @param \Smarty_Internal_Template $tpl
207     * @param \Smarty_Internal_Block    $block
208     *
209     * @return null|string block content
210     * @throws \SmartyException
211     */
212    public function callChild(Smarty_Internal_Template $tpl, Smarty_Internal_Block $block)
213    {
214        if (isset($block->child)) {
215            $this->process($tpl, $block->child, $block);
216        }
217    }
218
219    /**
220     * Render parent block on \$smarty.block.parent or {block append/prepend}
221     *
222     * @param \Smarty_Internal_Template $tpl
223     * @param \Smarty_Internal_Block    $block
224     * @param string                    $tag
225     *
226     * @return null|string  block content
227     * @throws \SmartyException
228     */
229    public function callParent(Smarty_Internal_Template $tpl, Smarty_Internal_Block $block, $tag)
230    {
231        if (isset($block->parent)) {
232            $this->callBlock($block->parent, $tpl);
233        } else {
234            throw new SmartyException("inheritance: illegal '{$tag}' used in child template '{$tpl->inheritance->sources[$block->tplIndex]->filepath}' block '{$block->name}'");
235        }
236    }
237
238    /**
239     * render block
240     *
241     * @param \Smarty_Internal_Block    $block
242     * @param \Smarty_Internal_Template $tpl
243     */
244    public function callBlock(Smarty_Internal_Block $block, Smarty_Internal_Template $tpl)
245    {
246        $this->sourceStack[] = $tpl->source;
247        $tpl->source = $this->sources[ $block->tplIndex ];
248        $block->callBlock($tpl);
249        $tpl->source = array_pop($this->sourceStack);
250    }
251}
252