Passed
Push — master ( e55157...ed38f6 )
by Timo
27:41
created

PageIndexer::getAccessRootlineByPageId()   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
eloc 1
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
namespace ApacheSolrForTypo3\Solr\IndexQueue;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2009-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\Access\Rootline;
28
use ApacheSolrForTypo3\Solr\Access\RootlineElement;
29
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32
/**
33
 * A special purpose indexer to index pages.
34
 *
35
 * In the case of pages we can't directly index the page records, we need to
36
 * retrieve the content that belongs to a page from tt_content, too. Also
37
 * plugins may be included on a page and thus may need to be executed.
38
 *
39
 * @author Ingo Renner <[email protected]>
40
 */
41
class PageIndexer extends Indexer
42
{
43
    /**
44
     * Indexes an item from the indexing queue.
45
     *
46
     * @param Item $item An index queue item
47
     * @return bool Whether indexing was successful
48
     */
49 1
    public function index(Item $item)
50
    {
51 1
        $this->setLogging($item);
52
53
        // check whether we should move on at all
54 1
        if (!$this->isPageIndexable($item)) {
55
            return false;
56
        }
57
58 1
        $solrConnections = $this->getSolrConnectionsByItem($item);
59 1
        foreach ($solrConnections as $systemLanguageUid => $solrConnection) {
60 1
            $contentAccessGroups = $this->getAccessGroupsFromContent($item, $systemLanguageUid);
61
62 1
            if (empty($contentAccessGroups)) {
63
                // might be an empty page w/no content elements or some TYPO3 error / bug
64
                // FIXME logging needed
65
                continue;
66
            }
67
68 1
            foreach ($contentAccessGroups as $userGroup) {
69 1
                $this->indexPage($item, $systemLanguageUid, $userGroup);
70
            }
71
        }
72
73 1
        return true;
74
    }
75
76
    /**
77
     * Checks whether we can index this page.
78
     *
79
     * @param Item $item The page we want to index encapsulated in an index queue item
80
     * @return bool True if we can index this page, FALSE otherwise
81
     */
82 1
    protected function isPageIndexable(Item $item)
83
    {
84
85
        // TODO do we still need this?
86
        // shouldn't those be sorted out by the record monitor / garbage collector already?
87
88 1
        $isIndexable = true;
89 1
        $record = $item->getRecord();
90
91 1
        if (isset($GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled'])
92 1
            && $record[$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']]
93
        ) {
94
            $isIndexable = false;
95
        }
96
97 1
        return $isIndexable;
98
    }
99
100
    /**
101
     * Gets the Solr connections applicable for a page.
102
     *
103
     * The connections include the default connection and connections to be used
104
     * for translations of a page.
105
     *
106
     * @param Item $item An index queue item
107
     * @return array An array of ApacheSolrForTypo3\Solr\System\Solr\SolrConnection connections, the array's keys are the sys_language_uid of the language of the connection
108
     */
109 1
    protected function getSolrConnectionsByItem(Item $item)
110
    {
111 1
        $solrConnections = parent::getSolrConnectionsByItem($item);
112
113 1
        $page = $item->getRecord();
114
        // may use \TYPO3\CMS\Core\Utility\GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg']) with TYPO3 4.6
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
115 1
        if ($page['l18n_cfg'] & 1) {
116
            // page is configured to hide the default translation -> remove Solr connection for default language
117
            unset($solrConnections[0]);
118
        }
119
120 1
        if (GeneralUtility::hideIfNotTranslated($page['l18n_cfg'])) {
121
            $accessibleSolrConnections = [];
122
            if (isset($solrConnections[0])) {
123
                $accessibleSolrConnections[0] = $solrConnections[0];
124
            }
125
126
            $translationOverlays = $this->pagesRepository->findTranslationOverlaysByPageId((int)$page['uid']);
127
128
            foreach ($translationOverlays as $overlay) {
129
                $languageId = $overlay['sys_language_uid'];
130
                if (array_key_exists($languageId, $solrConnections)) {
131
                    $accessibleSolrConnections[$languageId] = $solrConnections[$languageId];
132
                }
133
            }
134
135
            $solrConnections = $accessibleSolrConnections;
136
        }
137
138 1
        return $solrConnections;
139
    }
140
141
    /**
142
     * Finds the FE user groups used on a page including all groups of content
143
     * elements and groups of records of extensions that have correctly been
144
     * pushed through ContentObjectRenderer during rendering.
145
     *
146
     * @param Item $item Index queue item representing the current page to get the user groups from
147
     * @param int $language The sys_language_uid language ID
148
     * @return array Array of user group IDs
149
     */
150 1
    protected function getAccessGroupsFromContent(Item $item, $language = 0)
151
    {
152 1
        static $accessGroupsCache;
153
154 1
        $accessGroupsCacheEntryId = $item->getRecordUid() . '|' . $language;
155 1
        if (!isset($accessGroupsCache[$accessGroupsCacheEntryId])) {
156 1
            $request = $this->buildBasePageIndexerRequest();
157 1
            $request->setIndexQueueItem($item);
158 1
            $request->addAction('findUserGroups');
159
160 1
            $indexRequestUrl = $this->getDataUrl($item, $language);
161 1
            $response = $request->send($indexRequestUrl);
162
163 1
            $groups = $response->getActionResult('findUserGroups');
164 1
            if (is_array($groups)) {
0 ignored issues
show
introduced by
The condition is_array($groups) is always true.
Loading history...
165 1
                $accessGroupsCache[$accessGroupsCacheEntryId] = $groups;
166
            }
167
168 1
            if ($this->loggingEnabled) {
169
                $this->logger->log(
170
                    SolrLogManager::INFO,
171
                    'Page Access Groups',
172
                    [
173
                        'item' => (array)$item,
174
                        'language' => $language,
175
                        'index request url' => $indexRequestUrl,
176
                        'request' => (array)$request,
177
                        'response' => (array)$response,
178
                        'groups' => $groups
179
                    ]
180
                );
181
            }
182
        }
183
184 1
        return $accessGroupsCache[$accessGroupsCacheEntryId];
185
    }
186
187
    // Utility methods
188
189
    /**
190
     * Builds a base page indexer request with configured headers and other
191
     * parameters.
192
     *
193
     * @return PageIndexerRequest Base page indexer request
194
     */
195 1
    protected function buildBasePageIndexerRequest()
196
    {
197 1
        $request = $this->getPageIndexerRequest();
198 1
        $request->setParameter('loggingEnabled', $this->loggingEnabled);
199
200 1
        if (!empty($this->options['authorization.'])) {
201
            $request->setAuthorizationCredentials(
202
                $this->options['authorization.']['username'],
203
                $this->options['authorization.']['password']
204
            );
205
        }
206
207 1
        if (!empty($this->options['frontendDataHelper.']['headers.'])) {
208
            foreach ($this->options['frontendDataHelper.']['headers.'] as $headerValue) {
209
                $request->addHeader($headerValue);
210
            }
211
        }
212
213 1
        if (!empty($this->options['frontendDataHelper.']['requestTimeout'])) {
214
            $request->setTimeout((float)$this->options['frontendDataHelper.']['requestTimeout']);
215
        }
216
217 1
        return $request;
218
    }
219
220
    /**
221
     * @return PageIndexerRequest
222
     */
223
    protected function getPageIndexerRequest()
224
    {
225
        return GeneralUtility::makeInstance(PageIndexerRequest::class);
226
    }
227
228
    /**
229
     * Determines a page ID's URL.
230
     *
231
     * Tries to find a domain record to use to build an URL for a given page ID
232
     * and then actually build and return the page URL.
233
     *
234
     * @param Item $item Item to index
235
     * @param int $language The language id
236
     * @return string URL to send the index request to
237
     * @throws \RuntimeException
238
     */
239 1
    protected function getDataUrl(Item $item, $language = 0)
240
    {
241 1
        $scheme = 'http';
242 1
        $host = $item->getSite()->getDomain();
243 1
        $path = '/';
244 1
        $pageId = $item->getRecordUid();
245
246
        // deprecated
247 1
        if (!empty($this->options['scheme'])) {
248
            $this->logger->log(
249
                SolrLogManager::INFO,
250
                'Using deprecated option "scheme" to set the scheme (http / https) for the page indexer frontend helper. Use plugin.tx_solr.index.queue.pages.indexer.frontendDataHelper.scheme instead'
251
            );
252
            $scheme = $this->options['scheme'];
253
        }
254
255
        // check whether we should use ssl / https
256 1
        if (!empty($this->options['frontendDataHelper.']['scheme'])) {
257
            $scheme = $this->options['frontendDataHelper.']['scheme'];
258
        }
259
260
        // overwriting the host
261 1
        if (!empty($this->options['frontendDataHelper.']['host'])) {
262
            $host = $this->options['frontendDataHelper.']['host'];
263
        }
264
265
        // setting a path if TYPO3 is installed in a sub directory
266 1
        if (!empty($this->options['frontendDataHelper.']['path'])) {
267
            $path = $this->options['frontendDataHelper.']['path'];
268
        }
269
270 1
        $mountPointParameter = $this->getMountPageDataUrlParameter($item);
271 1
        $dataUrl = $scheme . '://' . $host . $path . 'index.php?id=' . $pageId;
272 1
        $dataUrl .= ($mountPointParameter !== '') ? '&MP=' . $mountPointParameter : '';
273 1
        $dataUrl .= '&L=' . $language;
274
275 1
        if (!GeneralUtility::isValidUrl($dataUrl)) {
276
            $this->logger->log(
277
                SolrLogManager::ERROR,
278
                'Could not create a valid URL to get frontend data while trying to index a page.',
279
                [
280
                    'item' => (array)$item,
281
                    'constructed URL' => $dataUrl,
282
                    'scheme' => $scheme,
283
                    'host' => $host,
284
                    'path' => $path,
285
                    'page ID' => $pageId,
286
                    'indexer options' => $this->options
287
                ]
288
            );
289
290
            throw new \RuntimeException(
291
                'Could not create a valid URL to get frontend data while trying to index a page. Created URL: ' . $dataUrl,
292
                1311080805
293
            );
294
        }
295
296 1
        if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueuePageIndexer']['dataUrlModifier']) {
297
            $dataUrlModifier = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueuePageIndexer']['dataUrlModifier']);
298
299
            if ($dataUrlModifier instanceof PageIndexerDataUrlModifier) {
300
                $dataUrl = $dataUrlModifier->modifyDataUrl($dataUrl, [
301
                    'item' => $item,
302
                    'scheme' => $scheme,
303
                    'host' => $host,
304
                    'path' => $path,
305
                    'pageId' => $pageId,
306
                    'language' => $language
307
                ]);
308
            } else {
309
                throw new \RuntimeException(
310
                    $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueuePageIndexer']['dataUrlModifier']
311
                    . ' is not an implementation of ApacheSolrForTypo3\Solr\IndexQueue\PageIndexerDataUrlModifier',
312
                    1290523345
313
                );
314
            }
315
        }
316
317 1
        return $dataUrl;
318
    }
319
320
    /**
321
     * Generates the MP URL parameter needed to access mount pages. If the item
322
     * is identified as being a mounted page, the &MP parameter is generated.
323
     *
324
     * @param Item $item Item to get an &MP URL parameter for
325
     * @return string &MP URL parameter if $item is a mounted page
326
     */
327 1
    protected function getMountPageDataUrlParameter(Item $item)
328
    {
329 1
        if (!$item->hasIndexingProperty('isMountedPage')) {
330 1
            return '';
331
        }
332
333
        return $item->getIndexingProperty('mountPageSource') . '-' . $item->getIndexingProperty('mountPageDestination');
334
    }
335
336
    #
337
    # Frontend User Groups Access
338
    #
339
340
    /**
341
     * Creates a single Solr Document for a page in a specific language and for
342
     * a specific frontend user group.
343
     *
344
     * @param Item $item The index queue item representing the page.
345
     * @param int $language The language to use.
346
     * @param int $userGroup The frontend user group to use.
347
     * @return PageIndexerResponse Page indexer response
348
     * @throws \RuntimeException if indexing an item failed
349
     */
350 1
    protected function indexPage(Item $item, $language = 0, $userGroup = 0)
351
    {
352 1
        $accessRootline = $this->getAccessRootline($item, $language, $userGroup);
353 1
        $request = $this->buildBasePageIndexerRequest();
354 1
        $request->setIndexQueueItem($item);
355 1
        $request->addAction('indexPage');
356 1
        $request->setParameter('accessRootline', (string)$accessRootline);
357
358 1
        $indexRequestUrl = $this->getDataUrl($item, $language);
359 1
        $response = $request->send($indexRequestUrl);
360 1
        $indexActionResult = $response->getActionResult('indexPage');
361
362 1
        if ($this->loggingEnabled) {
363
            $logSeverity = SolrLogManager::INFO;
364
            $logStatus = 'Info';
365
            if ($indexActionResult['pageIndexed']) {
366
                $logSeverity = SolrLogManager::NOTICE;
367
                $logStatus = 'Success';
368
            }
369
370
            $this->logger->log(
371
                $logSeverity,
372
                'Page Indexer: ' . $logStatus,
373
                [
374
                    'item' => (array)$item,
375
                    'language' => $language,
376
                    'user group' => $userGroup,
377
                    'index request url' => $indexRequestUrl,
378
                    'request' => (array)$request,
379
                    'request headers' => $request->getHeaders(),
380
                    'response' => (array)$response
381
                ]
382
            );
383
        }
384
385 1
        if (!$indexActionResult['pageIndexed']) {
386
            $message = 'Failed indexing page Index Queue item: ' . $item->getIndexQueueUid() . ' url: ' . $indexRequestUrl;
387
388
            throw new \RuntimeException($message, 1331837081);
389
        }
390
391 1
        return $response;
392
    }
393
394
    /**
395
     * Generates a page document's "Access Rootline".
396
     *
397
     * The Access Rootline collects frontend user group access restrictions set
398
     * for pages up in a page's rootline extended to sub-pages.
399
     *
400
     * The format is like this:
401
     * pageId1:group1,group2|groupId2:group3|c:group1,group4,groupN
402
     *
403
     * The single elements of the access rootline are separated by a pipe
404
     * character. All but the last elements represent pages, the last element
405
     * defines the access restrictions applied to the page's content elements
406
     * and records shown on the page.
407
     * Each page element is composed by the page ID of the page setting frontend
408
     * user access restrictions, a colon, and a comma separated list of frontend
409
     * user group IDs restricting access to the page.
410
     * The content access element does not have a page ID, instead it replaces
411
     * the ID by a lower case C.
412
     *
413
     * @param Item $item Index queue item representing the current page
414
     * @param int $language The sys_language_uid language ID
415
     * @param int $contentAccessGroup The user group to use for the content access rootline element. Optional, will be determined automatically if not set.
416
     * @return string An Access Rootline.
417
     */
418 1
    protected function getAccessRootline(Item $item, $language = 0, $contentAccessGroup = null)
419
    {
420 1
        static $accessRootlineCache;
421
422 1
        $mountPointParameter = $this->getMountPageDataUrlParameter($item);
423
424 1
        $accessRootlineCacheEntryId = $item->getRecordUid() . '|' . $language;
425 1
        if ($mountPointParameter !== '') {
426
            $accessRootlineCacheEntryId .= '|' . $mountPointParameter;
427
        }
428 1
        if (!is_null($contentAccessGroup)) {
429 1
            $accessRootlineCacheEntryId .= '|' . $contentAccessGroup;
430
        }
431
432 1
        if (!isset($accessRootlineCache[$accessRootlineCacheEntryId])) {
433 1
            $accessRootline = $this->getAccessRootlineByPageId($item->getRecordUid(), $mountPointParameter);
434
435
            // current page's content access groups
436 1
            $contentAccessGroups = [$contentAccessGroup];
437 1
            if (is_null($contentAccessGroup)) {
438
                $contentAccessGroups = $this->getAccessGroupsFromContent($item, $language);
439
            }
440 1
            $element = GeneralUtility::makeInstance(RootlineElement::class, /** @scrutinizer ignore-type */ 'c:' . implode(',', $contentAccessGroups));
441 1
            $accessRootline->push($element);
442
443 1
            $accessRootlineCache[$accessRootlineCacheEntryId] = $accessRootline;
444
        }
445
446 1
        return $accessRootlineCache[$accessRootlineCacheEntryId];
447
    }
448
449
    /**
450
     * Returns the access rootLine for a certain pageId.
451
     *
452
     * @param int $pageId
453
     * @param string $mountPointparameter
454
     * @return Rootline
455
     */
456
    protected function getAccessRootlineByPageId($pageId, $mountPointParameter)
457
    {
458
        return Rootline::getAccessRootlineByPageId($pageId, $mountPointParameter);
459
    }
460
}
461