Completed
Push — master ( 43ea5a...34ec74 )
by Arman
21s queued 15s
created

QtView::processValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 6
rs 10
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.9.7
13
 */
14
15
namespace Quantum\View;
16
17
use Quantum\Libraries\Database\Exceptions\DatabaseException;
18
use Quantum\Libraries\Session\Exceptions\SessionException;
19
use Quantum\Libraries\Config\Exceptions\ConfigException;
20
use Quantum\Libraries\Asset\Exceptions\AssetException;
21
use Quantum\Libraries\ResourceCache\ViewCache;
22
use Quantum\View\Exceptions\ViewException;
23
use Quantum\Libraries\Asset\AssetManager;
24
use Quantum\Di\Exceptions\DiException;
25
use Quantum\Exceptions\BaseException;
26
use Quantum\Renderer\Renderer;
27
use Quantum\Debugger\Debugger;
28
use ReflectionException;
29
use Psr\Log\LogLevel;
30
31
/**
32
 * Class QtView
33
 * @package Quantum\View
34
 */
35
class QtView
36
{
37
38
    /**
39
     * @var Renderer
40
     */
41
    private $renderer;
42
43
    /**
44
     * @var AssetManager
45
     */
46
    private $assetManager;
47
48
    /**
49
     * @var Debugger
50
     */
51
    private $debugger;
52
53
    /**
54
     * @var ViewCache
55
     */
56
    private $viewCache;
57
58
    /**
59
     * Layout file
60
     * @var string
61
     */
62
    private $layoutFile = null;
63
64
    /**
65
     * Rendered view
66
     * @var string
67
     */
68
    private $viewContent;
69
70
    /**
71
     * Assets to be included
72
     * @var array
73
     */
74
    private $assets = [];
75
76
    /**
77
     * View params
78
     * @var array
79
     */
80
    private $params = [];
81
82
    /**
83
     * Parameters that should not be escaped
84
     * @var array
85
     */
86
    private $skipEscapeParams = [];
87
88
    /**
89
     * @param Renderer $renderer
90
     * @param AssetManager $assetManager
91
     * @param Debugger $debugger
92
     * @param ViewCache $viewCache
93
     */
94
    public function __construct(
95
        Renderer $renderer,
96
        AssetManager $assetManager,
97
        Debugger $debugger,
98
        ViewCache $viewCache
99
    )
100
    {
101
        $this->renderer = $renderer;
102
        $this->assetManager = $assetManager;
103
        $this->debugger = $debugger;
104
        $this->viewCache = $viewCache;
105
    }
106
107
    /**
108
     * Sets a layout
109
     * @param string|null $layoutFile
110
     * @param array $assets
111
     */
112
    public function setLayout(?string $layoutFile, array $assets = [])
113
    {
114
        $this->layoutFile = $layoutFile;
115
        $this->assets = $assets;
116
    }
117
118
    /**
119
     * Gets the layout
120
     * @return string|null
121
     */
122
    public function getLayout(): ?string
123
    {
124
        return $this->layoutFile;
125
    }
126
127
    /**
128
     * Sets view parameter
129
     * @param string $key
130
     * @param mixed $value
131
     * @param bool $skipEscape
132
     */
133
    public function setParam(string $key, $value, bool $skipEscape = false)
134
    {
135
        $this->params[$key] = $value;
136
137
        if ($skipEscape) {
138
            $this->skipEscapeParams[$key] = true;
139
        }
140
    }
141
142
    /**
143
     * Gets the view parameter
144
     * @param string $key
145
     * @return mixed|null
146
     */
147
    public function getParam(string $key)
148
    {
149
        return $this->params[$key] ?? null;
150
    }
151
152
    /**
153
     * Sets multiple view parameters
154
     * @param array $params
155
     * @param bool $skipEscape
156
     */
157
    public function setParams(array $params, bool $skipEscape = false)
158
    {
159
        foreach ($params as $key => $value) {
160
            $this->setParam($key, $value, $skipEscape);
161
        }
162
    }
163
164
    /**
165
     * Gets all view parameters
166
     * @return array
167
     */
168
    public function getParams(): array
169
    {
170
        return $this->params;
171
    }
172
173
    /**
174
     * Flushes the view params
175
     */
176
    public function flushParams()
177
    {
178
        $this->params = [];
179
        $this->skipEscapeParams = [];
180
    }
181
182
    /**
183
     * Renders the view.
184
     * @param string $viewFile
185
     * @param array $params
186
     * @return string|null
187
     * @throws AssetException
188
     * @throws BaseException
189
     * @throws ConfigException
190
     * @throws DatabaseException
191
     * @throws DiException
192
     * @throws ReflectionException
193
     * @throws SessionException
194
     * @throws ViewException
195
     */
196
    public function render(string $viewFile, array $params = []): ?string
197
    {
198
        if (!$this->layoutFile) {
199
            throw ViewException::noLayoutSet();
200
        }
201
202
        if (!empty($params)) {
203
            $this->params = array_merge($this->params, $params);
204
        }
205
206
        $this->viewContent = $this->renderFile($viewFile);
207
208
        if (!empty($this->assets)) {
209
            $this->assetManager->register($this->assets);
210
        }
211
212
        if ($this->debugger->isEnabled()) {
213
            $this->updateDebugger($viewFile);
214
        }
215
216
        $layoutContent = $this->renderFile($this->layoutFile);
217
218
        if ($this->viewCache->isEnabled()) {
219
            $layoutContent = $this->viewCache
220
                ->set(route_uri(), $layoutContent)
221
                ->get(route_uri());
222
        }
223
224
        return $layoutContent;
225
    }
226
227
    /**
228
     * Renders partial view.
229
     * @param string $viewFile
230
     * @param array $params
231
     * @return string
232
     */
233
    public function renderPartial(string $viewFile, array $params = []): string
234
    {
235
        if (!empty($params)) {
236
            $this->params = array_merge($this->params, $params);
237
        }
238
239
        return $this->renderFile($viewFile);
240
    }
241
242
    /**
243
     * Gets the rendered view.
244
     * @return string|null
245
     * @throws ViewException
246
     */
247
    public function getView(): ?string
248
    {
249
        if ($this->viewContent === null) {
250
            throw ViewException::viewNotRendered();
251
        }
252
253
        return $this->viewContent;
254
    }
255
256
    /**
257
     * Renders the view
258
     * @param string $viewFile
259
     * @return string
260
     */
261
    private function renderFile(string $viewFile): string
262
    {
263
        $params = $this->xssFilter($this->params);
264
265
        return $this->renderer->render($viewFile, $params);
266
    }
267
268
    /**
269
     * XSS Filter
270
     * @param mixed $params
271
     * @return mixed
272
     */
273
    private function xssFilter($params)
274
    {
275
        if (is_string($params)) {
276
            $this->cleaner($params);
277
            $params = [$params];
278
        } else {
279
            foreach ($params as $key => &$value) {
280
                if (!isset($this->skipEscapeParams[$key])) {
281
                    $this->processValue($value);
282
                }
283
            }
284
        }
285
286
        return $params;
287
    }
288
289
    /**
290
     * Process value recursively for escaping
291
     * @param $value
292
     * @return void
293
     */
294
    private function processValue(&$value)
295
    {
296
        if (is_array($value)) {
297
            array_walk_recursive($value, [$this, 'cleaner']);
298
        } else {
299
            $this->cleaner($value);
300
        }
301
    }
302
303
    /**
304
     * Cleaner
305
     * @param mixed $value
306
     */
307
    private function cleaner(&$value)
308
    {
309
        if (is_object($value)) {
310
            $this->xssFilter($value);
311
        } else {
312
            $value = htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
313
        }
314
    }
315
316
    /**
317
     * @param string $viewFile
318
     * @return void
319
     */
320
    private function updateDebugger(string $viewFile)
321
    {
322
        $routesCell = $this->debugger->getStoreCell(Debugger::ROUTES);
323
        $currentData = current($routesCell)[LogLevel::INFO] ?? [];
324
        $additionalData = ['View' => current_module() . '/Views/' . $viewFile];
325
        $mergedData = array_merge($currentData, $additionalData);
326
        $this->debugger->clearStoreCell(Debugger::ROUTES);
327
        $this->debugger->addToStoreCell(Debugger::ROUTES, LogLevel::INFO, $mergedData);
328
    }
329
}