Issues (20)

Classes/System/RestApi/RestApiRequest.php (1 issue)

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 Exception;
31
use Luracast\Restler\Defaults;
32
use Luracast\Restler\Format\JsonFormat;
33
use Luracast\Restler\RestException;
34
use Luracast\Restler\Restler;
35
use stdClass;
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
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
    /**
60
     * store data from $_POST in this property
61
     *
62
     * Attention:
63
     * This property must be static, because it can happen, that some REST-API-calls
64
     * will be called recursive, so we MUST store the 'really original' data
65
     */
66
    private static array $originalPostVars;
67
68
    /**
69
     * store data from $_SERVER in this property
70
     *
71
     * Attention:
72
     * This property must be static, because it can happen, that some REST-API-calls
73
     * will be called recursive, so we MUST store the 'really original' data
74
     */
75
    private static array $originalServerSettings;
76
77
    private array $restApiGetData;
78
79
    private array $restApiPostData;
80
81
    /**
82
     * This property defines the request-uri (without GET-params, e.g. '/api/products/320'), which should be called
83
     */
84
    private string $restApiRequestUri;
85
86
    /**
87
     * This property defines the request-method (e.g. 'GET', 'POST', 'PUT' or 'DELETE'), which should be used while calling the rest-api
88
     */
89
    private string $restApiRequestMethod;
90
91
    private RestApiRequestScope $restApiRequestScope;
92
93
    private Cache $typo3Cache;
94
95
    /***************************************************************************************************************************/
96
    /***************************************************************************************************************************/
97
    /* Block of methods, which MUST be overridden from parent-class (otherwise this class can not work) *************************/
98
    /***************************************************************************************************************************/
99
    /***************************************************************************************************************************/
100
    /**
101 6
     * Override parent method...because we don't want to call it!
102
     * The original method would set some properties (e.g. set this object into static properties of global classes)
103 6
     */
104 6
    public function __construct(RestApiRequestScope $restApiRequestScope, Cache $typo3Cache)
105 6
    {
106
        $this->restApiRequestScope = $restApiRequestScope;
107
        $this->typo3Cache = $typo3Cache;
108
    }
109
110
    /**
111
     * Override parent method...because we don't want to call it (the original method would cache the determined routes)!
112
     */
113
    public function __destruct()
114
    {
115
    }
116
117
    /***************************************************************************************************************************/
118
    /***************************************************************************************************************************/
119
    /* Block of methods, which does NOT override logic from parent-class *******************************************************/
120
    /***************************************************************************************************************************/
121
    /***************************************************************************************************************************/
122
    /**
123
     * @param array|stdClass $getData
124
     * @param array|stdClass $postData
125
     * @return mixed can be a primitive or array or object
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
174
        return $requestData;
175
    }
176
177
    /**
178
     * Override parent method...because we must return the data of the REST-API request and we need NO exception-handling!
179
     *
180
     * @return mixed can be a primitive or array or object
181
     */
182
    public function handle()
183
    {
184
        // get information about the REST-request
185
        $this->get();
186
187
        if ($this->requestMethod === 'GET' && $this->typo3Cache->hasCacheEntry($this->url, $_GET)) {
188
            return $this->handleRequestByTypo3Cache();
189
        }
190
191
        // if no cache exist: restler should handle the request
192
        if (Defaults::$useVendorMIMEVersioning) {
193
            $this->responseFormat = $this->negotiateResponseFormat();
194
        }
195
196
        $this->route();
197
        $this->negotiate();
198
        $this->preAuthFilter();
199
        $this->authenticate();
200
        $this->postAuthFilter();
201
        $this->validate();
202
        $this->preCall();
203
        $this->call();
204
        $this->compose();
205
        $this->postCall();
206
207
        if ($this->responseFormat instanceof JsonFormat) {
208
            // return stdClass-object (instead of an array)
209
            return $this->getRestApiJsonFormat()
210
                ->decode($this->responseData);
211
        }
212
213
        return $this->responseFormat->decode($this->responseData);
214
    }
215
216
    /**
217
     * Return class, which can decode a JSON-string into a stdClass-object (instead of an array)
218
     */
219
    protected function getRestApiJsonFormat(): RestApiJsonFormat
220
    {
221
        return GeneralUtility::makeInstance(RestApiJsonFormat::class);
222
    }
223
224
    /**
225
     * override postCall so that we can cache response via TYPO3-caching-framework - if it's possible
226
     */
227
    protected function postCall()
228
    {
229
        parent::postCall();
230
231
        if ($this->typo3Cache->isResponseCacheableByTypo3Cache($this->requestMethod, $this->apiMethodInfo->metadata)) {
232
            $this->typo3Cache->cacheResponseByTypo3Cache(
233
                $this->responseCode,
234
                $this->url,
235
                $_GET,
236
                $this->apiMethodInfo->metadata,
237
                $this->responseData,
238
                get_class($this->responseFormat),
239
                [] // we don't know which headers would be 'normally' send - because this is an internal REST-API-call
240
            );
241
        }
242
    }
243
244
    /**
245
     * @param array|stdClass $data
246 6
     * @return array
247
     */
248 6
    private function convertDataToArray($data)
249 6
    {
250
        if (!$data) {
251 3
            return [];
252 3
        }
253
254
        if (is_array($data)) {
255
            return $data;
256
        }
257
258
        if ($data instanceof stdClass) {
0 ignored issues
show
$data is always a sub-type of stdClass.
Loading history...
259
            return json_decode(json_encode($data), true); // convert stdClass to array
260
        }
261
    }
262
263
    /**
264
     * @return mixed
265
     */
266
    private function handleRequestByTypo3Cache()
267
    {
268
        $cacheEntry = $this->typo3Cache->getCacheEntry($this->url, $_GET);
269
        $this->responseCode = $cacheEntry['responseCode'];
270
        $this->responseData = $cacheEntry['responseData'];
271
        $this->responseFormat = new $cacheEntry['responseFormatClass']();
272
273
        // send data to client
274
        if ($this->responseFormat instanceof JsonFormat) {
275
            // return stdClass-object (instead of an array)
276
            return $this->getRestApiJsonFormat()
277
                ->decode($this->responseData);
278
        }
279
280
        return $this->responseFormat->decode($this->responseData);
281
    }
282
283 6
    /**
284
     * Override (the stored) data of $_GET, $_POST and $_SERVER (which are used in several restler-PHP-classes) and the original
285 6
     * REST-API-Request-object, because this data/object 'defines' the REST-API-request, which we want to call
286 6
     */
287 6
    private function overrideOriginalRestApiRequest(): void
288 6
    {
289
        $_GET = $this->restApiGetData;
290 6
        $_POST = $this->restApiPostData;
291
        $_SERVER['REQUEST_METHOD'] = $this->restApiRequestMethod;
292
        $_SERVER['REQUEST_URI'] = $this->restApiRequestUri;
293 6
294
        if ($this->restApiRequestMethod !== 'POST' && $this->restApiRequestMethod !== 'PUT') {
295
            // content-type and content-length should only exist when request-method is
296 6
            // POST or PUT (because in this case there can be the request-data in the body)
297
            if (array_key_exists('CONTENT_TYPE', $_SERVER)) {
298
                unset($_SERVER['CONTENT_TYPE']);
299 6
            }
300
301
            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
302
                unset($_SERVER['HTTP_CONTENT_TYPE']);
303
            }
304 6
305 6
            if (array_key_exists('CONTENT_LENGTH', $_SERVER)) {
306
                unset($_SERVER['CONTENT_LENGTH']);
307
            }
308
        }
309
310
        $this->restApiRequestScope->overrideOriginalRestApiRequest($this);
311
        $this->restApiRequestScope->removeRestApiAuthenticationObjects();
312 6
313 6
        /**
314 6
         * add all authentication-classes:
315
         *  - we must add all authentication-classes, because the authentication-classes are stored in this object
316
         *  - we don't must add all REST-API-classes, because the REST-API-classes are not stored in this object
317
         */
318
        $this->authClasses = $this->restApiRequestScope->getOriginalRestApiRequest()
319 6
            ->_authClasses ?? [];
320
    }
321 6
322 6
    /**
323 6
     * Restore (the overridden) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
324 6
     */
325 6
    private function restoreOriginalRestApiRequest(): void
326 6
    {
327
        $_GET = self::$originalGetVars;
328
        $_POST = self::$originalPostVars;
329
        $_SERVER = self::$originalServerSettings;
330
        $this->restApiRequestScope->restoreOriginalRestApiRequest();
331 6
        $this->restApiRequestScope->restoreOriginalRestApiAuthenticationObjects();
332
    }
333 6
334 1
    /**
335 1
     * Store (the original) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
336 1
     */
337 1
    private function storeOriginalRestApiRequest(): void
338 1
    {
339
        if (!isset(self::$originalServerSettings)) {
340 6
            self::$originalGetVars = $_GET;
341
            self::$originalPostVars = $_POST;
342
            self::$originalServerSettings = $_SERVER;
343
            $this->restApiRequestScope->storeOriginalRestApiRequest();
344
            $this->restApiRequestScope->storeOriginalRestApiAuthenticationObjects();
345
        }
346
    }
347
}
348