Completed
Push — master ( 63956b...cafac5 )
by Vladimir
02:09
created

Controller::getExpandedValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
/**
4
 * @copyright 2018 Vladimir Jimenez
5
 * @license   https://github.com/stakx-io/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\Server;
9
10
use allejo\stakx\Compiler;
11
use allejo\stakx\Document\BasePageView;
12
use allejo\stakx\Document\CollectableItem;
13
use allejo\stakx\Document\DynamicPageView;
14
use allejo\stakx\Document\ReadableDocument;
15
use allejo\stakx\Document\RepeaterPageView;
16
use allejo\stakx\Document\StaticPageView;
17
use allejo\stakx\Document\TemplateReadyDocument;
18
use allejo\stakx\Filesystem\FilesystemLoader as fs;
19
use allejo\stakx\FrontMatter\ExpandedValue;
20
use allejo\stakx\Service;
21
use FastRoute\RouteCollector;
22
use Psr\Http\Message\ServerRequestInterface;
23
use React\Http\Response;
24
25
class Controller
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
26
{
27
    private static $baseUrl = '';
28
    private $lastModified = [];
29
30
    /**
31
     * @internal
32
     */
33
    private function __construct()
34
    {
35
    }
36
37
    /**
38
     * Build a controller for handling a Static PageView's URL.
39
     *
40
     * @param StaticPageView $pageView
41
     * @param Compiler       $compiler
42
     *
43
     * @return \Closure
44
     */
45
    private function staticPageViewAction(StaticPageView $pageView, Compiler $compiler)
46
    {
47
        return function () use ($pageView, $compiler) {
48
            Service::setOption('currentTemplate', $pageView->getAbsoluteFilePath());
49
50
            $compiler->getTemplateBridge()->clearTemplateCache();
51
52
            if ($this->hasBeenTouched($pageView))
53
            {
54
                $pageView->readContent();
55
            }
56
57
            $mimeType = MimeDetector::getMimeType(fs::getExtension($pageView->getTargetFile()));
0 ignored issues
show
Documentation introduced by
$pageView->getTargetFile() is of type string, but the function expects a object<string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
58
59
            return new Response(
60
                200,
61
                ['Content-Type' => $mimeType],
62
                $compiler->renderStaticPageView($pageView)
63
            );
64
        };
65
    }
66
67
    /**
68
     * Build a controller for handling a Dynamic PageView's URL.
69
     *
70
     * @param DynamicPageView $pageView
71
     * @param Compiler        $compiler
72
     *
73
     * @return \Closure
74
     */
75
    private function dynamicPageViewAction(DynamicPageView $pageView, Compiler $compiler)
76
    {
77
        return function (ServerRequestInterface $request) use ($pageView, $compiler) {
78
            Service::setOption('currentTemplate', $pageView->getAbsoluteFilePath());
79
80
            $compiler->getTemplateBridge()->clearTemplateCache();
81
82
            $contentItem = self::getContentItem($pageView, $request->getUri()->getPath());
83
84
            if ($contentItem === null)
85
            {
86
                return WebServer::return404();
87
            }
88
89
            if ($this->hasBeenTouched($pageView))
90
            {
91
                $pageView->readContent();
92
            }
93
94
            if ($this->hasBeenTouched($contentItem))
0 ignored issues
show
Documentation introduced by
$contentItem is of type object<allejo\stakx\Document\CollectableItem>, but the function expects a object<allejo\stakx\Document\ReadableDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
95
            {
96
                $contentItem->readContent();
97
            }
98
99
            return WebServer::return200($compiler->renderDynamicPageView($pageView, $contentItem));
0 ignored issues
show
Documentation introduced by
$contentItem is of type object<allejo\stakx\Document\CollectableItem>, but the function expects a object<allejo\stakx\Docu...\TemplateReadyDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
100
        };
101
    }
102
103
    /**
104
     * Build a controller for handling a Repeater PageView's URL.
105
     *
106
     * @param RepeaterPageView $pageView
107
     * @param Compiler         $compiler
108
     *
109
     * @return \Closure
110
     */
111
    private function repeaterPageViewAction(RepeaterPageView $pageView, Compiler $compiler)
112
    {
113
        return function (ServerRequestInterface $request) use ($pageView, $compiler) {
114
            Service::setOption('currentTemplate', $pageView->getAbsoluteFilePath());
115
116
            $compiler->getTemplateBridge()->clearTemplateCache();
117
118
            $expandedValue = self::getExpandedValue($pageView, $request->getUri()->getPath());
119
120
            if ($expandedValue === null)
121
            {
122
                return WebServer::return404();
123
            }
124
125
            if ($this->hasBeenTouched($pageView))
126
            {
127
                $pageView->readContent();
128
            }
129
130
            return WebServer::return200($compiler->renderRepeaterPageView($pageView, $expandedValue));
131
        };
132
    }
133
134
    /**
135
     * Return the appropriate action based on a PageView's type.
136
     *
137
     * @param BasePageView|DynamicPageView|RepeaterPageView|StaticPageView $pageView
138
     * @param Compiler                                                     $compiler
139
     *
140
     * @return \Closure
141
     */
142
    private function createAction(BasePageView $pageView, Compiler $compiler)
143
    {
144
        switch ($pageView->getType())
145
        {
146
            case BasePageView::STATIC_TYPE:
147
                return $this->staticPageViewAction($pageView, $compiler);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\BasePageView> is not a sub-type of object<allejo\stakx\Document\StaticPageView>. It seems like you assume a child class of the class allejo\stakx\Document\BasePageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
148
149
            case BasePageView::DYNAMIC_TYPE:
150
                return $this->dynamicPageViewAction($pageView, $compiler);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\BasePageView> is not a sub-type of object<allejo\stakx\Document\DynamicPageView>. It seems like you assume a child class of the class allejo\stakx\Document\BasePageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
151
152
            case BasePageView::REPEATER_TYPE:
153
                return $this->repeaterPageViewAction($pageView, $compiler);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\BasePageView> is not a sub-type of object<allejo\stakx\Document\RepeaterPageView>. It seems like you assume a child class of the class allejo\stakx\Document\BasePageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
154
155
            default:
156
                return function () {
157
                    $errMsg = 'This URL type has not yet been implemented.';
158
159
                    return new Response(501, ['Content-Type' => 'text/plain'], $errMsg);
160
                };
161
        }
162
    }
163
164
    /**
165
     * Create a redirect response to forward.
166
     *
167
     * @param string $to The destination URL
168
     *
169
     * @return \Closure
170
     */
171
    private function createRedirectAction($to)
172
    {
173
        return function () use ($to) {
174
            return new Response(302, [
175
                'Location' => $to,
176
            ]);
177
        };
178
    }
179
180
    /**
181
     * Check to see if a file has been touched since we last read it.
182
     *
183
     * @param ReadableDocument $document
184
     *
185
     * @return bool True if the file has been modified since it was last accessed
186
     */
187
    private function hasBeenTouched(ReadableDocument $document)
188
    {
189
        $rPath = $document->getRelativeFilePath();
190
191
        if (!isset($this->lastModified[$rPath]))
192
        {
193
            $this->lastModified[$rPath] = $document->getLastModified();
194
195
            return true;
196
        }
197
198
        return $document->getLastModified() > $this->lastModified[$rPath];
199
    }
200
201
    /**
202
     * Create a FastRoute Dispatcher.
203
     *
204
     * @param RouteMapper $router
205
     * @param Compiler    $compiler
206
     *
207
     * @return \FastRoute\Dispatcher
208
     */
209
    public static function create(RouteMapper $router, Compiler $compiler)
210
    {
211
        self::$baseUrl = $router->getBaseUrl();
212
213
        return \FastRoute\simpleDispatcher(function (RouteCollector $r) use ($router, $compiler) {
214
            $dispatcher = new Controller();
215
216
            foreach ($router->getRedirectMapping() as $from => $to)
217
            {
218
                $r->get($from, $dispatcher->createRedirectAction($to));
219
            }
220
221
            foreach ($router->getRouteMapping() as $route => $pageView)
222
            {
223
                $r->get($route, $dispatcher->createAction($pageView, $compiler));
224
            }
225
        });
226
    }
227
228
    /**
229
     * Normalize a given URL.
230
     *
231
     * A normalized URL is one with `baseurl` stripped away from it. This is necessary because all permalinks in stakx
232
     * are handled without the base so it's necessary to be able to reference correct correct permalinks.
233
     *
234
     * @param string $url
235
     *
236
     * @return mixed
237
     */
238
    public static function normalizeUrl($url)
239
    {
240
        return str_replace(self::$baseUrl, '/', $url);
241
    }
242
243
    /**
244
     * Find a ContentItem from a Dynamic PageView or null if it doesn't exist.
245
     *
246
     * @param DynamicPageView $pageView
247
     * @param                 $permalink
248
     *
249
     * @return CollectableItem|ReadableDocument|TemplateReadyDocument|null
250
     */
251
    private static function getContentItem(DynamicPageView $pageView, $permalink)
252
    {
253
        $permalink = self::normalizeUrl($permalink);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $permalink. This often makes code more readable.
Loading history...
254
255
        foreach ($pageView->getCollectableItems() as $collectableItem)
256
        {
257
            if ($collectableItem['permalink'] === $permalink)
258
            {
259
                return $collectableItem;
260
            }
261
        }
262
263
        return null;
264
    }
265
266
    /**
267
     * Find the ExpandedValue from a Repeater PageView or null if it doesn't exist.
268
     *
269
     * @param RepeaterPageView $pageView
270
     * @param                  $permalink
271
     *
272
     * @return ExpandedValue|null
273
     */
274
    private static function getExpandedValue(RepeaterPageView $pageView, $permalink)
275
    {
276
        $url = self::normalizeUrl($permalink);
277
        $repeaterPermalinks = $pageView->getRepeaterPermalinks();
278
279
        foreach ($repeaterPermalinks as $expandedValue)
280
        {
281
            if ($expandedValue->getEvaluated() === $url)
282
            {
283
                return $expandedValue;
284
            }
285
        }
286
287
        return null;
288
    }
289
}
290