Passed
Push — master ( 416163...1bd59b )
by Alexander
01:34
created

Page::head()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 27
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 20
c 2
b 0
f 0
nc 11
nop 0
dl 0
loc 27
rs 8.6666
1
<?php
2
3
namespace alkemann\h2l\response;
4
5
use alkemann\h2l\exceptions\InvalidUrl;
6
use alkemann\h2l\exceptions\ConfigMissing;
7
use alkemann\h2l\Log;
8
use alkemann\h2l\Message;
9
use alkemann\h2l\Request;
10
use alkemann\h2l\Response;
11
use alkemann\h2l\Environment;
12
use alkemann\h2l\util\Http;
13
14
/**
15
 * Class Page
16
 *
17
 * @package alkemann\h2l
18
 */
19
class Page extends Response
20
{
21
    /**
22
     * Overwrite this in view templates to set layout, i.e. `$this->layout = 'slim';`
23
     *
24
     * @var string
25
     */
26
    public $layout = 'default';
27
    /**
28
     * @var Request
29
     */
30
    protected $request;
31
    /**
32
     * @var array
33
     */
34
    protected $data = [];
35
    /**
36
     * @var string
37
     */
38
    private $template = 'error';
39
    /**
40
     * @var string
41
     */
42
    private $content_type = Http::CONTENT_HTML;
43
    /**
44
     * @var int
45
     */
46
    private $code = 200;
47
    /**
48
     * @var array
49
     */
50
    protected $config = [];
51
52
    public function __construct($data = [], array $config = [])
53
    {
54
        $this->data = $data;
55
        foreach (['request', 'content_type', 'code', 'layout'] as $key) {
56
            if (isset($config[$key])) {
57
                $this->{$key} = $config[$key];
58
                unset($config[$key]);
59
            }
60
        }
61
        if (isset($config['template'])) {
62
            $this->setTemplate($config['template']);
63
        }
64
        $this->config = $config;
65
66
        $this->message = (new Message())
67
            ->withCode($this->code)
68
            ->withHeader('Content-Type', $this->content_type)
69
        ;
70
    }
71
72
    /**
73
     * Analyze the request url to convert to a view template
74
     */
75
    public static function fromRequest(Request $request, array $config = []): Page
76
    {
77
        $config += [
78
            'request' => $request,
79
            'content_type' => $request->acceptType(),
80
        ];
81
        $page = new static($request->pageVars(), $config);
82
        $route = $request->route();
83
        $url = $route ? $route->url() : $request->url();
84
        $page->template = $config['template'] ?? $page->templateFromUrl($url);
85
        return $page;
86
    }
87
88
    /**
89
     * @throws InvalidUrl
90
     */
91
    public function isValid(): bool
92
    {
93
        $file = $this->getContentFile($this->template);
94
        if (!file_exists($file)) {
95
            throw new InvalidUrl("Missing view file: [ $file ]");
96
        }
97
        return true;
98
    }
99
100
    /**
101
     * Provide data (variables) that are to be extracted into the view (and layout) templates
102
     *
103
     * @param string|array $key an array of data or the name for $value
104
     * @param mixed $value if $key is a string, this can be the value of that var
105
     */
106
    public function setData($key, $value = null): void
107
    {
108
        if (is_array($key)) {
109
            foreach ($key as $k => $v) {
110
                $this->data[$k] = $v;
111
            }
112
        } else {
113
            $this->data[$key] = $value;
114
        }
115
    }
116
117
    public function request(): ?Request
118
    {
119
        return $this->request;
120
    }
121
122
    // @TODO refactor, and cache
123
    private function head(): string
124
    {
125
        $headfile = $this->getLayoutFile('head');
126
        $neckfile = $this->getLayoutFile('neck');
127
        if (!$headfile && !$neckfile) {
128
            return '';
129
        }
130
        ob_start();
131
        try {
132
            if ($headfile && file_exists($headfile)) {
133
                (function ($sldkfjlksejflskjflskdjflskdfj) {
134
                    extract($this->data);
135
                    include $sldkfjlksejflskjflskdjflskdfj;
136
                })($headfile);
137
            }
138
139
            if ($neckfile && file_exists($neckfile)) {
140
                (function ($lidsinqjhsdfytqkwjkasjdksadsdg) {
141
                    extract($this->data);
142
                    include $lidsinqjhsdfytqkwjkasjdksadsdg;
143
                })($neckfile);
144
            }
145
        } finally {
146
            $ret = ob_get_contents();
147
            ob_end_clean();
148
        }
149
        return $ret;
150
    }
151
152
    private function getLayoutFile(string $name): ?string
153
    {
154
        $path = $this->config['layout_path'] ?? Environment::get('layout_path');
155
        if (is_null($path)) {
156
            return null;
157
        }
158
        $ending = Http::fileEndingFromType($this->content_type);
159
        return $path . $this->layout . DIRECTORY_SEPARATOR . $name . '.' . $ending . '.php';
160
    }
161
162
    private function getContentFile(string $view): string
163
    {
164
        $path = $this->config['content_path']
165
            ?? Environment::get('content_path')
166
            ?? $this->config['template_path']
167
            ?? Environment::get('template_path');
168
169
        if (is_null($path)) {
170
            throw new ConfigMissing("No `content_path` or `template_path` configured!");
171
        }
172
        return $path . $view . '.php';
173
    }
174
175
    // @TODO refactor, and cache
176
    private function foot(): string
177
    {
178
        $footfile = $this->getLayoutFile('foot');
179
        if ($footfile && file_exists($footfile)) {
180
            ob_start();
181
            try {
182
                (function ($ldkfoskdfosjicyvutwehkshfskjdf) {
183
                    extract($this->data);
184
                    include $ldkfoskdfosjicyvutwehkshfskjdf;
185
                })($footfile);
186
            } finally {
187
                $ret = ob_get_contents();
188
                ob_end_clean();
189
            }
190
            return $ret;
191
        } else {
192
            return '';
193
        }
194
    }
195
196
    // @TODO refactor, and cache
197
198
    /**
199
     * @throws InvalidUrl
200
     */
201
    public function view(string $view): string
202
    {
203
        $file = $this->getContentFile($view);
204
        ob_start();
205
        try {
206
            // or another way to hide the file variable?
207
            (function ($dsfjskdfjsdlkfjsdkfjsdkfjsdlkfjsd) {
208
                extract($this->data);
209
                include $dsfjskdfjsdlkfjsdkfjsdkfjsdlkfjsd;
210
            })($file);
211
        } finally {
212
            $ret = ob_get_contents();
213
            ob_end_clean();
214
        }
215
        return $ret;
216
    }
217
218
    /**
219
     * Set header type, render the view, then optionally render layouts and wrap the template
220
     *
221
     * @return string fully rendered string, ready to be echo'ed
222
     * @throws InvalidUrl if the view template does not exist
223
     */
224
    public function render(): string
225
    {
226
        $this->setHeaders();
227
        $view = $this->view($this->template);
228
        $response = $this->head();
229
        $response .= $view;
230
        $response .= $this->foot();
231
        $this->message = $this->message->withBody($response);
232
        return $this->message->body();
233
    }
234
235
    public function setTemplate(string $template): void
236
    {
237
        $ending = Http::fileEndingFromType($this->content_type);
238
        $this->template = "{$template}.{$ending}";
239
    }
240
241
    private function templateFromUrl(?string $url = null): string
242
    {
243
        $parts = \explode('/', $url);
244
        $last = \array_slice($parts, -1, 1, true);
245
        unset($parts[key($last)]);
246
        $view = current($last);
247
        $period = strrpos($view, '.');
248
        if ($period) {
249
            $ending = substr($view, $period + 1);
250
            if ($type = Http::contentTypeFromFileEnding($ending)) {
251
                $this->setContentType($type);
252
                $view = substr($view, 0, $period);
253
            }
254
        }
255
        $ending = Http::fileEndingFromType($this->content_type);
256
        $ret = join(DIRECTORY_SEPARATOR, $parts) . DIRECTORY_SEPARATOR . $view . '.' . $ending;
257
        return trim($ret, DIRECTORY_SEPARATOR);
258
    }
259
260
    private function setContentType($type)
261
    {
262
        $this->content_type = $type;
263
        $this->message = $this->message->withHeader('Content-Type', $this->content_type);
264
    }
265
}
266