Page   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Importance

Changes 12
Bugs 0 Features 0
Metric Value
eloc 133
c 12
b 0
f 0
dl 0
loc 328
rs 9.28
wmc 39

15 Methods

Rating   Name   Duplication   Size   Complexity  
A isValid() 0 7 2
A setTemplate() 0 4 1
A setData() 0 8 3
A view() 0 16 2
A setContentType() 0 4 1
A getContentFile() 0 11 2
A foot() 0 18 4
A part() 0 22 3
A render() 0 9 1
A getLayoutFile() 0 8 2
A fromRequest() 0 11 2
B head() 0 29 8
A __construct() 0 17 4
A templateFromUrl() 0 17 3
A request() 0 3 1
1
<?php
2
3
namespace alkemann\h2l\response;
4
5
use alkemann\h2l\Environment;
6
use alkemann\h2l\exceptions\ConfigMissing;
7
use alkemann\h2l\exceptions\InvalidUrl;
8
use alkemann\h2l\Log;
9
use alkemann\h2l\Message;
10
use alkemann\h2l\Request;
11
use alkemann\h2l\Response;
12
use alkemann\h2l\util\Http;
13
use Exception;
14
15
/**
16
 * Class Page
17
 *
18
 * @package alkemann\h2l
19
 */
20
final class Page extends Response
21
{
22
    /**
23
     * Overwrite this in view templates to set layout, i.e. `$this->layout = 'slim';`
24
     *
25
     * @var string
26
     */
27
    public string $layout = 'default';
28
    /**
29
     * @var null|Request
30
     */
31
    protected ?Request $request = null;
32
    /**
33
     * @var array<string, mixed>
34
     */
35
    protected array $data = [];
36
    /**
37
     * @var string
38
     */
39
    private string $template = 'error.html';
40
    /**
41
     * @var string
42
     */
43
    private string $content_type = Http::CONTENT_HTML;
44
    /**
45
     * @var int
46
     */
47
    private int $code = 200;
48
    /**
49
     * @var array
50
     */
51
    protected array $config = [];
52
53
    /**
54
     * Constructor
55
     *
56
     * @param array<string, mixed> $data
57
     * @param array<string, mixed> $config
58
     */
59
    public function __construct($data = [], array $config = [])
60
    {
61
        $this->data = $data;
62
        foreach (['request', 'content_type', 'code', 'layout'] as $key) {
63
            if (isset($config[$key])) {
64
                $this->{$key} = $config[$key];
65
                unset($config[$key]);
66
            }
67
        }
68
        if (isset($config['template'])) {
69
            $this->setTemplate($config['template']);
70
        }
71
        $this->config = $config;
72
73
        $this->message = (new Message())
74
            ->withCode($this->code)
75
            ->withHeader('Content-Type', $this->content_type)
76
        ;
77
    }
78
79
    /**
80
     * Analyze the request url to convert to a view template
81
     *
82
     * @param Request $request
83
     * @param array<string, mixed> $config
84
     * @return Page
85
     */
86
    public static function fromRequest(Request $request, array $config = []): Page
87
    {
88
        $config += [
89
            'request' => $request,
90
            'content_type' => $request->acceptType(),
91
        ];
92
        $page = new Page($request->pageVars(), $config);
93
        $route = $request->route();
94
        $url = $route ? $route->url() : $request->url();
95
        $page->template = $config['template'] ?? $page->templateFromUrl($url);
96
        return $page;
97
    }
98
99
    /**
100
     * Returns true if a file can be found that matches the "template" that is set
101
     *
102
     * @return bool
103
     * @throws InvalidUrl is thrown if the file is missing
104
     */
105
    public function isValid(): bool
106
    {
107
        $file = $this->getContentFile($this->template);
108
        if (!file_exists($file)) {
109
            throw new InvalidUrl("Missing view file: [ $file ]");
110
        }
111
        return true;
112
    }
113
114
    /**
115
     * Provide data (variables) that are to be extracted into the view (and layout) templates
116
     *
117
     * @param string|array<string, mixed> $key an array of data or the name for $value
118
     * @param mixed $value if $key is a string, this can be the value of that var
119
     */
120
    public function setData($key, $value = null): void
121
    {
122
        if (is_array($key)) {
123
            foreach ($key as $k => $v) {
124
                $this->data[$k] = $v;
125
            }
126
        } else {
127
            $this->data[$key] = $value;
128
        }
129
    }
130
131
    /**
132
     * Returns the `Request` of this response
133
     *
134
     * @return null|Request
135
     */
136
    public function request(): ?Request
137
    {
138
        return $this->request;
139
    }
140
141
    /**
142
     * @TODO refactor, and cache
143
     * @return string
144
     */
145
    private function head(): string
146
    {
147
        $headfile = $this->getLayoutFile('head');
148
        $neckfile = $this->getLayoutFile('neck');
149
        if (!$headfile && !$neckfile) {
150
            return '';
151
        }
152
        ob_start();
153
        try {
154
            if ($headfile && file_exists($headfile)) {
155
                $sldkfjlksejflskjflskdjflskdfj = $headfile;
156
                (function() use ($sldkfjlksejflskjflskdjflskdfj) {
157
                    extract($this->data);
158
                    include $sldkfjlksejflskjflskdjflskdfj;
159
                })();
160
            }
161
162
            if ($neckfile && file_exists($neckfile)) {
163
                $lidsinqjhsdfytqkwjkasjdksadsdg = $neckfile;
164
                (function() use ($lidsinqjhsdfytqkwjkasjdksadsdg) {
165
                    extract($this->data);
166
                    include $lidsinqjhsdfytqkwjkasjdksadsdg;
167
                })();
168
            }
169
        } finally {
170
            $ret = ob_get_contents();
171
            ob_end_clean();
172
        }
173
        return is_string($ret) ? $ret : '';
174
    }
175
176
    /**
177
     * @param string $name
178
     * @return string|null
179
     */
180
    private function getLayoutFile(string $name): ?string
181
    {
182
        $path = $this->config['layout_path'] ?? Environment::get('layout_path');
183
        if (is_null($path)) {
184
            return null;
185
        }
186
        $ending = Http::fileEndingFromType($this->content_type);
187
        return $path . $this->layout . DIRECTORY_SEPARATOR . $name . '.' . $ending . '.php';
188
    }
189
190
    /**
191
     * @param string $view
192
     * @return string
193
     * @throws ConfigMissing if neither content nor template paths are found
194
     */
195
    private function getContentFile(string $view): string
196
    {
197
        $path = $this->config['content_path']
198
            ?? $this->config['template_path']
199
            ?? Environment::get('content_path')
200
            ?? Environment::get('template_path');
201
202
        if (is_null($path)) {
203
            throw new ConfigMissing("No `content_path` or `template_path` configured!");
204
        }
205
        return $path . $view . '.php';
206
    }
207
208
    /**
209
     * @TODO refactor, and cache
210
     * @return string
211
     */
212
    private function foot(): string
213
    {
214
        $footfile = $this->getLayoutFile('foot');
215
        if ($footfile && file_exists($footfile)) {
216
            ob_start();
217
            try {
218
                $ldkfoskdfosjicyvutwehkshfskjdf = $footfile;
219
                (function() use ($ldkfoskdfosjicyvutwehkshfskjdf) {
220
                    extract($this->data);
221
                    include $ldkfoskdfosjicyvutwehkshfskjdf;
222
                })();
223
            } finally {
224
                $ret = ob_get_contents();
225
                ob_end_clean();
226
            }
227
            return is_string($ret) ? $ret : '';
228
        } else {
229
            return '';
230
        }
231
    }
232
233
    /**
234
     * @TODO refactor, and cache
235
     * @TODO BUG, method should be private
236
     * @param string $view
237
     * @return string
238
     * @throws InvalidUrl if no view file found
239
     */
240
    public function view(string $view): string
241
    {
242
        $file = $this->getContentFile($view);
243
        ob_start();
244
        try {
245
            // or another way to hide the file variable?
246
            $dsfjskdfjsdlkfjsdkfjsdkfjsdlkfjsd = $file;
247
            (function() use ($dsfjskdfjsdlkfjsdkfjsdkfjsdlkfjsd) {
248
                extract($this->data);
249
                include $dsfjskdfjsdlkfjsdkfjsdkfjsdlkfjsd;
250
            })();
251
        } finally {
252
            $ret = ob_get_contents();
253
            ob_end_clean();
254
        }
255
        return is_string($ret) ? $ret : '';
256
    }
257
258
    /**
259
     * Render a parts file to include in view for reusing parts
260
     *
261
     * @param string $name name of part file to include, doesnt include path or file ending
262
     * @return string
263
     * @throws ConfigMissing if `parts_path` is not configured
264
     */
265
    public function part(string $name): string
266
    {
267
        $parts_path = Environment::get('parts_path');
268
        if (!$parts_path) {
269
            throw new ConfigMissing("Missing `parts_path` configuration!");
270
        }
271
        $ending = Http::fileEndingFromType($this->content_type);
272
273
        $parts_file = $parts_path . "{$name}.{$ending}.php";
274
275
        ob_start();
276
        try {
277
            $popsemdsdfosjicyvsoaowkdawd = $parts_file;
278
            (function() use ($popsemdsdfosjicyvsoaowkdawd) {
279
                extract($this->data);
280
                include $popsemdsdfosjicyvsoaowkdawd;
281
            })();
282
        } finally {
283
            $ret = ob_get_contents();
284
            ob_end_clean();
285
        }
286
        return is_string($ret) ? $ret : '';
287
    }
288
289
    /**
290
     * Set header type, render the view, then optionally render layouts and wrap the template
291
     *
292
     * @return string fully rendered string, ready to be echo'ed
293
     * @throws InvalidUrl if the view template does not exist
294
     */
295
    public function render(): string
296
    {
297
        $this->setHeaders();
298
        $view = $this->view($this->template);
299
        $response = $this->head();
300
        $response .= $view;
301
        $response .= $this->foot();
302
        $this->message = $this->message->withBody($response);
303
        return $this->message->body();
304
    }
305
306
    /**
307
     * Creates a filename from template and content type
308
     *
309
     * @param string $template
310
     */
311
    public function setTemplate(string $template): void
312
    {
313
        $ending = Http::fileEndingFromType($this->content_type);
314
        $this->template = "{$template}.{$ending}";
315
    }
316
317
    /**
318
     * @param string $url
319
     * @return string
320
     * @throws Exception if url has not enough parts
321
     */
322
    private function templateFromUrl(string $url): string
323
    {
324
        $parts = explode('/', $url);
325
        $last = array_slice($parts, -1, 1, true);
326
        unset($parts[key($last)]);
327
        $view = current($last);
328
        $period = strrpos($view, '.');
329
        if ($period) {
330
            $ending = substr($view, $period + 1);
331
            if ($type = Http::contentTypeFromFileEnding($ending)) {
332
                $this->setContentType($type);
333
                $view = substr($view, 0, $period);
334
            }
335
        }
336
        $ending = Http::fileEndingFromType($this->content_type);
337
        $ret = join(DIRECTORY_SEPARATOR, $parts) . DIRECTORY_SEPARATOR . $view . '.' . $ending;
338
        return trim($ret, DIRECTORY_SEPARATOR);
339
    }
340
341
    /**
342
     * @param string $type
343
     */
344
    private function setContentType(string $type): void
345
    {
346
        $this->content_type = $type;
347
        $this->message = $this->message->withHeader('Content-Type', $this->content_type);
348
    }
349
}
350