Passed
Push — main ( 51ac7d...b8c594 )
by
unknown
16:34
created

Classes/System/RestApi/RestApiRequest.php (2 issues)

1
<?php
2
namespace Aoe\Restler\System\RestApi;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2015 AOE GmbH <[email protected]>
8
 *
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use Aoe\Restler\System\TYPO3\Cache as Typo3Cache;
29
use Luracast\Restler\Restler;
30
use Luracast\Restler\RestException;
31
use Luracast\Restler\Defaults;
32
use Luracast\Restler\Format\JsonFormat;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use Exception;
35
use stdClass;
36
37
/**
38
 * This class represents a single REST-API-request, which can be called from PHP.
39
 * For each REST-API request, we need a new object of this class, because restler stores some data in this object
40
 *
41
 * The logic/idea behind this class is:
42
 * 1. We override the data of $_GET, $_POST and $_SERVER and we override the REST-API-request-object (aka Restler-object)
43
 *    ...because this data/object 'defines' the REST-API-request, which we want to call
44
 * 2. We call/start restler to handle the REST-API-request
45
 * 3. We return the result of the 'called' REST-API-request - instead of sending the result to the client
46
 */
47
class RestApiRequest extends Restler
48
{
49
    /**
50
     * store data from $_GET in this property
51
     *
52
     * Attention:
53
     * This property must be static, because it can happen, that some REST-API-calls
54
     * will be called recursive, so we MUST store the 'really original' data
55
     *
56
     * @var array
57
     */
58
    private static $originalGetVars;
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
     * @var array
67
     */
68
    private static $originalPostVars;
69
    /**
70
     * store data from $_SERVER in this property
71
     *
72
     * Attention:
73
     * This property must be static, because it can happen, that some REST-API-calls
74
     * will be called recursive, so we MUST store the 'really original' data
75
     *
76
     * @var array
77
     */
78
    private static $originalServerSettings;
79
80
    /**
81
     * @var array
82
     */
83
    private $restApiGetData;
84
    /**
85
     * @var array
86
     */
87
    private $restApiPostData;
88
    /**
89
     * This property defines the request-uri (without GET-params, e.g. '/api/products/320'), which should be called
90
     *
91
     * @var string
92
     */
93
    private $restApiRequestUri;
94
    /**
95
     * This property defines the request-method (e.g. 'GET', 'POST', 'PUT' or 'DELETE'), which should be used while calling the rest-api
96
     *
97
     * @var string
98
     */
99
    private $restApiRequestMethod;
100
    /**
101
     * @var RestApiRequestScope
102
     */
103
    private $restApiRequestScope;
104
    /**
105
     * @var Typo3Cache
106
     */
107
    private $typo3Cache;
108
109
110
111
    /***************************************************************************************************************************/
112
    /***************************************************************************************************************************/
113
    /* Block of methods, which does NOT override logic from parent-class *******************************************************/
114
    /***************************************************************************************************************************/
115
    /***************************************************************************************************************************/
116
    /**
117
     * @param string $requestMethod
118
     * @param string $requestUri
119
     * @param array|stdClass $getData
120
     * @param array|stdClass $postData
121
     * @return mixed can be a primitive or array or object
122
     * @throws RestException
123
     */
124 6
    public function executeRestApiRequest($requestMethod, $requestUri, $getData = null, $postData = null)
125
    {
126 6
        $this->restApiRequestMethod = $requestMethod;
127 6
        $this->restApiRequestUri = $requestUri;
128 6
        $this->restApiGetData = $this->convertDataToArray($getData);
129 6
        $this->restApiPostData = $this->convertDataToArray($postData);
130
131 6
        $this->storeOriginalRestApiRequest();
132 6
        $this->overrideOriginalRestApiRequest();
133
134
        try {
135 6
            $result = $this->handle();
136 2
            $this->restoreOriginalRestApiRequest();
137 2
            return $result;
138 4
        } catch (RestException $e) {
139 2
            $this->restoreOriginalRestApiRequest();
140 2
            throw $e;
141 2
        } catch (Exception $e) {
142 2
            $this->restoreOriginalRestApiRequest();
143 2
            throw new RestException(500, $e->getMessage(), array(), $e);
144
        }
145
    }
146
147
    /**
148
     * Return class, which can decode a JSON-string into a stdClass-object (instead of an array)
149
     *
150
     * @return RestApiJsonFormat
151
     */
152
    protected function getRestApiJsonFormat()
153
    {
154
        return GeneralUtility::makeInstance(RestApiJsonFormat::class);
155
    }
156
157
    /**
158
     * @param array|stdClass $data
159
     * @return array
160
     * @throws RestException
161
     */
162 6
    private function convertDataToArray($data)
163
    {
164 6
        if ($data === null) {
165 3
            return array();
166
        }
167 3
        if (is_array($data)) {
168 3
            return $data;
169
        }
170
        if ($data instanceof stdClass) {
0 ignored issues
show
$data is always a sub-type of stdClass.
Loading history...
171
            return json_decode(json_encode($data), true); // convert stdClass to array
172
        }
173
        throw new RestException(500, 'data must be type of null, array or stdClass');
174
    }
175
176
    /**
177
     * @return string
178
     */
179
    private function handleRequestByTypo3Cache()
180
    {
181
        $cacheEntry = $this->typo3Cache->getCacheEntry($this->url, $_GET);
182
        $this->responseCode = $cacheEntry['responseCode'];
183
        $this->responseData = $cacheEntry['responseData'];
184
        $this->responseFormat = new $cacheEntry['responseFormatClass']();
185
186
        // send data to client
187
        if ($this->responseFormat instanceof JsonFormat) {
188
            // return stdClass-object (instead of an array)
189
            return $this->getRestApiJsonFormat()->decode($this->responseData);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getRestApi...de($this->responseData) returns the type stdClass which is incompatible with the documented return type string.
Loading history...
190
        }
191
        return $this->responseFormat->decode($this->responseData);
192
    }
193
194
    /**
195
     * Override (the stored) data of $_GET, $_POST and $_SERVER (which are used in several restler-PHP-classes) and the original
196
     * REST-API-Request-object, because this data/object 'defines' the REST-API-request, which we want to call
197
     *
198
     * @return void
199
     */
200 6
    private function overrideOriginalRestApiRequest()
201
    {
202 6
        $_GET = $this->restApiGetData;
203 6
        $_POST = $this->restApiPostData;
204 6
        $_SERVER['REQUEST_METHOD'] = $this->restApiRequestMethod;
205 6
        $_SERVER['REQUEST_URI'] = $this->restApiRequestUri;
206
207 6
        if ($this->restApiRequestMethod !== 'POST' && $this->restApiRequestMethod !== 'PUT') {
208
            // content-type and content-length should only exist when request-method is
209
            // POST or PUT (because in this case there can be the request-data in the body)
210 6
            if (array_key_exists('CONTENT_TYPE', $_SERVER)) {
211
                unset($_SERVER['CONTENT_TYPE']);
212
            }
213 6
            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
214
                unset($_SERVER['HTTP_CONTENT_TYPE']);
215
            }
216 6
            if (array_key_exists('CONTENT_LENGTH', $_SERVER)) {
217
                unset($_SERVER['CONTENT_LENGTH']);
218
            }
219
        }
220
221 6
        $this->restApiRequestScope->overrideOriginalRestApiRequest($this);
222 6
        $this->restApiRequestScope->removeRestApiAuthenticationObjects();
223
224
        /**
225
         * add all authentication-classes:
226
         *  - we must add all authentication-classes, because the authentication-classes are stored in this object
227
         *  - we don't must add all REST-API-classes, because the REST-API-classes are not stored in this object
228
         */
229 6
        $this->authClasses = $this->restApiRequestScope->getOriginalRestApiRequest()->_authClasses;
230 6
    }
231
232
    /**
233
     * Restore (the overridden) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
234
     * @return void
235
     */
236 6
    private function restoreOriginalRestApiRequest()
237
    {
238 6
        $_GET = self::$originalGetVars;
239 6
        $_POST = self::$originalPostVars;
240 6
        $_SERVER = self::$originalServerSettings;
241 6
        $this->restApiRequestScope->restoreOriginalRestApiRequest();
242 6
        $this->restApiRequestScope->restoreOriginalRestApiAuthenticationObjects();
243 6
    }
244
245
    /**
246
     * Store (the original) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
247
     * @return void
248
     */
249 6
    private function storeOriginalRestApiRequest()
250
    {
251 6
        if (false === isset(self::$originalServerSettings)) {
252 1
            self::$originalGetVars = $_GET;
253 1
            self::$originalPostVars = $_POST;
254 1
            self::$originalServerSettings = $_SERVER;
255 1
            $this->restApiRequestScope->storeOriginalRestApiRequest();
256 1
            $this->restApiRequestScope->storeOriginalRestApiAuthenticationObjects();
257
        }
258 6
    }
259
260
261
262
    /***************************************************************************************************************************/
263
    /***************************************************************************************************************************/
264
    /* Block of methods, which MUST be overriden from parent-class (otherwise this class can not work) *************************/
265
    /***************************************************************************************************************************/
266
    /***************************************************************************************************************************/
267
    /**
268
     * Override parent method...because we don't want to call it!
269
     * The original method would set some properties (e.g. set this object into static properties of global classes)
270
     *
271
     * @param RestApiRequestScope $restApiRequestScope
272
     * @param Typo3Cache $typo3Cache
273
     */
274 6
    public function __construct(RestApiRequestScope $restApiRequestScope, Typo3Cache $typo3Cache)
275
    {
276 6
        $this->restApiRequestScope = $restApiRequestScope;
277 6
        $this->typo3Cache = $typo3Cache;
278 6
    }
279
280
    /**
281
     * Override parent method...because we don't want to call it (the original method would cache the determined routes)!
282
     */
283
    public function __destruct()
284
    {
285
    }
286
287
    /**
288
     * Override parent method...because we don't want to call it (the original method would send some headers to the client)!
289
     */
290
    public function composeHeaders(RestException $e = null)
291
    {
292
    }
293
294
    /**
295
     * Override parent method...because we must return the request-data of THIS REST-API request!
296
     * The original method would return the request-data of the ORIGINAL called REST-API request
297
     *
298
     * @param boolean $includeQueryParameters
299
     * @return array
300
     */
301
    public function getRequestData($includeQueryParameters = true)
302
    {
303
        $requestData = array();
304
        if ($this->restApiRequestMethod == 'PUT' || $this->restApiRequestMethod == 'PATCH' || $this->restApiRequestMethod == 'POST') {
305
            $requestData = array_merge($this->restApiPostData, array(Defaults::$fullRequestDataName => $this->restApiPostData));
306
        }
307
308
        if ($includeQueryParameters === true) {
309
            return $requestData + $this->restApiGetData;
310
        }
311
        return $requestData;
312
    }
313
314
    /**
315
     * Override parent method...because we must return the data of the REST-API request and we need NO exception-handling!
316
     *
317
     * @return mixed can be a primitive or array or object
318
     */
319
    public function handle()
320
    {
321
        // get information about the REST-request
322
        $this->get();
323
324
        if ($this->requestMethod === 'GET' && $this->typo3Cache->hasCacheEntry($this->url, $_GET)) {
325
            return $this->handleRequestByTypo3Cache();
326
        }
327
328
        // if no cache exist: restler should handle the request
329
        if (Defaults::$useVendorMIMEVersioning) {
330
            $this->responseFormat = $this->negotiateResponseFormat();
331
        }
332
        $this->route();
333
        $this->negotiate();
334
        $this->preAuthFilter();
335
        $this->authenticate();
336
        $this->postAuthFilter();
337
        $this->validate();
338
        $this->preCall();
339
        $this->call();
340
        $this->compose();
341
        $this->postCall();
342
343
        if ($this->responseFormat instanceof JsonFormat) {
344
            // return stdClass-object (instead of an array)
345
            return $this->getRestApiJsonFormat()->decode($this->responseData);
346
        }
347
        return $this->responseFormat->decode($this->responseData);
348
    }
349
350
    /**
351
     * override postCall so that we can cache response via TYPO3-caching-framework - if it's possible
352
     */
353
    protected function postCall()
354
    {
355
        parent::postCall();
356
357
        if ($this->typo3Cache->isResponseCacheableByTypo3Cache($this->requestMethod, $this->apiMethodInfo->metadata)) {
358
            $this->typo3Cache->cacheResponseByTypo3Cache(
359
                $this->responseCode,
360
                $this->url,
361
                $_GET,
362
                $this->apiMethodInfo->metadata,
363
                $this->responseData,
364
                get_class($this->responseFormat),
365
                array() // we don't know which headers would be 'normally' send - because this is an internal REST-API-call
366
            );
367
        }
368
    }
369
}
370