RestlerExtended::handle()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 4
eloc 7
nc 3
nop 0
dl 0
loc 17
ccs 0
cts 5
cp 0
crap 20
rs 10
c 2
b 1
f 0
1
<?php
2
3
namespace Aoe\Restler\System\Restler;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2024 AOE GmbH <[email protected]>
9
 *
10
 *  All rights reserved
11
 *
12
 *  This script is part of the TYPO3 project. The TYPO3 project is
13
 *  free software; you can redistribute it and/or modify
14
 *  it under the terms of the GNU General Public License as published by
15
 *  the Free Software Foundation; either version 3 of the License, or
16
 *  (at your option) any later version.
17
 *
18
 *  The GNU General Public License can be found at
19
 *  http://www.gnu.org/copyleft/gpl.html.
20
 *
21
 *  This script is distributed in the hope that it will be useful,
22
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 *  GNU General Public License for more details.
25
 *
26
 *  This copyright notice MUST APPEAR in all copies of the script!
27
 ***************************************************************/
28
29
use Aoe\Restler\System\TYPO3\Cache;
30
use Exception;
31
use Luracast\Restler\Defaults;
32
use Luracast\Restler\RestException;
33
use Luracast\Restler\Restler;
34
use Luracast\Restler\Scope;
35
use Psr\Http\Message\ServerRequestInterface;
36
use TYPO3\CMS\Core\Site\Entity\Site;
37
38
class RestlerExtended extends Restler
39
{
40
    /***************************************************************************************************************************/
41
    /***************************************************************************************************************************/
42
    /* Block of methods, which MUST be overriden from parent-class (otherwise we can't use the TYPO3-caching-framework) ********/
43
    /***************************************************************************************************************************/
44
    /***************************************************************************************************************************/
45
    /**
46
     * @param bool $productionMode    When set to false, it will run in
47
     *                                   debug mode and parse the class files
48
     *                                   every time to map it to the URL
49
     *
50
     * @param bool $refreshCache      will update the cache when set to true
51
     * @param ServerRequestInterface $request frontend request
52
     */
53
    public function __construct(
54
        private readonly Cache $typo3Cache,
55
        $productionMode = false,
56
        $refreshCache = false,
57
        private readonly ?ServerRequestInterface $request = null
58
    ) {
59
        parent::__construct($productionMode, $refreshCache);
60
61
        if (interface_exists(\Psr\Http\Server\MiddlewareInterface::class)) {
62
            // restler uses echo;die otherwise and then Typo3 standard mechanisms will not be called
63
            Defaults::$returnResponse = true;
64
        }
65
66
        // adds format support for application/hal+json
67
        Scope::$classAliases['HalJsonFormat'] = \Aoe\Restler\System\Restler\Format\HalJsonFormat::class;
68
        $this->setSupportedFormats('HalJsonFormat');
69
70
        // set pathes from request if present
71
        if ($this->request !== null) {
72
            $this->url = $this->getPath();
73
        }
74
    }
75
76
    /**
77
     * Main function for processing the api request
78
     * and return the response
79
     */
80
    public function handle()
81
    {
82
        try {
83
            // get information about the REST-request (this is required to check, if we can handle the REST-request by TYPO3-cache)
84
            $this->get();
85
        } catch (RestException) {
86
            // Exception occurred (e.g. 'Error encoding/decoding JSON') during getting information about REST-request:
87
            // Let restler handle the error (e.g. that JSON could not be read) - and NOT the TYPO3-exception-handling!
88
            return parent::handle();
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::handle() targeting Luracast\Restler\Restler::handle() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
89
        }
90
91
        if ($this->requestMethod === 'GET' && $this->typo3Cache->hasCacheEntry($this->url, $_GET)) {
92
            return $this->handleRequestByTypo3Cache();
93
        }
94
95
        // if no cache exist: restler should handle the request
96
        return parent::handle();
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::handle() targeting Luracast\Restler\Restler::handle() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
97
    }
98
99
    /**
100
     * Rewrap the not accessible private stream in a new one.
101
     *
102
     * @return bool|resource
103
     */
104
    public function getRequestStream()
105
    {
106
        $stream = fopen('php://temp', 'wb+');
107
        fwrite($stream, (string) $this->request->getBody());
0 ignored issues
show
Bug introduced by
The method getBody() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
        fwrite($stream, (string) $this->request->/** @scrutinizer ignore-call */ getBody());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
108
        fseek($stream, 0, SEEK_SET);
109
110
        return $stream;
111
    }
112
113
    /**
114
     * Determine path (and baseUrl) for current request.
115
     */
116
    protected function getPath(): string
117
    {
118
        if ($this->request !== null) {
119
            // set base path depending on site config
120
            $site = $this->request->getAttribute('site');
121
            if ($site !== null && $site instanceof Site) {
122
                $siteBasePath = $this->request->getAttribute('site')
123
                    ->getBase()
124
                    ->getPath();
125
                if ($siteBasePath !== '/' && $siteBasePath[-1] !== '/') {
126
                    $siteBasePath .= '/';
127
                }
128
            } else {
129
                $siteBasePath = '/';
130
            }
131
132
            $this->baseUrl = (string) $this->request->getUri()
133
                ->withQuery('')
134
                ->withPath($siteBasePath);
135
136
            // set url with base path removed
137
            return rtrim(
138
                (string) preg_replace('%^' . preg_quote((string) $siteBasePath, '%') . '%', '', $this->request->getUri()->getPath()),
139
                '/'
140
            );
141
        }
142
143
        return parent::getPath();
144
    }
145
146
    /**
147
     * override postCall so that we can cache response via TYPO3-caching-framework - if it's possible
148
     */
149
    protected function postCall()
150
    {
151
        parent::postCall();
152
153
        if ($this->typo3Cache->isResponseCacheableByTypo3Cache($this->requestMethod, $this->apiMethodInfo->metadata)) {
154
            $this->typo3Cache->cacheResponseByTypo3Cache(
155
                $this->responseCode,
0 ignored issues
show
Bug introduced by
It seems like $this->responseCode can also be of type null; however, parameter $responseCode of Aoe\Restler\System\TYPO3...eResponseByTypo3Cache() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
                /** @scrutinizer ignore-type */ $this->responseCode,
Loading history...
156
                $this->url,
157
                $_GET,
158
                $this->apiMethodInfo->metadata,
159
                $this->responseData,
160
                $this->responseFormat::class,
161
                headers_list()
162
            );
163
        }
164
    }
165
166
    private function handleRequestByTypo3Cache(): string
167
    {
168
        $cacheEntry = $this->typo3Cache->getCacheEntry($this->url, $_GET);
169
170
        if (count($cacheEntry['responseHeaders']) === 0) {
171
            // the cache is from an internal REST-API-call, so we must manually send some headers
172
            @header('Content-Type: application/json; charset=utf-8');
0 ignored issues
show
Bug introduced by
Are you sure the usage of header('Content-Type: ap...n/json; charset=utf-8') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for header(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

172
            /** @scrutinizer ignore-unhandled */ @header('Content-Type: application/json; charset=utf-8');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
173
            @header('Cache-Control: private, no-cache, no-store, must-revalidate');
0 ignored issues
show
Bug introduced by
Are you sure the usage of header('Cache-Control: p...tore, must-revalidate') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
174
        } else {
175
            // set/manipulate headers
176
            foreach ($cacheEntry['responseHeaders'] as $responseHeader) {
177
                if (str_starts_with((string) $responseHeader, 'Expires:')) {
178
                    if ($cacheEntry['frontendCacheExpires'] === 0) {
179
                        $expires = $cacheEntry['frontendCacheExpires'];
180
                    } else {
181
                        $expires = gmdate('D, d M Y H:i:s \G\M\T', time() + $cacheEntry['frontendCacheExpires']);
182
                    }
183
184
                    @header('Expires: ' . $expires);
0 ignored issues
show
Bug introduced by
Are you sure the usage of header('Expires: ' . $expires) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
185
                } else {
186
                    @header($responseHeader);
0 ignored issues
show
Bug introduced by
Are you sure the usage of header($responseHeader) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
187
                }
188
            }
189
        }
190
191
        @header('X-Cached-By-Typo3: 1');
0 ignored issues
show
Bug introduced by
Are you sure the usage of header('X-Cached-By-Typo3: 1') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
192
193
        // send data to client
194
        $this->responseCode = $cacheEntry['responseCode'];
195
        $this->responseData = $cacheEntry['responseData'];
196
        return $this->respond();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->respond() could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
197
    }
198
}
199