343 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace WapplerSystems\Meilisearch\IndexQueue\Initializer;
 | 
						|
 | 
						|
/***************************************************************
 | 
						|
 *  Copyright notice
 | 
						|
 *
 | 
						|
 *  (c) 2011-2015 Ingo Renner <ingo@typo3.org>
 | 
						|
 *  All rights reserved
 | 
						|
 *
 | 
						|
 *  This script is part of the TYPO3 project. The TYPO3 project is
 | 
						|
 *  free software; you can redistribute it and/or modify
 | 
						|
 *  it under the terms of the GNU General Public License as published by
 | 
						|
 *  the Free Software Foundation; either version 3 of the License, or
 | 
						|
 *  (at your option) any later version.
 | 
						|
 *
 | 
						|
 *  The GNU General Public License can be found at
 | 
						|
 *  http://www.gnu.org/copyleft/gpl.html.
 | 
						|
 *  A copy is found in the textfile GPL.txt and important notices to the license
 | 
						|
 *  from the author is found in LICENSE.txt distributed with these scripts.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 *  This script is distributed in the hope that it will be useful,
 | 
						|
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 *  GNU General Public License for more details.
 | 
						|
 *
 | 
						|
 *  This copyright notice MUST APPEAR in all copies of the script!
 | 
						|
 ***************************************************************/
 | 
						|
 | 
						|
use WapplerSystems\Meilisearch\Domain\Index\Queue\QueueItemRepository;
 | 
						|
use WapplerSystems\Meilisearch\Domain\Site\SiteRepository;
 | 
						|
use WapplerSystems\Meilisearch\IndexQueue\Item;
 | 
						|
use WapplerSystems\Meilisearch\IndexQueue\Queue;
 | 
						|
use WapplerSystems\Meilisearch\System\Logging\SolrLogManager;
 | 
						|
use WapplerSystems\Meilisearch\System\Records\Pages\PagesRepository;
 | 
						|
use Doctrine\DBAL\DBALException;
 | 
						|
use TYPO3\CMS\Backend\Utility\BackendUtility;
 | 
						|
use TYPO3\CMS\Core\Database\Connection;
 | 
						|
use TYPO3\CMS\Core\Database\ConnectionPool;
 | 
						|
use TYPO3\CMS\Core\Messaging\FlashMessage;
 | 
						|
use TYPO3\CMS\Core\Utility\GeneralUtility;
 | 
						|
 | 
						|
/**
 | 
						|
 * Index Queue initializer for pages which also covers resolution of mount
 | 
						|
 * pages.
 | 
						|
 *
 | 
						|
 * @author Ingo Renner <ingo@typo3.org>
 | 
						|
 */
 | 
						|
class Page extends AbstractInitializer
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * The type of items this initializer is handling.
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    protected $type = 'pages';
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var PagesRepository
 | 
						|
     */
 | 
						|
    protected $pagesRepository;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Constructor, sets type and indexingConfigurationName to "pages".
 | 
						|
     *
 | 
						|
     * @param QueueItemRepository|null $queueItemRepository
 | 
						|
     * @param PagesRepository|null $pagesRepository
 | 
						|
     */
 | 
						|
    public function __construct(QueueItemRepository $queueItemRepository = null, PagesRepository $pagesRepository = null)
 | 
						|
    {
 | 
						|
        parent::__construct($queueItemRepository);
 | 
						|
        $this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Overrides the general setType() implementation, forcing type to "pages".
 | 
						|
     *
 | 
						|
     * @param string $type Type to initialize (ignored).
 | 
						|
     */
 | 
						|
    public function setType($type)
 | 
						|
    {
 | 
						|
        $this->type = 'pages';
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Initializes Index Queue page items for a site. Includes regular pages
 | 
						|
     * and mounted pages - no nested mount page structures though.
 | 
						|
     *
 | 
						|
     * @return bool TRUE if initialization was successful, FALSE on error.
 | 
						|
     */
 | 
						|
    public function initialize()
 | 
						|
    {
 | 
						|
        $pagesInitialized = parent::initialize();
 | 
						|
        $mountPagesInitialized = $this->initializeMountPages();
 | 
						|
 | 
						|
        return ($pagesInitialized && $mountPagesInitialized);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Initialize a single page that is part of a mounted tree.
 | 
						|
     *
 | 
						|
     * @param array $mountProperties Array of mount point properties mountPageSource, mountPageDestination, and mountPageOverlayed
 | 
						|
     * @param int $mountPageId The ID of the mounted page
 | 
						|
     */
 | 
						|
    public function initializeMountedPage(array $mountProperties, $mountPageId)
 | 
						|
    {
 | 
						|
        $mountedPages = [$mountPageId];
 | 
						|
 | 
						|
        $this->addMountedPagesToIndexQueue($mountedPages, $mountProperties);
 | 
						|
        $this->addIndexQueueItemIndexingProperties($mountProperties, $mountedPages);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Initializes Mount Pages to be indexed through the Index Queue. The Mount
 | 
						|
     * Pages are searched and their mounted virtual sub-trees are then resolved
 | 
						|
     * and added to the Index Queue as if they were actually present below the
 | 
						|
     * Mount Page.
 | 
						|
     *
 | 
						|
     * @return bool TRUE if initialization of the Mount Pages was successful, FALSE otherwise
 | 
						|
     */
 | 
						|
    protected function initializeMountPages()
 | 
						|
    {
 | 
						|
        $mountPagesInitialized = false;
 | 
						|
        $mountPages = $this->pagesRepository->findAllMountPagesByWhereClause($this->buildPagesClause() . $this->buildTcaWhereClause() . ' AND doktype = 7 AND no_search = 0');
 | 
						|
 | 
						|
        if (empty($mountPages)) {
 | 
						|
            $mountPagesInitialized = true;
 | 
						|
            return $mountPagesInitialized;
 | 
						|
        }
 | 
						|
 | 
						|
        $databaseConnection = $this->queueItemRepository->getConnectionForAllInTransactionInvolvedTables(
 | 
						|
            'tx_meilisearch_indexqueue_item',
 | 
						|
            'tx_meilisearch_indexqueue_indexing_property'
 | 
						|
        );
 | 
						|
 | 
						|
        foreach ($mountPages as $mountPage) {
 | 
						|
            if (!$this->validateMountPage($mountPage)) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            $mountedPages = $this->resolveMountPageTree($mountPage);
 | 
						|
 | 
						|
            // handling mount_pid_ol behavior
 | 
						|
            if ($mountPage['mountPageOverlayed']) {
 | 
						|
                // the page shows the mounted page's content
 | 
						|
                $mountedPages[] = $mountPage['mountPageSource'];
 | 
						|
            } else {
 | 
						|
                // Add page like a regular page, as only the sub tree is
 | 
						|
                // mounted. The page itself has its own content.
 | 
						|
                $indexQueue = GeneralUtility::makeInstance(Queue::class);
 | 
						|
                $indexQueue->updateItem($this->type, $mountPage['uid']);
 | 
						|
            }
 | 
						|
 | 
						|
            // This can happen when the mount point does not show the content of the
 | 
						|
            // mounted page and the mounted page does not have any subpages.
 | 
						|
            if (empty($mountedPages)) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            $databaseConnection->beginTransaction();
 | 
						|
            try {
 | 
						|
                $this->addMountedPagesToIndexQueue($mountedPages, $mountPage);
 | 
						|
                $this->addIndexQueueItemIndexingProperties($mountPage, $mountedPages);
 | 
						|
 | 
						|
                $databaseConnection->commit();
 | 
						|
                $mountPagesInitialized = true;
 | 
						|
            } catch (\Exception $e) {
 | 
						|
                $databaseConnection->rollBack();
 | 
						|
 | 
						|
                $this->logger->log(
 | 
						|
                    SolrLogManager::ERROR,
 | 
						|
                    'Index Queue initialization failed for mount pages',
 | 
						|
                    [
 | 
						|
                        $e->__toString()
 | 
						|
                    ]
 | 
						|
                );
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return $mountPagesInitialized;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Checks whether a Mount Page is properly configured.
 | 
						|
     *
 | 
						|
     * @param array $mountPage A mount page
 | 
						|
     * @return bool TRUE if the Mount Page is OK, FALSE otherwise
 | 
						|
     */
 | 
						|
    protected function validateMountPage(array $mountPage)
 | 
						|
    {
 | 
						|
        $isValidMountPage = true;
 | 
						|
 | 
						|
        if (empty($mountPage['mountPageSource'])) {
 | 
						|
            $isValidMountPage = false;
 | 
						|
 | 
						|
            $flashMessage = GeneralUtility::makeInstance(
 | 
						|
                FlashMessage::class,
 | 
						|
                'Property "Mounted page" must not be empty. Invalid Mount Page configuration for page ID ' . $mountPage['uid'] . '.',
 | 
						|
                'Failed to initialize Mount Page tree. ',
 | 
						|
                FlashMessage::ERROR
 | 
						|
            );
 | 
						|
            // @extensionScannerIgnoreLine
 | 
						|
            $this->flashMessageQueue->addMessage($flashMessage);
 | 
						|
        }
 | 
						|
 | 
						|
        if (!$this->mountedPageExists($mountPage['mountPageSource'])) {
 | 
						|
            $isValidMountPage = false;
 | 
						|
 | 
						|
            $flashMessage = GeneralUtility::makeInstance(
 | 
						|
                FlashMessage::class,
 | 
						|
                'The mounted page must be accessible in the frontend. '
 | 
						|
                . 'Invalid Mount Page configuration for page ID '
 | 
						|
                . $mountPage['uid'] . ', the mounted page with ID '
 | 
						|
                . $mountPage['mountPageSource']
 | 
						|
                . ' is not accessible in the frontend.',
 | 
						|
                'Failed to initialize Mount Page tree. ',
 | 
						|
                FlashMessage::ERROR
 | 
						|
            );
 | 
						|
            // @extensionScannerIgnoreLine
 | 
						|
            $this->flashMessageQueue->addMessage($flashMessage);
 | 
						|
        }
 | 
						|
 | 
						|
        return $isValidMountPage;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Checks whether the mounted page (mount page source) exists. That is,
 | 
						|
     * whether it accessible in the frontend. So the record must exist
 | 
						|
     * (deleted = 0) and must not be hidden (hidden = 0).
 | 
						|
     *
 | 
						|
     * @param int $mountedPageId Mounted page ID
 | 
						|
     * @return bool TRUE if the page is accessible in the frontend, FALSE otherwise.
 | 
						|
     */
 | 
						|
    protected function mountedPageExists($mountedPageId)
 | 
						|
    {
 | 
						|
        $mountedPageExists = false;
 | 
						|
 | 
						|
        $mountedPage = BackendUtility::getRecord('pages', $mountedPageId, 'uid', ' AND hidden = 0');
 | 
						|
        if (!empty($mountedPage)) {
 | 
						|
            $mountedPageExists = true;
 | 
						|
        }
 | 
						|
 | 
						|
        return $mountedPageExists;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Adds the virtual / mounted pages to the Index Queue as if they would
 | 
						|
     * belong to the same site where they are mounted.
 | 
						|
     *
 | 
						|
     * @param array $mountedPages An array of mounted page IDs
 | 
						|
     * @param array $mountProperties Array with mount point properties (mountPageSource, mountPageDestination, mountPageOverlayed)
 | 
						|
     */
 | 
						|
    protected function addMountedPagesToIndexQueue(array $mountedPages, array $mountProperties)
 | 
						|
    {
 | 
						|
        $mountPointIdentifier = $this->getMountPointIdentifier($mountProperties);
 | 
						|
        $mountPointPageIsWithExistingQueueEntry = $this->queueItemRepository->findPageIdsOfExistingMountPagesByMountIdentifier($mountPointIdentifier);
 | 
						|
        $mountedPagesThatNeedToBeAdded = array_diff($mountedPages, $mountPointPageIsWithExistingQueueEntry);
 | 
						|
 | 
						|
        if (count($mountedPagesThatNeedToBeAdded) === 0) {
 | 
						|
            //nothing to do
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        /* @var Connection $connection */
 | 
						|
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_meilisearch_indexqueue_item');
 | 
						|
 | 
						|
        $mountIdentifier = $this->getMountPointIdentifier($mountProperties);
 | 
						|
        $initializationQuery = 'INSERT INTO tx_meilisearch_indexqueue_item (root, item_type, item_uid, indexing_configuration, indexing_priority, changed, has_indexing_properties, pages_mountidentifier, errors) '
 | 
						|
            . $this->buildSelectStatement() . ', 1, ' . $connection->quote($mountIdentifier, \PDO::PARAM_STR) . ',""'
 | 
						|
            . 'FROM pages '
 | 
						|
            . 'WHERE '
 | 
						|
            . 'uid IN(' . implode(',', $mountedPagesThatNeedToBeAdded) . ') '
 | 
						|
            . $this->buildTcaWhereClause()
 | 
						|
            . $this->buildUserWhereClause();
 | 
						|
        $logData = ['query' => $initializationQuery];
 | 
						|
 | 
						|
        try {
 | 
						|
            $logData['rows'] = $this->queueItemRepository->initializeByNativeSQLStatement($initializationQuery);
 | 
						|
        } catch (DBALException $DBALException) {
 | 
						|
            $logData['error'] = $DBALException->getCode() . ': ' . $DBALException->getMessage();
 | 
						|
        }
 | 
						|
 | 
						|
        $this->logInitialization($logData);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Adds Index Queue item indexing properties for mounted pages. The page
 | 
						|
     * indexer later needs to know that he's dealing with a mounted page, the
 | 
						|
     * indexing properties will let make it possible for the indexer to
 | 
						|
     * distinguish the mounted pages.
 | 
						|
     *
 | 
						|
     * @param array $mountPage An array with information about the root/destination Mount Page
 | 
						|
     * @param array $mountedPages An array of mounted page IDs
 | 
						|
     */
 | 
						|
    protected function addIndexQueueItemIndexingProperties(array $mountPage, array $mountedPages)
 | 
						|
    {
 | 
						|
        $mountIdentifier = $this->getMountPointIdentifier($mountPage);
 | 
						|
        $mountPageItems = $this->queueItemRepository->findAllIndexQueueItemsByRootPidAndMountIdentifierAndMountedPids($this->site->getRootPageId(), $mountIdentifier, $mountedPages);
 | 
						|
 | 
						|
        foreach ($mountPageItems as $mountPageItemRecord) {
 | 
						|
            /* @var Item $mountPageItem */
 | 
						|
            $mountPageItem = GeneralUtility::makeInstance(Item::class, /** @scrutinizer ignore-type */ $mountPageItemRecord);
 | 
						|
            $mountPageItem->setIndexingProperty('mountPageSource', $mountPage['mountPageSource']);
 | 
						|
            $mountPageItem->setIndexingProperty('mountPageDestination', $mountPage['mountPageDestination']);
 | 
						|
            $mountPageItem->setIndexingProperty('isMountedPage', '1');
 | 
						|
 | 
						|
            $mountPageItem->storeIndexingProperties();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Builds an identifier of the given mount point properties.
 | 
						|
     *
 | 
						|
     * @param array $mountProperties Array with mount point properties (mountPageSource, mountPageDestination, mountPageOverlayed)
 | 
						|
     * @return string String consisting of mountPageSource-mountPageDestination-mountPageOverlayed
 | 
						|
     */
 | 
						|
    protected function getMountPointIdentifier(array $mountProperties)
 | 
						|
    {
 | 
						|
        return $mountProperties['mountPageSource']
 | 
						|
        . '-' . $mountProperties['mountPageDestination']
 | 
						|
        . '-' . $mountProperties['mountPageOverlayed'];
 | 
						|
    }
 | 
						|
 | 
						|
    // Mount Page resolution
 | 
						|
    /**
 | 
						|
     * Gets all the pages from a mounted page tree.
 | 
						|
     *
 | 
						|
     * @param array $mountPage
 | 
						|
     * @return array An array of page IDs in the mounted page tree
 | 
						|
     */
 | 
						|
    protected function resolveMountPageTree($mountPage)
 | 
						|
    {
 | 
						|
        $mountPageSourceId = $mountPage['mountPageSource'];
 | 
						|
        $mountPageIdentifier = $this->getMountPointIdentifier($mountPage);
 | 
						|
 | 
						|
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
 | 
						|
        /* @var $siteRepository SiteRepository */
 | 
						|
        $mountedSite = $siteRepository->getSiteByPageId($mountPageSourceId, $mountPageIdentifier);
 | 
						|
 | 
						|
        return $mountedSite ? $mountedSite->getPages($mountPageSourceId) : [];
 | 
						|
    }
 | 
						|
}
 |