ext-solr icon indicating copy to clipboard operation
ext-solr copied to clipboard

Indexing pages below a backend user section leads to errors

Open mschwemer opened this issue 7 years ago • 1 comments

If a standard page (or any other indexable page) is below a page of type backend user section this leads to indexing error. The error message in the TYPO3 log is

Uncaught TYPO3 Exception: #1517584045: Solr Document can not be prepared. The Reason: ID was not an accessible page

This is correct so far, as these contents may not be indexed, but also they should not occur as errors.

mschwemer avatar Nov 07 '18 11:11 mschwemer

I wrote this event listener to solve this issue for our projects. Seems to do the trick.

<?php

declare(strict_types=1);

namespace Vendor\CustomSolr\Solr\EventListener;

use ApacheSolrForTypo3\Solr\Event\IndexQueue\AfterIndexQueueHasBeenInitializedEvent;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Exception;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;

final readonly class IndexQueueInitializationEventListener
{
    /**
     * @throws Exception
     */
    public function __invoke(AfterIndexQueueHasBeenInitializedEvent $event): void
    {
        if ($event->getIndexingConfigurationName() !== 'pages') {
            return;
        }

        $site = $event->getSite();
        $rootPageId = $site->getRootPageId();

        $this->removeBackendUserSectionSubpages($rootPageId);
    }

    /**
     * @throws Exception
     */
    private function removeBackendUserSectionSubpages(int $rootPageId): void
    {
        $backendUserSectionPageIds = $this->findBackendUserSectionPages($rootPageId);
        if (empty($backendUserSectionPageIds)) {
            return;
        }

        $subpageIds = [];
        foreach ($backendUserSectionPageIds as $pageId) {
            $subpageIds = array_merge($subpageIds, $this->findSubpagesOfPage($pageId));
        }

        if (empty($subpageIds)) {
            return;
        }

        $this->removeItemsFromIndexQueue($subpageIds);
    }

    /**
     * @throws Exception
     */
    private function findBackendUserSectionPages(int $rootPageId): array
    {
        /** @var QueryBuilder $queryBuilder */
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable('pages');

        $result = $queryBuilder
            ->select('uid')
            ->from('pages')
            ->where(
                $queryBuilder->expr()->eq('doktype', $queryBuilder->createNamedParameter(6, \PDO::PARAM_INT)),
                $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
            )
            ->executeQuery()
            ->fetchAllAssociative();

        $potentialBackendUserSectionPageIds = array_map(static function(array $page) {
            return (int)$page['uid'];
        }, $result);

        $backendUserSectionPageIds = [];
        foreach ($potentialBackendUserSectionPageIds as $pageId) {
            $isInSite = $this->isPageInSiteRootline($pageId, $rootPageId);
            if ($isInSite) {
                $backendUserSectionPageIds[] = $pageId;
            }
        }

        return $backendUserSectionPageIds;
    }

    /**
     * @throws Exception
     */
    private function isPageInSiteRootline(int $pageId, int $rootPageId): bool
    {
        if ($pageId === $rootPageId) {
            return true;
        }

        $connection = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getConnectionForTable('pages');

        $currentPageId = $pageId;
        $iterations = 0;
        while ($currentPageId > 0 && $iterations < 100) {
            $record = $connection->select(
                ['pid'],
                'pages',
                ['uid' => $currentPageId, 'deleted' => 0]
            )->fetchAssociative();

            if ($record === false) {
                return false;
            }

            $currentPageId = (int)$record['pid'];

            if ($currentPageId === $rootPageId) {
                return true;
            }

            $iterations++;
        }

        return false;
    }

    /**
     * @throws Exception
     */
    private function findSubpagesOfPage(int $pageId): array
    {
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable('pages');

        $allSubpages = [];
        $pagesToProcess = [$pageId];
        $processedPages = [];

        while (!empty($pagesToProcess)) {
            $currentPageId = array_shift($pagesToProcess);

            if (in_array($currentPageId, $processedPages, true)) {
                continue;
            }

            $processedPages[] = $currentPageId;

            $result = $queryBuilder->select('uid')
                ->from('pages')
                ->where(
                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($currentPageId, \PDO::PARAM_INT)),
                    $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
                )
                ->executeQuery()
                ->fetchAllAssociative();

            foreach ($result as $page) {
                $childPageId = (int)$page['uid'];
                $allSubpages[] = $childPageId;
                $pagesToProcess[] = $childPageId;
            }
        }

        return $allSubpages;
    }

    private function removeItemsFromIndexQueue(array $pageIds): void
    {
        if (empty($pageIds)) {
            return;
        }

        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable('tx_solr_indexqueue_item');

        $queryBuilder
            ->delete('tx_solr_indexqueue_item')
            ->where(
                $queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
                $queryBuilder->expr()->in(
                    'item_uid',
                    $queryBuilder->createNamedParameter($pageIds, ArrayParameterType::INTEGER)
                )
            )
            ->executeStatement();
    }
}

Services.yaml

  Vendor\CustomSolr\Solr\EventListener\IndexQueueInitializationEventListener:
    tags:
      - name: event.listener
        identifier: 'removeBackendUserSectionSubpages'
        event: ApacheSolrForTypo3\Solr\Event\IndexQueue\AfterIndexQueueHasBeenInitializedEvent

wazum avatar Apr 01 '25 13:04 wazum