Passed
Push — main ( 04a742...5b603f )
by
unknown
04:37 queued 12s
created

RestApiRequest::overrideOriginalRestApiRequest()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6.2373

Importance

Changes 0
Metric Value
cc 6
eloc 15
c 0
b 0
f 0
nc 9
nop 0
dl 0
loc 31
ccs 13
cts 16
cp 0.8125
crap 6.2373
rs 9.2222
1
<?php
2
3
namespace Aoe\Restler\System\RestApi;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2015 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 Luracast\Restler\Restler;
31
use Luracast\Restler\RestException;
32
use Luracast\Restler\Defaults;
33
use Luracast\Restler\Format\JsonFormat;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
use Exception;
36
use stdClass;
37
38
/**
39
 * This class represents a single REST-API-request, which can be called from PHP.
40
 * For each REST-API request, we need a new object of this class, because restler stores some data in this object
41
 *
42
 * The logic/idea behind this class is:
43
 * 1. We override the data of $_GET, $_POST and $_SERVER and we override the REST-API-request-object (aka Restler-object)
44
 *    ...because this data/object 'defines' the REST-API-request, which we want to call
45
 * 2. We call/start restler to handle the REST-API-request
46
 * 3. We return the result of the 'called' REST-API-request - instead of sending the result to the client
47
 */
48
class RestApiRequest extends Restler
49
{
50
    /**
51
     * store data from $_GET in this property
52
     *
53
     * Attention:
54
     * This property must be static, because it can happen, that some REST-API-calls
55
     * will be called recursive, so we MUST store the 'really original' data
56
     */
57
    private static array $originalGetVars;
58
    /**
59
     * store data from $_POST in this property
60
     *
61
     * Attention:
62
     * This property must be static, because it can happen, that some REST-API-calls
63
     * will be called recursive, so we MUST store the 'really original' data
64
     */
65
    private static array $originalPostVars;
66
    /**
67
     * store data from $_SERVER in this property
68
     *
69
     * Attention:
70
     * This property must be static, because it can happen, that some REST-API-calls
71
     * will be called recursive, so we MUST store the 'really original' data
72
     */
73
    private static array $originalServerSettings;
74
75
    private array $restApiGetData;
76
    private array $restApiPostData;
77
    /**
78
     * This property defines the request-uri (without GET-params, e.g. '/api/products/320'), which should be called
79
     */
80
    private string $restApiRequestUri;
81
    /**
82
     * This property defines the request-method (e.g. 'GET', 'POST', 'PUT' or 'DELETE'), which should be used while calling the rest-api
83
     */
84
    private string $restApiRequestMethod;
85
86
    private RestApiRequestScope $restApiRequestScope;
87
88
    private Cache $typo3Cache;
89
90
91
92
    /***************************************************************************************************************************/
93
    /***************************************************************************************************************************/
94
    /* Block of methods, which MUST be overridden from parent-class (otherwise this class can not work) *************************/
95
    /***************************************************************************************************************************/
96
    /***************************************************************************************************************************/
97
    /**
98
     * Override parent method...because we don't want to call it!
99
     * The original method would set some properties (e.g. set this object into static properties of global classes)
100
     */
101 6
    public function __construct(RestApiRequestScope $restApiRequestScope, Cache $typo3Cache)
102
    {
103 6
        $this->restApiRequestScope = $restApiRequestScope;
104 6
        $this->typo3Cache = $typo3Cache;
105 6
    }
106
107
    /**
108
     * Override parent method...because we don't want to call it (the original method would cache the determined routes)!
109
     */
110
    public function __destruct()
111
    {
112
    }
113
114
115
116
    /***************************************************************************************************************************/
117
    /***************************************************************************************************************************/
118
    /* Block of methods, which does NOT override logic from parent-class *******************************************************/
119
    /***************************************************************************************************************************/
120
    /***************************************************************************************************************************/
121
    /**
122
     * @param array|stdClass $getData
123
     * @param array|stdClass $postData
124
     * @return mixed can be a primitive or array or object
125
     * @throws RestException
126
     */
127 6
    public function executeRestApiRequest(string $requestMethod, string $requestUri, $getData = null, $postData = null)
128
    {
129 6
        $this->restApiRequestMethod = $requestMethod;
130 6
        $this->restApiRequestUri = $requestUri;
131 6
        $this->restApiGetData = $this->convertDataToArray($getData);
132 6
        $this->restApiPostData = $this->convertDataToArray($postData);
133
134 6
        $this->storeOriginalRestApiRequest();
135 6
        $this->overrideOriginalRestApiRequest();
136
137
        try {
138 6
            $result = $this->handle();
139 2
            $this->restoreOriginalRestApiRequest();
140 2
            return $result;
141 4
        } catch (RestException $restException) {
142 2
            $this->restoreOriginalRestApiRequest();
143 2
            throw $restException;
144 2
        } catch (Exception $exception) {
145 2
            $this->restoreOriginalRestApiRequest();
146 2
            throw new RestException('500', $exception->getMessage(), [], $exception);
147
        }
148
    }
149
150
    /**
151
     * Override parent method...because we don't want to call it (the original method would send some headers to the client)!
152
     */
153
    public function composeHeaders(RestException $e = null)
154
    {
155
    }
156
157
    /**
158
     * Override parent method...because we must return the request-data of THIS REST-API request!
159
     * The original method would return the request-data of the ORIGINAL called REST-API request
160
     *
161
     * @param boolean $includeQueryParameters
162
     */
163
    public function getRequestData($includeQueryParameters = true): array
164
    {
165
        $requestData = [];
166
        if ($this->restApiRequestMethod == 'PUT' || $this->restApiRequestMethod == 'PATCH' || $this->restApiRequestMethod == 'POST') {
167
            $requestData = array_merge($this->restApiPostData, [Defaults::$fullRequestDataName => $this->restApiPostData]);
168
        }
169
170
        if ($includeQueryParameters) {
171
            return $requestData + $this->restApiGetData;
172
        }
173
        return $requestData;
174
    }
175
176
    /**
177
     * Override parent method...because we must return the data of the REST-API request and we need NO exception-handling!
178
     *
179
     * @return mixed can be a primitive or array or object
180
     */
181
    public function handle()
182
    {
183
        // get information about the REST-request
184
        $this->get();
185
186
        if ($this->requestMethod === 'GET' && $this->typo3Cache->hasCacheEntry($this->url, $_GET)) {
187
            return $this->handleRequestByTypo3Cache();
188
        }
189
190
        // if no cache exist: restler should handle the request
191
        if (Defaults::$useVendorMIMEVersioning) {
192
            $this->responseFormat = $this->negotiateResponseFormat();
193
        }
194
        $this->route();
195
        $this->negotiate();
196
        $this->preAuthFilter();
197
        $this->authenticate();
198
        $this->postAuthFilter();
199
        $this->validate();
200
        $this->preCall();
201
        $this->call();
202
        $this->compose();
203
        $this->postCall();
204
205
        if ($this->responseFormat instanceof JsonFormat) {
206
            // return stdClass-object (instead of an array)
207
            return $this->getRestApiJsonFormat()
208
                ->decode($this->responseData);
209
        }
210
        return $this->responseFormat->decode($this->responseData);
211
    }
212
213
    /**
214
     * Return class, which can decode a JSON-string into a stdClass-object (instead of an array)
215
     */
216
    protected function getRestApiJsonFormat(): RestApiJsonFormat
217
    {
218
        return GeneralUtility::makeInstance(RestApiJsonFormat::class);
219
    }
220
221
    /**
222
     * override postCall so that we can cache response via TYPO3-caching-framework - if it's possible
223
     */
224
    protected function postCall()
225
    {
226
        parent::postCall();
227
228
        if ($this->typo3Cache->isResponseCacheableByTypo3Cache($this->requestMethod, $this->apiMethodInfo->metadata)) {
229
            $this->typo3Cache->cacheResponseByTypo3Cache(
230
                $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

230
                /** @scrutinizer ignore-type */ $this->responseCode,
Loading history...
231
                $this->url,
232
                $_GET,
233
                $this->apiMethodInfo->metadata,
234
                $this->responseData,
235
                get_class($this->responseFormat),
236
                [] // we don't know which headers would be 'normally' send - because this is an internal REST-API-call
237
            );
238
        }
239
    }
240
241
    /**
242
     * @param array|stdClass $data
243
     * @return array
244
     * @throws RestException
245
     */
246 6
    private function convertDataToArray($data)
247
    {
248 6
        if (!$data) {
249 6
            return [];
250
        }
251 3
        if (is_array($data)) {
252 3
            return $data;
253
        }
254
        if ($data instanceof stdClass) {
0 ignored issues
show
introduced by
$data is always a sub-type of stdClass.
Loading history...
255
            return json_decode(json_encode($data), true); // convert stdClass to array
256
        }
257
    }
258
259
    /**
260
     * @return mixed
261
     * @throws RestException
262
     */
263
    private function handleRequestByTypo3Cache()
264
    {
265
        $cacheEntry = $this->typo3Cache->getCacheEntry($this->url, $_GET);
266
        $this->responseCode = $cacheEntry['responseCode'];
267
        $this->responseData = $cacheEntry['responseData'];
268
        $this->responseFormat = new $cacheEntry['responseFormatClass']();
269
270
        // send data to client
271
        if ($this->responseFormat instanceof JsonFormat) {
272
            // return stdClass-object (instead of an array)
273
            return $this->getRestApiJsonFormat()
274
                ->decode($this->responseData);
275
        }
276
        return $this->responseFormat->decode($this->responseData);
277
    }
278
279
    /**
280
     * Override (the stored) data of $_GET, $_POST and $_SERVER (which are used in several restler-PHP-classes) and the original
281
     * REST-API-Request-object, because this data/object 'defines' the REST-API-request, which we want to call
282
     */
283 6
    private function overrideOriginalRestApiRequest()
284
    {
285 6
        $_GET = $this->restApiGetData;
286 6
        $_POST = $this->restApiPostData;
287 6
        $_SERVER['REQUEST_METHOD'] = $this->restApiRequestMethod;
288 6
        $_SERVER['REQUEST_URI'] = $this->restApiRequestUri;
289
290 6
        if ($this->restApiRequestMethod !== 'POST' && $this->restApiRequestMethod !== 'PUT') {
291
            // content-type and content-length should only exist when request-method is
292
            // POST or PUT (because in this case there can be the request-data in the body)
293 6
            if (array_key_exists('CONTENT_TYPE', $_SERVER)) {
294
                unset($_SERVER['CONTENT_TYPE']);
295
            }
296 6
            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
297
                unset($_SERVER['HTTP_CONTENT_TYPE']);
298
            }
299 6
            if (array_key_exists('CONTENT_LENGTH', $_SERVER)) {
300
                unset($_SERVER['CONTENT_LENGTH']);
301
            }
302
        }
303
304 6
        $this->restApiRequestScope->overrideOriginalRestApiRequest($this);
305 6
        $this->restApiRequestScope->removeRestApiAuthenticationObjects();
306
307
        /**
308
         * add all authentication-classes:
309
         *  - we must add all authentication-classes, because the authentication-classes are stored in this object
310
         *  - we don't must add all REST-API-classes, because the REST-API-classes are not stored in this object
311
         */
312 6
        $this->authClasses = $this->restApiRequestScope->getOriginalRestApiRequest()
313 6
            ->_authClasses ?? [];
314 6
    }
315
316
    /**
317
     * Restore (the overridden) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
318
     */
319 6
    private function restoreOriginalRestApiRequest()
320
    {
321 6
        $_GET = self::$originalGetVars;
322 6
        $_POST = self::$originalPostVars;
323 6
        $_SERVER = self::$originalServerSettings;
324 6
        $this->restApiRequestScope->restoreOriginalRestApiRequest();
325 6
        $this->restApiRequestScope->restoreOriginalRestApiAuthenticationObjects();
326 6
    }
327
328
    /**
329
     * Store (the original) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
330
     */
331 6
    private function storeOriginalRestApiRequest()
332
    {
333 6
        if (!isset(self::$originalServerSettings)) {
334 1
            self::$originalGetVars = $_GET;
335 1
            self::$originalPostVars = $_POST;
336 1
            self::$originalServerSettings = $_SERVER;
337 1
            $this->restApiRequestScope->storeOriginalRestApiRequest();
338 1
            $this->restApiRequestScope->storeOriginalRestApiAuthenticationObjects();
339
        }
340 6
    }
341
}
342