xref: /web-bugs/src/Template/Context.php (revision e8bab353)
1<?php
2
3namespace App\Template;
4
5/**
6 * Context represents a template variable scope where $this pseudo-variable can
7 * be used in the templates and context methods can be called as $this->method().
8 */
9class Context
10{
11    /**
12     * Templates directory.
13     *
14     * @var string
15     */
16    private $dir;
17
18    /**
19     * The current processed template or snippet file.
20     *
21     * @var string
22     */
23    private $current;
24
25    /**
26     * All assigned and set variables for the template.
27     *
28     * @var array
29     */
30    private $variables = [];
31
32    /**
33     * Pool of blocks for the template context.
34     *
35     * @var array
36     */
37    private $blocks = [];
38
39    /**
40     * Parent templates extended by child templates.
41     *
42     * @var array
43     */
44    public $tree = [];
45
46    /**
47     * Registered callables.
48     *
49     * @var array
50     */
51    private $callables = [];
52
53    /**
54     * Current nesting level of the output buffering mechanism.
55     *
56     * @var int
57     */
58    private $bufferLevel = 0;
59
60    /**
61     * Class constructor.
62     */
63    public function __construct(
64        string $dir,
65        array $variables = [],
66        array $callables = []
67    ) {
68        $this->dir = $dir;
69        $this->variables = $variables;
70        $this->callables = $callables;
71    }
72
73    /**
74     * Sets a parent layout for the given template. Additional variables in the
75     * parent scope can be defined via the second argument.
76     */
77    public function extends(string $parent, array $variables = []): void
78    {
79        if (isset($this->tree[$this->current])) {
80            throw new \Exception('Extending '.$parent.' is not possible.');
81        }
82
83        $this->tree[$this->current] = [$parent, $variables];
84    }
85
86    /**
87     * Return a block content from the pool by name.
88     */
89    public function block(string $name): string
90    {
91        return $this->blocks[$name] ?? '';
92    }
93
94    /**
95     * Starts a new template block. Under the hood a simple separate output
96     * buffering is used to capture the block content. Content can be also
97     * appended to previously set same block name.
98     */
99    public function start(string $name): void
100    {
101        $this->blocks[$name] = '';
102
103        ++$this->bufferLevel;
104
105        ob_start();
106    }
107
108    /**
109     * Append content to a template block. If no block with the key name exists
110     * yet it starts a new one.
111     */
112    public function append(string $name): void
113    {
114        if (!isset($this->blocks[$name])) {
115            $this->blocks[$name] = '';
116        }
117
118        ++$this->bufferLevel;
119
120        ob_start();
121    }
122
123    /**
124     * Ends block output buffering and stores its content into the pool.
125     */
126    public function end(string $name): void
127    {
128        --$this->bufferLevel;
129
130        $content = ob_get_clean();
131
132        if (!empty($this->blocks[$name])) {
133            $this->blocks[$name] .= $content;
134        } else {
135            $this->blocks[$name] = $content;
136        }
137    }
138
139    /**
140     * Include template file into existing template.
141     *
142     * @return mixed
143     */
144    public function include(string $template, array $variables = [])
145    {
146        if (count($variables) > extract($variables, EXTR_SKIP)) {
147            throw new \Exception(
148                'Variables with numeric names $0, $1... cannot be imported to scope '.$template
149            );
150        }
151
152        return include $this->dir.'/'.$template;
153    }
154
155    /**
156     * Scalpel when preventing XSS vulnerabilities. This escapes given string
157     * and still preserves certain characters as HTML.
158     */
159    public function e(string $string): string
160    {
161        return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
162    }
163
164    /**
165     * Hammer when protecting against XSS. Sanitize strings and replace all
166     * characters to their applicable HTML entities from it.
167     */
168    public function noHtml(string $string): string
169    {
170        return htmlentities($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
171    }
172
173    /**
174     * A proxy to call registered callable.
175     *
176     * @return mixed
177     */
178    public function __call(string $method, array $arguments)
179    {
180        if (isset($this->callables[$method])) {
181            return call_user_func_array($this->callables[$method], $arguments);
182        }
183    }
184}
185