Passed
Push — master ( 02f8b8...776b21 )
by Timo
22:25
created

PageIndexerRequest::setTimeout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 2
1
<?php
2
namespace ApacheSolrForTypo3\Solr\IndexQueue;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2010-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\System\Configuration\ExtensionConfiguration;
28
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
29
use Psr\Http\Message\ResponseInterface;
30
use GuzzleHttp\Exception\ServerException;
31
use GuzzleHttp\Exception\ClientException;
32
use TYPO3\CMS\Core\Http\RequestFactory;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
35
/**
36
 * Index Queue Page Indexer request with details about which actions to perform.
37
 *
38
 * @author Ingo Renner <[email protected]>
39
 */
40
class PageIndexerRequest
41
{
42
43
    /**
44
     * List of actions to perform during page rendering.
45
     *
46
     * @var array
47
     */
48
    protected $actions = [];
49
50
    /**
51
     * Parameters as sent from the Index Queue page indexer.
52
     *
53
     * @var array
54
     */
55
    protected $parameters = [];
56
57
    /**
58
     * Headers as sent from the Index Queue page indexer.
59
     *
60
     * @var array
61
     */
62
    protected $header = [];
63
64
    /**
65
     * Unique request ID.
66
     *
67
     * @var string
68
     */
69
    protected $requestId;
70
71
    /**
72
     * Username to use for basic auth protected URLs.
73
     *
74
     * @var string
75
     */
76
    protected $username = '';
77
78
    /**
79
     * Password to use for basic auth protected URLs.
80
     *
81
     * @var string
82
     */
83
    protected $password = '';
84
85
    /**
86
     * An Index Queue item related to this request.
87
     *
88
     * @var Item
89
     */
90
    protected $indexQueueItem = null;
91
92
    /**
93
     * Request timeout in seconds
94
     *
95
     * @var float
96
     */
97
    protected $timeout;
98
99
    /**
100
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
101
     */
102
    protected $logger = null;
103
104
    /**
105
     * @var ExtensionConfiguration
106
     */
107
    protected $extensionConfiguration;
108
109
    /**
110
     * @var RequestFactory
111
     */
112
    protected $requestFactory;
113
114
    /**
115
     * PageIndexerRequest constructor.
116
     *
117
     * @param string $jsonEncodedParameters json encoded header
118
     * @param SolrLogManager|null $solrLogManager
119
     * @param ExtensionConfiguration|null $extensionConfiguration
120
     * @param RequestFactory|null $requestFactory
121
     */
122 21
    public function __construct($jsonEncodedParameters = null, SolrLogManager $solrLogManager = null, ExtensionConfiguration $extensionConfiguration = null, RequestFactory $requestFactory = null)
123
    {
124 21
        $this->requestId = uniqid();
125 21
        $this->timeout = (float)ini_get('default_socket_timeout');
126
127 21
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
0 ignored issues
show
Bug introduced by
__CLASS__ of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

127
        $this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
Loading history...
128 21
        $this->extensionConfiguration = $extensionConfiguration ?? GeneralUtility::makeInstance(ExtensionConfiguration::class);
129 21
        $this->requestFactory = $requestFactory ?? GeneralUtility::makeInstance(RequestFactory::class);
130
131 21
        if (is_null($jsonEncodedParameters)) {
132 13
            return;
133
        }
134
135 8
        $this->parameters = (array)json_decode($jsonEncodedParameters, true);
136 8
        $this->requestId = $this->parameters['requestId'];
137 8
        unset($this->parameters['requestId']);
138
139 8
        $actions = explode(',', $this->parameters['actions']);
140 8
        foreach ($actions as $action) {
141 8
            $this->addAction($action);
142
        }
143 8
        unset($this->parameters['actions']);
144 8
    }
145
146
    /**
147
     * Adds an action to perform during page rendering.
148
     *
149
     * @param string $action Action name.
150
     */
151 8
    public function addAction($action)
152
    {
153 8
        $this->actions[] = $action;
154 8
    }
155
156
    /**
157
     * Executes the request.
158
     *
159
     * Uses headers to submit additional data and avoiding to have these
160
     * arguments integrated into the URL when created by RealURL.
161
     *
162
     * @param string $url The URL to request.
163
     * @return PageIndexerResponse Response
164
     */
165 5
    public function send($url)
166
    {
167
        /** @var $response PageIndexerResponse */
168 5
        $response = GeneralUtility::makeInstance(PageIndexerResponse::class);
169 5
        $decodedResponse = $this->getUrlAndDecodeResponse($url, $response);
170
171 4
        if ($decodedResponse['requestId'] != $this->requestId) {
172 1
            throw new \RuntimeException(
173 1
                'Request ID mismatch. Request ID was ' . $this->requestId . ', received ' . $decodedResponse['requestId'] . '. Are requests cached?',
174 1
                1351260655
175
            );
176
        }
177
178 3
        $response->setRequestId($decodedResponse['requestId']);
179
180 3
        if (!is_array($decodedResponse['actionResults'])) {
181
            // nothing to parse
182
            return $response;
183
        }
184
185 3
        foreach ($decodedResponse['actionResults'] as $action => $actionResult) {
186 3
            $response->addActionResult($action, $actionResult);
187
        }
188
189 3
        return $response;
190
    }
191
192
    /**
193
     * This method is used to retrieve an url from the frontend and decode the response.
194
     *
195
     * @param string $url
196
     * @param PageIndexerResponse $response
197
     * @return mixed
198
     */
199 5
    protected function getUrlAndDecodeResponse($url, PageIndexerResponse $response)
200
    {
201 5
        $headers = $this->getHeaders();
202 5
        $rawResponse = $this->getUrl($url, $headers, $this->timeout);
203
        // convert JSON response to response object properties
204 5
        $decodedResponse = $response->getResultsFromJson($rawResponse->getBody()->getContents());
205
206 5
        if ($rawResponse === false || $decodedResponse === false) {
207 1
            $this->logger->log(
208 1
                SolrLogManager::ERROR,
209 1
                'Failed to execute Page Indexer Request. Request ID: ' . $this->requestId,
210
                [
211 1
                    'request ID' => $this->requestId,
212 1
                    'request url' => $url,
213 1
                    'request headers' => $headers,
214 1
                    'response headers' => $rawResponse->getHeaders(),
215 1
                    'raw response body' => $rawResponse->getBody()->getContents()
216
                ]
217
            );
218
219 1
            throw new \RuntimeException('Failed to execute Page Indexer Request. See log for details. Request ID: ' . $this->requestId, 1319116885);
220
        }
221 4
        return $decodedResponse;
222
    }
223
224
    /**
225
     * Generates the headers to be send with the request.
226
     *
227
     * @return string[] Array of HTTP headers.
228
     */
229 6
    public function getHeaders()
230
    {
231 6
        $headers = $this->header;
232 6
        $headers[] = 'User-Agent: ' . $this->getUserAgent();
233 6
        $itemId = $this->indexQueueItem->getIndexQueueUid();
234 6
        $pageId = $this->indexQueueItem->getRecordPageId();
235
236
        $indexerRequestData = [
237 6
            'requestId' => $this->requestId,
238 6
            'item' => $itemId,
239 6
            'page' => $pageId,
240 6
            'actions' => implode(',', $this->actions),
241 6
            'hash' => md5(
242 6
                $itemId . '|' .
243 6
                $pageId . '|' .
244 6
                $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
245
            )
246
        ];
247
248 6
        $indexerRequestData = array_merge($indexerRequestData, $this->parameters);
249 6
        $headers[] = 'X-Tx-Solr-Iq: ' . json_encode($indexerRequestData, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
250
251 6
        return $headers;
252
    }
253
254
    /**
255
     * @return string
256
     */
257 6
    protected function getUserAgent()
258
    {
259 6
        return $GLOBALS['TYPO3_CONF_VARS']['HTTP']['headers']['User-Agent'] ?? 'TYPO3';
260
    }
261
262
    /**
263
     * Adds an HTTP header to be send with the request.
264
     *
265
     * @param string $header HTTP header
266
     */
267
    public function addHeader($header)
268
    {
269
        $this->header[] = $header;
270
    }
271
272
    /**
273
     * Checks whether this is a legitimate request coming from the Index Queue
274
     * page indexer worker task.
275
     *
276
     * @return bool TRUE if it's a legitimate request, FALSE otherwise.
277
     */
278 2
    public function isAuthenticated()
279
    {
280 2
        $authenticated = false;
281
282 2
        if (is_null($this->parameters)) {
0 ignored issues
show
introduced by
The condition is_null($this->parameters) can never be true.
Loading history...
283
            return $authenticated;
284
        }
285
286 2
        $calculatedHash = md5(
287 2
            $this->parameters['item'] . '|' .
288 2
            $this->parameters['page'] . '|' .
289 2
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
290
        );
291
292 2
        if ($this->parameters['hash'] === $calculatedHash) {
293 1
            $authenticated = true;
294
        }
295
296 2
        return $authenticated;
297
    }
298
299
    /**
300
     * Gets the list of actions to perform during page rendering.
301
     *
302
     * @return array List of actions
303
     */
304
    public function getActions()
305
    {
306
        return $this->actions;
307
    }
308
309
    /**
310
     * Gets the request's parameters.
311
     *
312
     * @return array Request parameters.
313
     */
314
    public function getParameters()
315
    {
316
        return $this->parameters;
317
    }
318
319
    /**
320
     * Gets the request's unique ID.
321
     *
322
     * @return string Unique request ID.
323
     */
324 3
    public function getRequestId()
325
    {
326 3
        return $this->requestId;
327
    }
328
329
    /**
330
     * Gets a specific parameter's value.
331
     *
332
     * @param string $parameterName The parameter to retrieve.
333
     * @return mixed NULL if a parameter was not set or it's value otherwise.
334
     */
335 11
    public function getParameter($parameterName)
336
    {
337 11
        return isset($this->parameters[$parameterName]) ? $this->parameters[$parameterName] : null;
338
    }
339
340
    /**
341
     * Sets a request's parameter and its value.
342
     *
343
     * @param string $parameter Parameter name
344
     * @param string $value Parameter value.
345
     */
346 11
    public function setParameter($parameter, $value)
347
    {
348 11
        if (is_bool($value)) {
0 ignored issues
show
introduced by
The condition is_bool($value) can never be true.
Loading history...
349
            $value = $value ? '1' : '0';
350
        }
351
352 11
        $this->parameters[$parameter] = $value;
353 11
    }
354
355
    /**
356
     * Sets username and password to be used for a basic auth request header.
357
     *
358
     * @param string $username username.
359
     * @param string $password password.
360
     */
361 1
    public function setAuthorizationCredentials($username, $password)
362
    {
363 1
        $this->username = $username;
364 1
        $this->password = $password;
365 1
    }
366
367
    /**
368
     * Sets the Index Queue item this request is related to.
369
     *
370
     * @param Item $item Related Index Queue item.
371
     */
372 6
    public function setIndexQueueItem(Item $item)
373
    {
374 6
        $this->indexQueueItem = $item;
375 6
    }
376
377
    /**
378
     * Returns the request timeout in seconds
379
     *
380
     * @return float
381
     */
382 1
    public function getTimeout()
383
    {
384 1
        return $this->timeout;
385
    }
386
387
    /**
388
     * Sets the request timeout in seconds
389
     *
390
     * @param float $timeout Timeout seconds
391
     */
392
    public function setTimeout($timeout)
393
    {
394
        $this->timeout = (float)$timeout;
395
    }
396
397
    /**
398
     * Fetches a page by sending the configured headers.
399
     *
400
     * @param string $url
401
     * @param string[] $headers
402
     * @param float $timeout
403
     * @return ResponseInterface
404
     * @throws \Exception
405
     */
406 1
    protected function getUrl($url, $headers, $timeout): ResponseInterface
407
    {
408
        try {
409 1
            $options = $this->buildGuzzleOptions($headers, $timeout);
410 1
            $response = $this->requestFactory->request($url, 'GET', $options);
411
        } catch (ClientException $e) {
412
            $response = $e->getResponse();
413
        } catch (ServerException $e) {
414
            $response = $e->getResponse();
415
        }
416
417 1
        return $response;
418
    }
419
420
    /**
421
     * Build the options array for the guzzle client.
422
     *
423
     * @param array $headers
424
     * @param float $timeout
425
     * @return array
426
     */
427 1
    protected function buildGuzzleOptions($headers, $timeout)
428
    {
429 1
        $finalHeaders = [];
430
431 1
        foreach ($headers as $header) {
432 1
            list($name, $value) = explode(':', $header, 2);
433 1
            $finalHeaders[$name] = trim($value);
434
        }
435
436 1
        $options = ['headers' => $finalHeaders, 'timeout' => $timeout];
437 1
        if (!empty($this->username) && !empty($this->password)) {
438 1
            $options['auth'] = [$this->username, $this->password];
439
        }
440
441 1
        if ($this->extensionConfiguration->getIsSelfSignedCertificatesEnabled()) {
442
            $options['verify'] = false;
443
        }
444
445 1
        return $options;
446
    }
447
}
448