Passed
Push — main ( 9b4012...e36571 )
by Markus
33:50 queued 20:13
created

PageIndexer   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Test Coverage

Coverage 64.57%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 28
eloc 110
c 2
b 0
f 0
dl 0
loc 308
ccs 82
cts 127
cp 0.6457
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getIndexQueueItem() 0 5 1
A getSolrConnection() 0 8 1
A getHighestAuthenticationServicePriority() 0 13 4
A getData() 0 3 1
A activate() 0 10 1
A getAccessRootline() 0 9 2
A authorizeFrontendUser() 0 22 3
A generatePageUrl() 0 27 4
A registerAuthorizationService() 0 24 1
B hook_indexContent() 0 67 10
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace ApacheSolrForTypo3\Solr\IndexQueue\FrontendHelper;
17
18
use ApacheSolrForTypo3\Solr\Access\Rootline;
19
use ApacheSolrForTypo3\Solr\ConnectionManager;
20
use ApacheSolrForTypo3\Solr\IndexQueue\Item;
21
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
22
use ApacheSolrForTypo3\Solr\NoSolrConnectionFoundException;
23
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
24
use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection;
25
use ApacheSolrForTypo3\Solr\Typo3PageIndexer;
26
use ApacheSolrForTypo3\Solr\Util;
27
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
28
use Doctrine\DBAL\Exception as DBALException;
29
use Throwable;
30
use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
31
use TYPO3\CMS\Core\SingletonInterface;
32
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
35
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
36
use UnexpectedValueException;
37
38
/**
39
 * Index Queue Page Indexer frontend helper to ask the frontend page indexer to
40
 * index the page.
41
 *
42
 * @author Ingo Renner <[email protected]>
43
 */
44
class PageIndexer extends AbstractFrontendHelper implements SingletonInterface
45
{
46
    /**
47
     * This frontend helper's executed action.
48
     *
49
     * @var string
50
     */
51
    protected string $action = 'indexPage';
52
53
    /**
54
     * the page currently being indexed.
55
     *
56
     * @var TypoScriptFrontendController
57
     */
58
    protected TypoScriptFrontendController $page;
59
60
    /**
61
     * Response data
62
     *
63
     * @var array
64
     */
65
    protected array $responseData = [];
66
67
    /**
68
     * Activates a frontend helper by registering for hooks and other
69
     * resources required by the frontend helper to work.
70
     *
71
     * @noinspection PhpUnused
72
     */
73 10
    public function activate()
74
    {
75 10
        $pageIndexingHookRegistration = PageIndexer::class;
76
77 10
        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'][__CLASS__] = $pageIndexingHookRegistration . '->hook_indexContent';
78
79
        // indexes fields defined in plugin.tx_solr.index.queue.pages.fields
80 10
        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['Indexer']['indexPageSubstitutePageDocument'][PageFieldMappingIndexer::class] = PageFieldMappingIndexer::class;
81
82 10
        $this->registerAuthorizationService();
83
    }
84
85
    /**
86
     * Returns the status of whether a page was indexed.
87
     *
88
     * @return array Page indexed status.
89
     * @noinspection PhpUnused
90
     */
91
    public function getData(): array
92
    {
93
        return $this->responseData;
94
    }
95
96
    //
97
    // Indexer authorisation for access restricted pages / content
98
    //
99
100
    /**
101
     * Fakes a logged in user to retrieve access restricted content.
102
     *
103
     * @noinspection PhpUnused
104
     */
105
    public function authorizeFrontendUser()
106
    {
107
        $accessRootline = $this->getAccessRootline();
108
        $stringAccessRootline = (string)$accessRootline;
109
110
        if (empty($stringAccessRootline)) {
111
            return;
112
        }
113
114
        if (!is_array($GLOBALS['TSFE']->fe_user->user)) {
115
            $GLOBALS['TSFE']->fe_user->user = [];
116
        }
117
118
        $groups = $accessRootline->getGroups();
119
        $groupList = implode(',', $groups);
120
121
        $GLOBALS['TSFE']->fe_user->user['username'] = AuthorizationService::SOLR_INDEXER_USERNAME;
122
        $GLOBALS['TSFE']->fe_user->user['usergroup'] = $groupList;
123
124
        $this->responseData['authorization'] = [
125
            'username' => $GLOBALS['TSFE']->fe_user->user['username'],
126
            'usergroups' => $GLOBALS['TSFE']->fe_user->user['usergroup'],
127
        ];
128
    }
129
130
    /**
131
     * Gets the access rootline as defined by the request.
132
     *
133
     * @return Rootline The access rootline to use for indexing.
134
     */
135 10
    protected function getAccessRootline(): Rootline
136
    {
137 10
        $stringAccessRootline = '';
138
139 10
        if ($this->request->getParameter('accessRootline')) {
0 ignored issues
show
Bug introduced by
The method getParameter() does not exist on null. ( Ignorable by Annotation )

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

139
        if ($this->request->/** @scrutinizer ignore-call */ getParameter('accessRootline')) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
140
            $stringAccessRootline = $this->request->getParameter('accessRootline');
141
        }
142
143 10
        return GeneralUtility::makeInstance(Rootline::class, /** @scrutinizer ignore-type */ $stringAccessRootline);
144
    }
145
146
    /**
147
     * Registers an authentication service to authorize / grant the indexer to
148
     * access protected pages.
149
     */
150 10
    protected function registerAuthorizationService()
151
    {
152 10
        $overrulingPriority = $this->getHighestAuthenticationServicePriority() + 1;
153
154 10
        ExtensionManagementUtility::addService(
155 10
            'solr', // extension key
156 10
            'auth', // service type
157 10
            AuthorizationService::class,
158
            // service key
159 10
            [// service meta data
160 10
                'title' => 'Solr Indexer Authorization',
161 10
                'description' => 'Authorizes the Solr Index Queue indexer to access protected pages.',
162
163 10
                'subtype' => 'getUserFE,authUserFE,getGroupsFE',
164
165 10
                'available' => true,
166 10
                'priority' => $overrulingPriority,
167 10
                'quality' => 100,
168
169 10
                'os' => '',
170 10
                'exec' => '',
171
172 10
                'classFile' => ExtensionManagementUtility::extPath('solr') . 'Classes/IndexQueue/FrontendHelper/AuthorizationService.php',
173 10
                'className' => AuthorizationService::class,
174 10
            ]
175 10
        );
176
    }
177
178
    /**
179
     * Determines the highest priority of all registered authentication
180
     * services.
181
     *
182
     * @return int Highest priority of all registered authentication service
183
     */
184 10
    protected function getHighestAuthenticationServicePriority(): int
185
    {
186 10
        $highestPriority = 0;
187
188 10
        if (is_array($GLOBALS['T3_SERVICES']['auth'] ?? null)) {
189 10
            foreach ($GLOBALS['T3_SERVICES']['auth'] as $service) {
190 10
                if ($service['priority'] > $highestPriority) {
191 10
                    $highestPriority = $service['priority'];
192
                }
193
            }
194
        }
195
196 10
        return $highestPriority;
197
    }
198
199
    //
200
    // Indexing
201
    //
202
203
    /**
204
     * Generates the current page's URL.
205
     *
206
     * Uses the provided GET parameters, page id and language id.
207
     *
208
     * @return string URL of the current page.
209
     */
210 10
    protected function generatePageUrl(): string
211
    {
212 10
        if ($this->request->getParameter('overridePageUrl')) {
213
            return $this->request->getParameter('overridePageUrl');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->request->g...eter('overridePageUrl') could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
214
        }
215
216
        /** @var $contentObject ContentObjectRenderer */
217 10
        $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
218
219 10
        $typolinkConfiguration = [
220 10
            'parameter' => (int)($this->page->id),
221 10
            'linkAccessRestrictedPages' => '1',
222 10
        ];
223
224 10
        $language = GeneralUtility::_GET('L');
225 10
        if (!empty($language)) {
226
            $typolinkConfiguration['additionalParams'] = '&L=' . $language;
227
        }
228
229 10
        $url = $contentObject->typoLink_URL($typolinkConfiguration);
230
231
        // clean up
232 10
        if ($url == '') {
233
            $url = '/';
234
        }
235
236 10
        return $url;
237
    }
238
239
    /**
240
     * Handles the indexing of the page content during post-processing of a
241
     * generated page.
242
     *
243
     * @param array $params unused
244
     * @param TypoScriptFrontendController $page TypoScript frontend
245
     * @noinspection PhpUnused
246
     * @noinspection PhpUnusedParameterInspection
247
     */
248 10
    public function hook_indexContent(array $params, TypoScriptFrontendController $page)
249
    {
250 10
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
251
252 10
        $this->page = $page;
253 10
        $configuration = Util::getSolrConfiguration();
254
255 10
        $logPageIndexed = $configuration->getLoggingIndexingPageIndexed();
256 10
        if (!$this->page->config['config']['index_enable'] ?? false) {
257
            if ($logPageIndexed) {
258
                $this->logger->log(
0 ignored issues
show
Bug introduced by
The method log() does not exist on null. ( Ignorable by Annotation )

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

258
                $this->logger->/** @scrutinizer ignore-call */ 
259
                               log(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
259
                    SolrLogManager::ERROR,
260
                    'Indexing is disabled. Set config.index_enable = 1 .'
261
                );
262
            }
263
            return;
264
        }
265
266
        try {
267 10
            $indexQueueItem = $this->getIndexQueueItem();
268 10
            if (is_null($indexQueueItem)) {
269
                throw new UnexpectedValueException('Can not get index queue item', 1482162337);
270
            }
271
272 10
            $solrConnection = $this->getSolrConnection($indexQueueItem);
273
274
            /** @var $indexer Typo3PageIndexer */
275 10
            $indexer = GeneralUtility::makeInstance(Typo3PageIndexer::class, /** @scrutinizer ignore-type */ $page);
276 10
            $indexer->setSolrConnection($solrConnection);
277 10
            $indexer->setPageAccessRootline($this->getAccessRootline());
278 10
            $indexer->setPageUrl($this->generatePageUrl());
279 10
            $indexer->setMountPointParameter($GLOBALS['TSFE']->MP);
280 10
            $indexer->setIndexQueueItem($indexQueueItem);
281
282 10
            $this->responseData['pageIndexed'] = (int)$indexer->indexPage();
283 10
            $this->responseData['originalPageDocument'] = (array)$indexer->getPageSolrDocument();
284 10
            $this->responseData['solrConnection'] = [
285 10
                'rootPage' => $indexQueueItem->getRootPageUid(),
286 10
                'sys_language_uid' => Util::getLanguageUid(),
287 10
                'solr' => (string)$solrConnection->getNode('write'),
288 10
            ];
289
290 10
            $documentsSentToSolr = $indexer->getDocumentsSentToSolr();
291 10
            foreach ($documentsSentToSolr as $document) {
292 10
                $this->responseData['documentsSentToSolr'][] = (array)$document;
293
            }
294
        } catch (Throwable $e) {
295
            $this->responseData['pageIndexed'] = false;
296
            if ($configuration->getLoggingExceptions()) {
297
                $this->logger->log(
298
                    SolrLogManager::ERROR,
299
                    'Exception while trying to index page ' . $page->id,
300
                    [
301
                        $e->__toString(),
302
                    ]
303
                );
304
            }
305
        }
306
307 10
        if ($logPageIndexed) {
308
            $success = $this->responseData['pageIndexed'] ? 'Success' : 'Failed';
309
            $severity = $this->responseData['pageIndexed'] ? SolrLogManager::NOTICE : SolrLogManager::ERROR;
310
311
            $this->logger->log(
312
                $severity,
313
                'Page indexed: ' . $success,
314
                $this->responseData
315
            );
316
        }
317
    }
318
319
    /**
320
     * Gets the solr connection to use for indexing the page based on the
321
     * Index Queue item's properties.
322
     *
323
     * @param Item $indexQueueItem
324
     * @return SolrConnection Solr server connection
325
     * @throws AspectNotFoundException
326
     * @throws NoSolrConnectionFoundException
327
     * @throws DBALDriverException
328
     */
329 10
    protected function getSolrConnection(Item $indexQueueItem): SolrConnection
330
    {
331
        /** @var $connectionManager ConnectionManager */
332 10
        $connectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
333
334 10
        return $connectionManager->getConnectionByRootPageId(
335 10
            $indexQueueItem->getRootPageUid(),
0 ignored issues
show
Bug introduced by
It seems like $indexQueueItem->getRootPageUid() can also be of type null; however, parameter $pageId of ApacheSolrForTypo3\Solr\...onnectionByRootPageId() 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

335
            /** @scrutinizer ignore-type */ $indexQueueItem->getRootPageUid(),
Loading history...
336 10
            Util::getLanguageUid()
337 10
        );
338
    }
339
340
    /**
341
     * This method retrieves the item from the index queue, that is indexed in this request.
342
     *
343
     * @return Item|null
344
     * @throws DBALDriverException
345
     * @throws DBALException|\Doctrine\DBAL\DBALException
346
     */
347 10
    protected function getIndexQueueItem(): ?Item
348
    {
349
        /** @var $indexQueue Queue */
350 10
        $indexQueue = GeneralUtility::makeInstance(Queue::class);
351 10
        return $indexQueue->getItem($this->request->getParameter('item'));
0 ignored issues
show
Bug introduced by
It seems like $this->request->getParameter('item') can also be of type null; however, parameter $itemId of ApacheSolrForTypo3\Solr\...xQueue\Queue::getItem() 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

351
        return $indexQueue->getItem(/** @scrutinizer ignore-type */ $this->request->getParameter('item'));
Loading history...
352
    }
353
}
354