Passed
Push — task/2976_TYPO3.11_compatibili... ( fdb8e9...6dca74 )
by Rafael
23:01
created

Page::mountedPageExists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
namespace ApacheSolrForTypo3\Solr\IndexQueue\Initializer;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2011-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
 *  A copy is found in the textfile GPL.txt and important notices to the license
19
 *  from the author is found in LICENSE.txt distributed with these scripts.
20
 *
21
 *
22
 *  This script is distributed in the hope that it will be useful,
23
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
 *  GNU General Public License for more details.
26
 *
27
 *  This copyright notice MUST APPEAR in all copies of the script!
28
 ***************************************************************/
29
30
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\QueueItemRepository;
31
use ApacheSolrForTypo3\Solr\Domain\Site\SiteInterface;
32
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
33
use ApacheSolrForTypo3\Solr\IndexQueue\Item;
34
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
35
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
36
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
37
use Doctrine\DBAL\ConnectionException;
38
use Doctrine\DBAL\Exception as DBALException;
39
use TYPO3\CMS\Backend\Utility\BackendUtility;
40
use TYPO3\CMS\Core\Database\Connection;
41
use TYPO3\CMS\Core\Database\ConnectionPool;
42
use TYPO3\CMS\Core\Messaging\FlashMessage;
43
use TYPO3\CMS\Core\Utility\GeneralUtility;
44
45
/**
46
 * Index Queue initializer for pages which also covers resolution of mount
47
 * pages.
48
 *
49
 * @author Ingo Renner <[email protected]>
50
 */
51
class Page extends AbstractInitializer
52
{
53
    /**
54
     * The type of items this initializer is handling.
55
     * @var string
56
     */
57
    protected $type = 'pages';
58
59
    /**
60
     * Overrides the general setType() implementation, forcing type to "pages".
61
     *
62
     * @param string $type Type to initialize (ignored).
63
     */
64 12
    public function setType($type)
65
    {
66 12
        $this->type = 'pages';
67 12
    }
68
69
    /**
70
     * Initializes Index Queue page items for a site. Includes regular pages
71
     * and mounted pages - no nested mount page structures though.
72
     *
73
     * @return bool TRUE if initialization was successful, FALSE on error.
74
     */
75 12
    public function initialize()
76
    {
77 12
        $pagesInitialized = parent::initialize();
78 12
        $mountPagesInitialized = $this->initializeMountPointPages();
79
80 12
        return ($pagesInitialized && $mountPagesInitialized);
81
    }
82
83
    /**
84
     * Initialize a single page that is part of a mounted tree.
85
     *
86
     * @param array $mountProperties Array of mount point properties mountPageSource, mountPageDestination, and mountPageOverlayed
87
     * @param int $mountedPageId The ID of the mounted page
88
     */
89 1
    public function initializeMountedPage(array $mountProperties, int $mountedPageId)
90
    {
91 1
        $mountedPages = [$mountedPageId];
92
93 1
        $this->addMountedPagesToIndexQueue($mountedPages, $mountProperties);
94 1
        $this->addIndexQueueItemIndexingProperties($mountProperties, $mountedPages);
95 1
    }
96
97
    /**
98
     * Initializes Mount Point(pages) to be indexed through the Index Queue. The Mount
99
     * Points are searched and their mounted virtual sub-trees are then resolved
100
     * and added to the Index Queue as if they were actually present below the
101
     * Mount Point.
102
     *
103
     * @return bool TRUE if initialization of the Mount Pages was successful, FALSE otherwise
104
     * @throws ConnectionException
105
     */
106 12
    protected function initializeMountPointPages(): bool
107
    {
108 12
        $mountPointsInitialized = false;
109 12
        $mountPoints = $this->pagesRepository->findAllMountPagesByWhereClause(
110 12
            $this->buildPagesClause()
111 12
            . $this->buildTcaWhereClause()
112 12
            . ' AND doktype = 7 AND no_search = 0'
113
        );
114
115 12
        if (empty($mountPoints)) {
116 10
            return true;
117
        }
118
119 6
        $databaseConnection = $this->queueItemRepository->getConnectionForAllInTransactionInvolvedTables(
120 6
            'tx_solr_indexqueue_item',
121 6
            'tx_solr_indexqueue_indexing_property'
122
        );
123
124 6
        foreach ($mountPoints as $mountPoint) {
125 6
            if (!$this->validateMountPoint($mountPoint)) {
126 3
                continue;
127
            }
128
129 6
            $mountedPages = $this->resolveMountPageTree($mountPoint);
130
131
            // handling mount_pid_ol behavior
132 6
            if (!$mountPoint['mountPageOverlayed']) {
133
                // Add page like a regular page, as only the sub tree is mounted.
134
                // The page itself has its own content, which is handled like standard page.
135 3
                $indexQueue = GeneralUtility::makeInstance(Queue::class);
136 3
                $indexQueue->updateItem($this->type, $mountPoint['uid']);
137
            }
138
139
            // This can happen when the mount point does not show the content of the
140
            // mounted page and the mounted page does not have any subpages.
141 6
            if (empty($mountedPages)) {
142
                continue;
143
            }
144
145 6
            $databaseConnection->beginTransaction();
146
            try {
147 6
                $this->addMountedPagesToIndexQueue($mountedPages, $mountPoint);
148 6
                $this->addIndexQueueItemIndexingProperties($mountPoint, $mountedPages);
149
150 6
                $databaseConnection->commit();
151 6
                $mountPointsInitialized = true;
152
            } catch (\Throwable $e) {
153
                $databaseConnection->rollBack();
154
155
                $this->logger->log(
156
                    SolrLogManager::ERROR,
157
                    'Index Queue initialization failed for mount pages',
158
                    [
159
                        $e->__toString()
160
                    ]
161
                );
162
                break;
163
            }
164
        }
165
166 6
        return $mountPointsInitialized;
167
    }
168
169
    /**
170
     * Checks whether a Mount Point page is properly configured.
171
     *
172
     * @param array $mountPoint A mount page
173
     * @return bool TRUE if the Mount Page is OK, FALSE otherwise
174
     */
175 6
    protected function validateMountPoint(array $mountPoint): bool
176
    {
177 6
        $isValidMountPage = true;
178
179 6
        if (empty($mountPoint['mountPageSource'])) {
180 3
            $isValidMountPage = false;
181
182 3
            $flashMessage = GeneralUtility::makeInstance(
183 3
                FlashMessage::class,
184 3
                'Property "Mounted page" must not be empty. Invalid Mount Page configuration for page ID ' . $mountPoint['uid'] . '.',
185 3
                'Failed to initialize Mount Page tree. ',
186 3
                FlashMessage::ERROR
187
            );
188
            // @extensionScannerIgnoreLine
189 3
            $this->flashMessageQueue->addMessage($flashMessage);
190
        }
191
192 6
        if (!$this->mountedPageExists($mountPoint['mountPageSource'])) {
193 3
            $isValidMountPage = false;
194
195 3
            $flashMessage = GeneralUtility::makeInstance(
196 3
                FlashMessage::class,
197
                'The mounted page must be accessible in the frontend. '
198
                . 'Invalid Mount Page configuration for page ID '
199 3
                . $mountPoint['uid'] . ', the mounted page with ID '
200 3
                . $mountPoint['mountPageSource']
201 3
                . ' is not accessible in the frontend.',
202 3
                'Failed to initialize Mount Page tree. ',
203 3
                FlashMessage::ERROR
204
            );
205
            // @extensionScannerIgnoreLine
206 3
            $this->flashMessageQueue->addMessage($flashMessage);
207
        }
208
209 6
        return $isValidMountPage;
210
    }
211
212
    /**
213
     * Checks whether the mounted page (mount page source) exists. That is,
214
     * whether it accessible in the frontend. So the record must exist
215
     * (deleted = 0) and must not be hidden (hidden = 0).
216
     *
217
     * @param int $mountedPageId Mounted page ID
218
     * @return bool TRUE if the page is accessible in the frontend, FALSE otherwise.
219
     */
220 6
    protected function mountedPageExists($mountedPageId): bool
221
    {
222 6
        $mountedPageExists = false;
223
224 6
        $mountedPage = BackendUtility::getRecord('pages', $mountedPageId, 'uid', ' AND hidden = 0');
225 6
        if (!empty($mountedPage)) {
226 6
            $mountedPageExists = true;
227
        }
228
229 6
        return $mountedPageExists;
230
    }
231
232
    /**
233
     * Adds the virtual / mounted pages to the Index Queue as if they would
234
     * belong to the same site where they are mounted.
235
     *
236
     * @param array $mountedPages An array of mounted page IDs
237
     * @param array $mountProperties Array with mount point properties (mountPageSource, mountPageDestination, mountPageOverlayed)
238
     */
239 7
    protected function addMountedPagesToIndexQueue(array $mountedPages, array $mountProperties)
240
    {
241 7
        $mountPointIdentifier = $this->getMountPointIdentifier($mountProperties);
242 7
        $mountPointPageIsWithExistingQueueEntry = $this->queueItemRepository->findPageIdsOfExistingMountPagesByMountIdentifier($mountPointIdentifier);
243 7
        $mountedPagesThatNeedToBeAdded = array_diff($mountedPages, $mountPointPageIsWithExistingQueueEntry);
244
245 7
        if (count($mountedPagesThatNeedToBeAdded) === 0) {
246
            //nothing to do
247
            return;
248
        }
249
250
        /* @var Connection $connection */
251 7
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_solr_indexqueue_item');
252
253 7
        $mountIdentifier = $this->getMountPointIdentifier($mountProperties);
254 7
        $initializationQuery = 'INSERT INTO tx_solr_indexqueue_item (root, item_type, item_uid, indexing_configuration, indexing_priority, changed, has_indexing_properties, pages_mountidentifier, errors) '
255 7
            . $this->buildSelectStatement() . ', 1, ' . $connection->quote($mountIdentifier, \PDO::PARAM_STR) . ',""'
256 7
            . 'FROM pages '
257 7
            . 'WHERE '
258 7
            . 'uid IN(' . implode(',', $mountedPagesThatNeedToBeAdded) . ') '
259 7
            . $this->buildTcaWhereClause()
260 7
            . $this->buildUserWhereClause();
261 7
        $logData = ['query' => $initializationQuery];
262
263
        try {
264 7
            $logData['rows'] = $this->queueItemRepository->initializeByNativeSQLStatement($initializationQuery);
265
        } catch (DBALException $DBALException) {
266
            $logData['error'] = $DBALException->getCode() . ': ' . $DBALException->getMessage();
267
        }
268
269 7
        $this->logInitialization($logData);
270 7
    }
271
272
    /**
273
     * Adds Index Queue item indexing properties for mounted pages. The page
274
     * indexer later needs to know that he's dealing with a mounted page, the
275
     * indexing properties will let make it possible for the indexer to
276
     * distinguish the mounted pages.
277
     *
278
     * @param array $mountPage An array with information about the root/destination Mount Page
279
     * @param array $mountedPages An array of mounted page IDs
280
     */
281 7
    protected function addIndexQueueItemIndexingProperties(array $mountPage, array $mountedPages)
282
    {
283 7
        $mountIdentifier = $this->getMountPointIdentifier($mountPage);
284 7
        $mountPageItems = $this->queueItemRepository->findAllIndexQueueItemsByRootPidAndMountIdentifierAndMountedPids($this->site->getRootPageId(), $mountIdentifier, $mountedPages);
285
286 7
        foreach ($mountPageItems as $mountPageItemRecord) {
287
            /* @var Item $mountPageItem */
288 7
            $mountPageItem = GeneralUtility::makeInstance(Item::class, /** @scrutinizer ignore-type */ $mountPageItemRecord);
289 7
            $mountPageItem->setIndexingProperty('mountPageSource', $mountPage['mountPageSource']);
290 7
            $mountPageItem->setIndexingProperty('mountPageDestination', $mountPage['mountPageDestination']);
291 7
            $mountPageItem->setIndexingProperty('isMountedPage', '1');
292
293 7
            $mountPageItem->storeIndexingProperties();
294
        }
295 7
    }
296
297
    /**
298
     * Builds an identifier of the given mount point properties.
299
     *
300
     * @param array $mountProperties Array with mount point properties (mountPageSource, mountPageDestination, mountPageOverlayed)
301
     * @return string String consisting of mountPageSource-mountPageDestination-mountPageOverlayed
302
     */
303 7
    protected function getMountPointIdentifier(array $mountProperties)
304
    {
305 7
        return $mountProperties['mountPageSource']
306 7
        . '-' . $mountProperties['mountPageDestination']
307 7
        . '-' . $mountProperties['mountPageOverlayed'];
308
    }
309
310
    // Mount Page resolution
311
312
    /**
313
     * Gets all the pages from a mounted page tree.
314
     *
315
     * @param array $mountPage
316
     * @return array An array of page IDs in the mounted page tree
317
     */
318 6
    protected function resolveMountPageTree(array $mountPage): array
319
    {
320 6
        $mountPageSourceId = (int)$mountPage['mountPageSource'];
321
322 6
        $mountPageTree = $this->site->getPages($mountPageSourceId, 'pages');
323
324
        // Do not include $mountPageSourceId in tree, if the mount point is not set to overlay.
325 6
        if (!empty($mountPageTree) && !$mountPage['mountPageOverlayed']) {
326 3
            $mountPageTree = array_diff($mountPageTree , [$mountPageSourceId]);
327
        }
328
329 6
        return $mountPageTree;
330
    }
331
}
332