Passed
Push — main ( 846b4b...9d0fba )
by Felix
12:18
created

RestApiRequest::overrideOriginalRestApiRequest()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
c 0
b 0
f 0
nc 9
nop 0
dl 0
loc 30
rs 9.2222
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
    public function executeRestApiRequest($requestMethod, $requestUri, $getData = null, $postData = null)
125
    {
126
        $this->restApiRequestMethod = $requestMethod;
127
        $this->restApiRequestUri = $requestUri;
128
        $this->restApiGetData = $this->convertDataToArray($getData);
129
        $this->restApiPostData = $this->convertDataToArray($postData);
130
131
        $this->storeOriginalRestApiRequest();
132
        $this->overrideOriginalRestApiRequest();
133
134
        try {
135
            $result = $this->handle();
136
            $this->restoreOriginalRestApiRequest();
137
            return $result;
138
        } catch (RestException $e) {
139
            $this->restoreOriginalRestApiRequest();
140
            throw $e;
141
        } catch (Exception $e) {
142
            $this->restoreOriginalRestApiRequest();
143
            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
        $objectManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
155
        return $objectManager->get('Aoe\\Restler\\System\\RestApi\\RestApiJsonFormat');
156
    }
157
158
    /**
159
     * @param array|stdClass $data
160
     * @return array
161
     * @throws RestException
162
     */
163
    private function convertDataToArray($data)
164
    {
165
        if ($data === null) {
166
            return array();
167
        }
168
        if (is_array($data)) {
169
            return $data;
170
        }
171
        if ($data instanceof stdClass) {
0 ignored issues
show
introduced by
$data is always a sub-type of stdClass.
Loading history...
172
            return json_decode(json_encode($data), true); // convert stdClass to array
173
        }
174
        throw new RestException(500, 'data must be type of null, array or stdClass');
175
    }
176
177
    /**
178
     * @return string
179
     */
180
    private function handleRequestByTypo3Cache()
181
    {
182
        $cacheEntry = $this->typo3Cache->getCacheEntry($this->url, $_GET);
183
        $this->responseCode = $cacheEntry['responseCode'];
184
        $this->responseData = $cacheEntry['responseData'];
185
        $this->responseFormat = new $cacheEntry['responseFormatClass']();
186
187
        // send data to client
188
        if ($this->responseFormat instanceof JsonFormat) {
189
            // return stdClass-object (instead of an array)
190
            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...
191
        }
192
        return $this->responseFormat->decode($this->responseData);
193
    }
194
195
    /**
196
     * Override (the stored) data of $_GET, $_POST and $_SERVER (which are used in several restler-PHP-classes) and the original
197
     * REST-API-Request-object, because this data/object 'defines' the REST-API-request, which we want to call
198
     *
199
     * @return void
200
     */
201
    private function overrideOriginalRestApiRequest()
202
    {
203
        $_GET = $this->restApiGetData;
204
        $_POST = $this->restApiPostData;
205
        $_SERVER['REQUEST_METHOD'] = $this->restApiRequestMethod;
206
        $_SERVER['REQUEST_URI'] = $this->restApiRequestUri;
207
208
        if ($this->restApiRequestMethod !== 'POST' && $this->restApiRequestMethod !== 'PUT') {
209
            // content-type and content-length should only exist when request-method is
210
            // POST or PUT (because in this case there can be the request-data in the body)
211
            if (array_key_exists('CONTENT_TYPE', $_SERVER)) {
212
                unset($_SERVER['CONTENT_TYPE']);
213
            }
214
            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
215
                unset($_SERVER['HTTP_CONTENT_TYPE']);
216
            }
217
            if (array_key_exists('CONTENT_LENGTH', $_SERVER)) {
218
                unset($_SERVER['CONTENT_LENGTH']);
219
            }
220
        }
221
222
        $this->restApiRequestScope->overrideOriginalRestApiRequest($this);
223
        $this->restApiRequestScope->removeRestApiAuthenticationObjects();
224
225
        /**
226
         * add all authentication-classes:
227
         *  - we must add all authentication-classes, because the authentication-classes are stored in this object
228
         *  - we don't must add all REST-API-classes, because the REST-API-classes are not stored in this object
229
         */
230
        $this->authClasses = $this->restApiRequestScope->getOriginalRestApiRequest()->_authClasses;
231
    }
232
233
    /**
234
     * Restore (the overridden) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
235
     * @return void
236
     */
237
    private function restoreOriginalRestApiRequest()
238
    {
239
        $_GET = self::$originalGetVars;
240
        $_POST = self::$originalPostVars;
241
        $_SERVER = self::$originalServerSettings;
242
        $this->restApiRequestScope->restoreOriginalRestApiRequest();
243
        $this->restApiRequestScope->restoreOriginalRestApiAuthenticationObjects();
244
    }
245
246
    /**
247
     * Store (the original) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
248
     * @return void
249
     */
250
    private function storeOriginalRestApiRequest()
251
    {
252
        if (false === isset(self::$originalServerSettings)) {
253
            self::$originalGetVars = $_GET;
254
            self::$originalPostVars = $_POST;
255
            self::$originalServerSettings = $_SERVER;
256
            $this->restApiRequestScope->storeOriginalRestApiRequest();
257
            $this->restApiRequestScope->storeOriginalRestApiAuthenticationObjects();
258
        }
259
    }
260
261
262
263
    /***************************************************************************************************************************/
264
    /***************************************************************************************************************************/
265
    /* Block of methods, which MUST be overriden from parent-class (otherwise this class can not work) *************************/
266
    /***************************************************************************************************************************/
267
    /***************************************************************************************************************************/
268
    /**
269
     * Override parent method...because we don't want to call it!
270
     * The original method would set some properties (e.g. set this object into static properties of global classes)
271
     *
272
     * @param RestApiRequestScope $restApiRequestScope
273
     * @param Typo3Cache $typo3Cache
274
     */
275
    public function __construct(RestApiRequestScope $restApiRequestScope, Typo3Cache $typo3Cache)
276
    {
277
        $this->restApiRequestScope = $restApiRequestScope;
278
        $this->typo3Cache = $typo3Cache;
279
    }
280
281
    /**
282
     * Override parent method...because we don't want to call it (the original method would cache the determined routes)!
283
     */
284
    public function __destruct()
285
    {
286
    }
287
288
    /**
289
     * Override parent method...because we don't want to call it (the original method would send some headers to the client)!
290
     */
291
    public function composeHeaders(RestException $e = null)
292
    {
293
    }
294
295
    /**
296
     * Override parent method...because we must return the request-data of THIS REST-API request!
297
     * The original method would return the request-data of the ORIGINAL called REST-API request
298
     *
299
     * @param boolean $includeQueryParameters
300
     * @return array
301
     */
302
    public function getRequestData($includeQueryParameters = true)
303
    {
304
        $requestData = array();
305
        if ($this->restApiRequestMethod == 'PUT' || $this->restApiRequestMethod == 'PATCH' || $this->restApiRequestMethod == 'POST') {
306
            $requestData = array_merge($this->restApiPostData, array(Defaults::$fullRequestDataName => $this->restApiPostData));
307
        }
308
309
        if ($includeQueryParameters === true) {
310
            return $requestData + $this->restApiGetData;
311
        }
312
        return $requestData;
313
    }
314
315
    /**
316
     * Override parent method...because we must return the data of the REST-API request and we need NO exception-handling!
317
     *
318
     * @return mixed can be a primitive or array or object
319
     */
320
    public function handle()
321
    {
322
        // get information about the REST-request
323
        $this->get();
324
325
        if ($this->requestMethod === 'GET' && $this->typo3Cache->hasCacheEntry($this->url, $_GET)) {
326
            return $this->handleRequestByTypo3Cache();
327
        }
328
329
        // if no cache exist: restler should handle the request
330
        if (Defaults::$useVendorMIMEVersioning) {
331
            $this->responseFormat = $this->negotiateResponseFormat();
332
        }
333
        $this->route();
334
        $this->negotiate();
335
        $this->preAuthFilter();
336
        $this->authenticate();
337
        $this->postAuthFilter();
338
        $this->validate();
339
        $this->preCall();
340
        $this->call();
341
        $this->compose();
342
        $this->postCall();
343
344
        if ($this->responseFormat instanceof JsonFormat) {
345
            // return stdClass-object (instead of an array)
346
            return $this->getRestApiJsonFormat()->decode($this->responseData);
347
        }
348
        return $this->responseFormat->decode($this->responseData);
349
    }
350
351
    /**
352
     * override postCall so that we can cache response via TYPO3-caching-framework - if it's possible
353
     */
354
    protected function postCall()
355
    {
356
        parent::postCall();
357
358
        if ($this->typo3Cache->isResponseCacheableByTypo3Cache($this->requestMethod, $this->apiMethodInfo->metadata)) {
359
            $this->typo3Cache->cacheResponseByTypo3Cache(
360
                $this->responseCode,
361
                $this->url,
362
                $_GET,
363
                $this->apiMethodInfo->metadata,
364
                $this->responseData,
365
                get_class($this->responseFormat),
366
                array() // we don't know which headers would be 'normally' send - because this is an internal REST-API-call
367
            );
368
        }
369
    }
370
}
371