diff --git a/Classes/AdditionalPageIndexer.php b/Classes/AdditionalPageIndexer.php deleted file mode 100644 index 6f71cdec4a..0000000000 --- a/Classes/AdditionalPageIndexer.php +++ /dev/null @@ -1,36 +0,0 @@ - - */ -interface AdditionalPageIndexer -{ - /** - * Provides additional documents that should be indexed together with a page. - * - * @param Document $pageDocument The original page document. - * @param array $allDocuments An array containing all the documents collected until here, including the page document - * @return array An array of additional \ApacheSolrForTypo3\Solr\System\Solr\Document\Document objects - */ - public function getAdditionalPageDocuments(Document $pageDocument, array $allDocuments): array; -} diff --git a/Classes/Event/Indexing/BeforeDocumentIsProcessedForIndexingEvent.php b/Classes/Event/Indexing/BeforeDocumentIsProcessedForIndexingEvent.php new file mode 100644 index 0000000000..a642566a6f --- /dev/null +++ b/Classes/Event/Indexing/BeforeDocumentIsProcessedForIndexingEvent.php @@ -0,0 +1,83 @@ +documents[] = $this->document; + } + + public function getSite(): Site + { + return $this->site; + } + + public function getSiteLanguage(): SiteLanguage + { + return $this->siteLanguage; + } + + public function getIndexQueueItem(): Item + { + return $this->indexQueueItem; + } + + public function getDocument(): Document + { + return $this->document; + } + + /** + * @param Document[] $documents + */ + public function addDocuments(array $documents): void + { + $this->documents = array_merge($this->documents, $documents); + } + + /** + * @return Document[] + */ + public function getDocuments(): array + { + return $this->documents; + } +} diff --git a/Classes/Event/Indexing/BeforeDocumentsAreIndexedEvent.php b/Classes/Event/Indexing/BeforeDocumentsAreIndexedEvent.php new file mode 100644 index 0000000000..153f5d81d1 --- /dev/null +++ b/Classes/Event/Indexing/BeforeDocumentsAreIndexedEvent.php @@ -0,0 +1,78 @@ +site; + } + + public function getSiteLanguage(): SiteLanguage + { + return $this->siteLanguage; + } + + public function getIndexQueueItem(): Item + { + return $this->indexQueueItem; + } + + public function getDocument(): Document + { + return $this->document; + } + + /** + * @param Document[] $documents + */ + public function addDocuments(array $documents): void + { + $this->documents = array_merge($this->documents, $documents); + } + + /** + * @return Document[] + */ + public function getDocuments(): array + { + return $this->documents; + } +} diff --git a/Classes/Event/Indexing/BeforePageDocumentIsProcessedForIndexingEvent.php b/Classes/Event/Indexing/BeforePageDocumentIsProcessedForIndexingEvent.php new file mode 100644 index 0000000000..4c8e7e3d92 --- /dev/null +++ b/Classes/Event/Indexing/BeforePageDocumentIsProcessedForIndexingEvent.php @@ -0,0 +1,82 @@ +documents[] = $this->document; + } + + public function getSite(): Site + { + return $this->site; + } + + public function getSiteLanguage(): SiteLanguage + { + return $this->siteLanguage; + } + + public function getIndexQueueItem(): Item + { + return $this->indexQueueItem; + } + + public function getDocument(): Document + { + return $this->document; + } + + /** + * @param Document[] $documents + */ + public function addDocuments(array $documents): void + { + $this->documents = array_merge($this->documents, $documents); + } + + /** + * @return Document[] + */ + public function getDocuments(): array + { + return $this->documents; + } +} diff --git a/Classes/IndexQueue/AdditionalIndexQueueItemIndexer.php b/Classes/IndexQueue/AdditionalIndexQueueItemIndexer.php deleted file mode 100644 index 736bb3b641..0000000000 --- a/Classes/IndexQueue/AdditionalIndexQueueItemIndexer.php +++ /dev/null @@ -1,37 +0,0 @@ - - */ -interface AdditionalIndexQueueItemIndexer -{ - /** - * Provides additional documents that should be indexed together with an Index Queue item. - * - * @param Item $item The item currently being indexed. - * @param int $language The language uid of the documents - * @param Document $itemDocument The original item document. - * @return Document[] array An array of additional Document objects - */ - public function getAdditionalItemDocuments(Item $item, int $language, Document $itemDocument): array; -} diff --git a/Classes/IndexQueue/Indexer.php b/Classes/IndexQueue/Indexer.php index 065cdca188..6871fd8dcb 100644 --- a/Classes/IndexQueue/Indexer.php +++ b/Classes/IndexQueue/Indexer.php @@ -21,6 +21,8 @@ use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Builder; use ApacheSolrForTypo3\Solr\Domain\Site\Site; use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository; +use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentIsProcessedForIndexingEvent; +use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentsAreIndexedEvent; use ApacheSolrForTypo3\Solr\Exception as EXTSolrException; use ApacheSolrForTypo3\Solr\FieldProcessor\Service; use ApacheSolrForTypo3\Solr\FrontendEnvironment; @@ -34,7 +36,7 @@ use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter; use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection; use Doctrine\DBAL\Exception as DBALException; -use InvalidArgumentException; +use Psr\EventDispatcher\EventDispatcherInterface; use RuntimeException; use Throwable; use TYPO3\CMS\Core\Context\LanguageAspectFactory; @@ -42,13 +44,12 @@ use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\RootlineUtility; -use UnexpectedValueException; /** * A general purpose indexer to be used for indexing of any kind of regular - * records like tt_news, tt_address, and so on. + * records like news records, tt_address, and so on. * Specialized indexers can extend this class to handle advanced stuff like - * category resolution in tt_news or file indexing. + * category resolution in news records or file indexing. * * @author Ingo Renner * @copyright (c) 2009-2015 Ingo Renner @@ -79,6 +80,7 @@ class Indexer extends AbstractIndexer protected bool $loggingEnabled = false; protected SolrLogManager $logger; + protected EventDispatcherInterface $eventDispatcher; public function __construct( array $options = [], @@ -87,6 +89,7 @@ public function __construct( ConnectionManager $connectionManager = null, FrontendEnvironment $frontendEnvironment = null, SolrLogManager $logger = null, + EventDispatcherInterface $eventDispatcher = null, ) { $this->options = $options; $this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class); @@ -94,6 +97,7 @@ public function __construct( $this->connectionManager = $connectionManager ?? GeneralUtility::makeInstance(ConnectionManager::class); $this->frontendEnvironment = $frontendEnvironment ?? GeneralUtility::makeInstance(FrontendEnvironment::class); $this->logger = $logger ?? GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__); + $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::makeInstance(EventDispatcherInterface::class); } /** @@ -149,8 +153,6 @@ public function index(Item $item): bool */ protected function indexItem(Item $item, int $language = 0): bool { - $documents = []; - $itemDocument = $this->itemToDocument($item, $language); if (is_null($itemDocument)) { /* @@ -162,10 +164,13 @@ protected function indexItem(Item $item, int $language = 0): bool return true; } - $documents[] = $itemDocument; - $documents = array_merge($documents, $this->getAdditionalDocuments($item, $language, $itemDocument)); + $documents = $this->getAdditionalDocuments($itemDocument, $item, $language); + $documents = $this->processDocuments($item, $documents); - $documents = self::preAddModifyDocuments($item, $language, $documents); + + $event = new BeforeDocumentsAreIndexedEvent($itemDocument, $item->getSite()->getTypo3SiteObject(), $item->getSite()->getTypo3SiteObject()->getLanguageById($language), $item, $documents); + $event = $this->eventDispatcher->dispatch($event); + $documents = $event->getDocuments(); $response = $this->currentlyUsedSolrConnection->getWriteService()->addDocuments($documents); if ($response->getHttpStatus() !== 200) { @@ -406,6 +411,24 @@ protected function getAccessRootline(Item $item): string return 'r:' . $accessRestriction; } + /** + * Adds the document to the list of all documents (done in the event constructor), + * and allows to add more documents before processing all of them. + * + * @return Document[] + */ + protected function getAdditionalDocuments(Document $itemDocument, Item $item, int $language): array + { + $event = new BeforeDocumentIsProcessedForIndexingEvent( + $itemDocument, + $item->getSite()->getTypo3SiteObject(), + $item->getSite()->getTypo3SiteObject()->getLanguageById($language), + $item + ); + $event = $this->eventDispatcher->dispatch($event); + return $event->getDocuments(); + } + /** * Sends the documents to the field processing service which takes care of * manipulating fields as defined in the field's configuration. @@ -436,79 +459,6 @@ protected function processDocuments(Item $item, array $documents): array return $documents; } - /** - * Allows third party extensions to provide additional documents which - * should be indexed for the current item. - * - * @param Item $item The item currently being indexed. - * @param int $language The language uid currently being indexed. - * @param Document $itemDocument The document representing the item for the given language. - * - * @return Document[] array An array of additional Document objects to index. - */ - protected function getAdditionalDocuments(Item $item, int $language, Document $itemDocument): array - { - $documents = []; - - if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'] ?? null)) { - foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'] as $classReference) { - if (!class_exists($classReference)) { - throw new InvalidArgumentException('Class does not exits' . $classReference, 1490363487); - } - $additionalIndexer = GeneralUtility::makeInstance($classReference); - if ($additionalIndexer instanceof AdditionalIndexQueueItemIndexer) { - $additionalDocuments = $additionalIndexer->getAdditionalItemDocuments($item, $language, $itemDocument); - - if (is_array($additionalDocuments)) { - $documents = array_merge( - $documents, - $additionalDocuments - ); - } - } else { - throw new UnexpectedValueException( - get_class($additionalIndexer) . ' must implement interface ' . AdditionalIndexQueueItemIndexer::class, - 1326284551 - ); - } - } - } - return $documents; - } - - /** - * Provides a hook to manipulate documents right before they get added to - * the Solr index. - * - * @param Item $item The item currently being indexed. - * @param int $language The language uid of the documents - * @param array $documents An array of documents to be indexed - * - * @return array An array of modified documents - */ - public static function preAddModifyDocuments(Item $item, int $language, array $documents): array - { - if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'] ?? null)) { - foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'] as $classReference) { - $documentsModifier = GeneralUtility::makeInstance($classReference); - - if ($documentsModifier instanceof PageIndexerDocumentsModifier) { - $documents = $documentsModifier->modifyDocuments($item, $language, $documents); - } else { - throw new RuntimeException( - 'The class "' . get_class($documentsModifier) - . '" registered as document modifier in hook - preAddModifyDocuments must implement interface - ApacheSolrForTypo3\Solr\IndexQueue\PageIndexerDocumentsModifier', - 1309522677 - ); - } - } - } - - return $documents; - } - // Initialization /** @@ -683,7 +633,7 @@ protected function setLogging(Item $item): void * Logs the item and what document was created from it * * @param Item $item The item that is being indexed. - * @param array $itemDocuments An array of Solr documents created from the item's data + * @param Document[] $itemDocuments An array of Solr documents created from the item's data * @param ResponseAdapter $response The Solr response for the particular index document */ protected function log(Item $item, array $itemDocuments, ResponseAdapter $response): void diff --git a/Classes/IndexQueue/PageIndexerDocumentsModifier.php b/Classes/IndexQueue/PageIndexerDocumentsModifier.php deleted file mode 100644 index 3073266a11..0000000000 --- a/Classes/IndexQueue/PageIndexerDocumentsModifier.php +++ /dev/null @@ -1,35 +0,0 @@ - - */ -interface PageIndexerDocumentsModifier -{ - /** - * Modifies the given documents - * - * @param Item $item The currently being indexed item. - * @param int $language The language uid of the documents - * @param array $documents An array of documents to be indexed - * @return array An array of modified documents - */ - public function modifyDocuments(Item $item, int $language, array $documents): array; -} diff --git a/Classes/Typo3PageIndexer.php b/Classes/Typo3PageIndexer.php index 75b4ba45ed..1e2e0c557e 100644 --- a/Classes/Typo3PageIndexer.php +++ b/Classes/Typo3PageIndexer.php @@ -19,9 +19,10 @@ use ApacheSolrForTypo3\Solr\Access\Rootline; use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Builder; +use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentsAreIndexedEvent; +use ApacheSolrForTypo3\Solr\Event\Indexing\BeforePageDocumentIsProcessedForIndexingEvent; use ApacheSolrForTypo3\Solr\FieldProcessor\Service; use ApacheSolrForTypo3\Solr\IndexQueue\FrontendHelper\PageFieldMappingIndexer; -use ApacheSolrForTypo3\Solr\IndexQueue\Indexer; use ApacheSolrForTypo3\Solr\IndexQueue\Item; use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration; use ApacheSolrForTypo3\Solr\System\Logging\DebugWriter; @@ -29,6 +30,7 @@ use ApacheSolrForTypo3\Solr\System\Solr\Document\Document; use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection; use Doctrine\DBAL\Exception as DBALException; +use Psr\EventDispatcher\EventDispatcherInterface; use RuntimeException; use Throwable; use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException; @@ -205,8 +207,6 @@ public function setSolrConnection(SolrConnection $solrConnection): void */ public function indexPage(): bool { - $documents = []; // this will become useful as soon as when starting to index individual records instead of whole pages - if (is_null($this->solrConnection)) { // intended early return as it doesn't make sense to continue // and waste processing time if the solr server isn't available @@ -219,14 +219,15 @@ public function indexPage(): bool $pageDocument = $this->substitutePageDocument($pageDocument); self::$pageSolrDocument = $pageDocument; - $documents[] = $pageDocument; - $documents = $this->getAdditionalDocuments($pageDocument, $documents); + $event = new BeforePageDocumentIsProcessedForIndexingEvent($pageDocument, $this->page->getSite(), $this->page->getLanguage(), $this->indexQueueItem); + $event = $this->getEventDispatcher()->dispatch($event); + $documents = $event->getDocuments(); + $this->processDocuments($documents); - $documents = Indexer::preAddModifyDocuments( - $this->indexQueueItem, - $this->page->getLanguage()->getLanguageId(), - $documents - ); + + $event = new BeforeDocumentsAreIndexedEvent($pageDocument, $this->page->getSite(), $this->page->getLanguage(), $this->indexQueueItem, $documents); + $event = $this->getEventDispatcher()->dispatch($event); + $documents = $event->getDocuments(); $pageIndexed = $this->addDocumentsToSolrIndex($documents); $this->documentsSentToSolr = $documents; @@ -296,8 +297,7 @@ protected function substitutePageDocument(Document $pageDocument): Document $substituteIndexer->setPageIndexingConfigurationName($indexConfigurationName); } - $substituteDocument = $substituteIndexer->getPageDocument($pageDocument); - $pageDocument = $substituteDocument; + $pageDocument = $substituteIndexer->getPageDocument($pageDocument); } return $pageDocument; @@ -311,44 +311,11 @@ protected function getIndexConfigurationNameForCurrentPage(): string return isset($this->indexQueueItem) ? $this->indexQueueItem->getIndexingConfigurationName() : 'pages'; } - /** - * Allows third party extensions to provide additional documents which - * should be indexed for the current page. - * - * @param Document $pageDocument The main document representing this page. - * @param Document[] $existingDocuments An array of documents already created for this page. - * @return array An array of additional Document objects to index - */ - protected function getAdditionalDocuments(Document $pageDocument, array $existingDocuments): array - { - $documents = $existingDocuments; - - if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['Indexer']['indexPageAddDocuments'] ?? null)) { - return $documents; - } - - foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['Indexer']['indexPageAddDocuments'] as $classReference) { - $additionalIndexer = GeneralUtility::makeInstance($classReference); - - if (!$additionalIndexer instanceof AdditionalPageIndexer) { - $message = get_class($additionalIndexer) . ' must implement interface ' . AdditionalPageIndexer::class; - throw new UnexpectedValueException($message, 1310491024); - } - - $additionalDocuments = $additionalIndexer->getAdditionalPageDocuments($pageDocument, $documents); - if (!empty($additionalDocuments)) { - $documents = array_merge($documents, $additionalDocuments); - } - } - - return $documents; - } - /** * Sends the given documents to the field processing service which takes * care of manipulating fields as defined in the field's configuration. * - * @param array $documents An array of documents to manipulate + * @param Document[] $documents An array of documents to manipulate * @throws DBALException * @throws Exception */ @@ -364,7 +331,7 @@ protected function processDocuments(array $documents): void /** * Adds the collected documents to the Solr index. * - * @param array $documents An array of Document objects. + * @param Document[] $documents An array of Document objects. * @return bool TRUE if documents were added successfully, FALSE otherwise */ protected function addDocumentsToSolrIndex(array $documents): bool @@ -442,10 +409,15 @@ public function setPageAccessRootline(Rootline $accessRootline): void /** * Gets the documents that have been sent to Solr * - * @return array An array of Document objects + * @return Document[] An array of Document objects */ public function getDocumentsSentToSolr(): array { return $this->documentsSentToSolr; } + + protected function getEventDispatcher(): EventDispatcherInterface + { + return GeneralUtility::makeInstance(EventDispatcherInterface::class); + } } diff --git a/Documentation/Development/Indexing.rst b/Documentation/Development/Indexing.rst index e126d89871..05016731d8 100644 --- a/Documentation/Development/Indexing.rst +++ b/Documentation/Development/Indexing.rst @@ -4,21 +4,44 @@ Indexing ======== -In this section i describe the possibilities to extend page indexing in EXT:solr with custom code. +In this section i describe the possibilities to extend page indexing in EXT:solr with custom code via TYPO3 Hooks or PSR-14 events. Page Indexing ============= There are several points to extend the Typo3PageIndexer class and register own classes that are used during the indexing. -indexPageAddDocuments ---------------------- +BeforePageDocumentIsProcessedForIndexingEvent +--------------------------------------------- -Registered classes can be used to add additional documents to solr when a page get's indexed. +Registered Event Listeners can be used to add additional documents to solr when a page gets indexed. -Registration with: $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['Indexer']['indexPageAddDocuments'] -Required Interface: AdditionalPageIndexer +Registration of an event listener in your extension's :file:`Services.yaml`: +.. code-block:: yaml + + MyVendor\MyPackage\EventListeners\MyEventListener: + tags: + - name: event.listener + identifier: 'my-package/modify-documents' + +The corresponding event listener class: + +.. code-block:: php + + use ApacheSolrForTypo3\Solr\Event\Indexing\BeforePageDocumentIsProcessedForIndexingEvent; + use ApacheSolrForTypo3\Solr\System\Solr\Document\Document; + + class MyEventListener { + + public function __invoke(BeforePageDocumentIsProcessedForIndexingEvent $event): void + { + $additionalDocument = new Document(); + $event->addDocuments([$additionalDocument]); + } + } + +For other records than pages, the PSR-14 Event :php:class:`ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentIsProcessedForIndexingEvent` can be used. indexPageSubstitutePageDocument ------------------------------- @@ -30,13 +53,35 @@ Registration with: $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['Indexer']['in Required Interface: SubstitutePageIndexer -preAddModifyDocuments ---------------------- +BeforeDocumentsAreIndexedEvent +------------------------------ + +Registered Event Listeners can be used to process Solr documents (pages and records) before they are added to index. + +Registration of an event listener in your extension's :file:`Services.yaml`: + +.. code-block:: yaml + + MyVendor\MyPackage\EventListeners\MyEventListener: + tags: + - name: event.listener + identifier: 'my-package/modify-documents' + +The corresponding event listener class: + +.. code-block:: php + + use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentsAreIndexedEvent; -Registered classes can be used to process Solr documents (pages and records) before they are added to index. + class MyEventListener { -Registration with: $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'] -Required Interface: PageIndexerDocumentsModifier + public function __invoke(BeforeDocumentsAreIndexedEvent $event): void + { + foreach ($event->getDocuments() as $document) { + $document->addField('my_custom_field', 'my_custom_value'); + } + } + } Independent indexer @@ -59,8 +104,7 @@ If external data should be indexed or the RecordIndexer is not required, it is p class Indexer { - /** @var ConnectionManager */ - protected $connectionManager; + protected ConnectionManager $connectionManager; public function __construct() { @@ -96,7 +140,7 @@ If external data should be indexed or the RecordIndexer is not required, it is p $connections = $this->connectionManager->getAllConnections(); foreach ($connections as $connectionLanguage => $connection) { /** @var SolrConnection */ - $connection->getWriteService()->deleteByType('cutom_type'); + $connection->getWriteService()->deleteByType('custom_type'); } } @@ -148,9 +192,9 @@ If external data should be indexed or the RecordIndexer is not required, it is p $document = GeneralUtility::makeInstance(Document::class); // required fields - $document->setField('id', 'cutom_type_' . $itemRecord['uid']); - $document->setField('variantId', 'cutom_type' . $itemRecord['uid']); - $document->setField('type', 'cutom_type'); + $document->setField('id', 'custom_type_' . $itemRecord['uid']); + $document->setField('variantId', 'custom_type' . $itemRecord['uid']); + $document->setField('type', 'custom_type'); $document->setField('appKey', 'EXT:solr'); $document->setField('access', ['r:0']); diff --git a/Tests/Integration/Fixtures/Extensions/fake_extension2/Classes/EventListeners/AddAdditionalTestDocumentsToIndexer.php b/Tests/Integration/Fixtures/Extensions/fake_extension2/Classes/EventListeners/AddAdditionalTestDocumentsToIndexer.php new file mode 100644 index 0000000000..060471d59e --- /dev/null +++ b/Tests/Integration/Fixtures/Extensions/fake_extension2/Classes/EventListeners/AddAdditionalTestDocumentsToIndexer.php @@ -0,0 +1,31 @@ +getIndexQueueItem()->getRecord()['activate-event-listener'] ?? '') === true) { + $event->addDocuments([new Document(['can-be-an-alternative-record' => 'additional-test-document'])]); + } + } +} diff --git a/Tests/Integration/Fixtures/Extensions/fake_extension2/Configuration/Services.yaml b/Tests/Integration/Fixtures/Extensions/fake_extension2/Configuration/Services.yaml new file mode 100644 index 0000000000..4b12ac4851 --- /dev/null +++ b/Tests/Integration/Fixtures/Extensions/fake_extension2/Configuration/Services.yaml @@ -0,0 +1,10 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + ApacheSolrForTypo3\SolrFakeExtension2\EventListeners\AddAdditionalTestDocumentsToIndexer: + tags: + - name: event.listener + identifier: 'fake-extension2/example-documents-before-indexing' diff --git a/Tests/Integration/Fixtures/Extensions/fake_extension2/composer.json b/Tests/Integration/Fixtures/Extensions/fake_extension2/composer.json index d61effbe0c..94cbf3a150 100644 --- a/Tests/Integration/Fixtures/Extensions/fake_extension2/composer.json +++ b/Tests/Integration/Fixtures/Extensions/fake_extension2/composer.json @@ -3,6 +3,14 @@ "type": "typo3-cms-extension", "description": "Fake Extension 2", "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "*" + }, + "autoload": { + "psr-4": { + "ApacheSolrForTypo3\\SolrFakeExtension2\\": "Classes/" + } + }, "extra": { "typo3/cms": { "extension-key": "fake_extension2" diff --git a/Tests/Integration/IndexQueue/FrontendHelper/TestAdditionalPageIndexer.php b/Tests/Integration/Fixtures/Extensions/fake_extension3/Classes/EventListeners/AddExampleDocumentsBeforePageIndexing.php similarity index 56% rename from Tests/Integration/IndexQueue/FrontendHelper/TestAdditionalPageIndexer.php rename to Tests/Integration/Fixtures/Extensions/fake_extension3/Classes/EventListeners/AddExampleDocumentsBeforePageIndexing.php index 1e345808cd..fb95bdc6d2 100644 --- a/Tests/Integration/IndexQueue/FrontendHelper/TestAdditionalPageIndexer.php +++ b/Tests/Integration/Fixtures/Extensions/fake_extension3/Classes/EventListeners/AddExampleDocumentsBeforePageIndexing.php @@ -15,25 +15,21 @@ * The TYPO3 project - inspiring people to share! */ -namespace ApacheSolrForTypo3\Solr\Tests\Integration\IndexQueue\FrontendHelper; +namespace ApacheSolrForTypo3\SolrFakeExtension3\EventListeners; -use ApacheSolrForTypo3\Solr\AdditionalPageIndexer; -use ApacheSolrForTypo3\Solr\System\Solr\Document\Document; +use ApacheSolrForTypo3\Solr\Event\Indexing\BeforePageDocumentIsProcessedForIndexingEvent; -class TestAdditionalPageIndexer implements AdditionalPageIndexer +final class AddExampleDocumentsBeforePageIndexing { /** * Provides additional documents that should be indexed together with a page. - * - * @param Document $pageDocument The original page document. - * @param array $allDocuments An array containing all the documents collected until here, including the page document - * @return Document[] array An array of additional Document objects */ - public function getAdditionalPageDocuments(Document $pageDocument, array $allDocuments): array + public function __invoke(BeforePageDocumentIsProcessedForIndexingEvent $event): void { if (!($_GET['additionalTestPageIndexer'] ?? false)) { - return []; + return; } + $pageDocument = $event->getDocument(); $secondDocument = clone $pageDocument; $id = $pageDocument['id']; @@ -41,6 +37,6 @@ public function getAdditionalPageDocuments(Document $pageDocument, array $allDoc $secondDocument->setField('id', $copyId); $secondDocument->setField('custom_stringS', 'additional text'); - return [$secondDocument]; + $event->addDocuments([$secondDocument]); } } diff --git a/Tests/Integration/Fixtures/Extensions/fake_extension3/Classes/EventListeners/TestModificationOfDocuments.php b/Tests/Integration/Fixtures/Extensions/fake_extension3/Classes/EventListeners/TestModificationOfDocuments.php new file mode 100644 index 0000000000..019d4cbb39 --- /dev/null +++ b/Tests/Integration/Fixtures/Extensions/fake_extension3/Classes/EventListeners/TestModificationOfDocuments.php @@ -0,0 +1,33 @@ +getDocuments() as $document) { + $document->addField('postProcessorField_stringS', 'postprocessed'); + } + } +} diff --git a/Tests/Integration/Fixtures/Extensions/fake_extension3/Configuration/Services.yaml b/Tests/Integration/Fixtures/Extensions/fake_extension3/Configuration/Services.yaml new file mode 100644 index 0000000000..f848a6c538 --- /dev/null +++ b/Tests/Integration/Fixtures/Extensions/fake_extension3/Configuration/Services.yaml @@ -0,0 +1,15 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + ApacheSolrForTypo3\SolrFakeExtension3\EventListeners\AddExampleDocumentsBeforePageIndexing: + tags: + - name: event.listener + identifier: 'fake-extension3/example-documents-before-page-indexing' + + ApacheSolrForTypo3\SolrFakeExtension3\EventListeners\TestModificationOfDocuments: + tags: + - name: event.listener + identifier: 'fake-extension3/test-modification-of-documents' diff --git a/Tests/Integration/Fixtures/Extensions/fake_extension3/composer.json b/Tests/Integration/Fixtures/Extensions/fake_extension3/composer.json index f2c25071d7..c479fe8081 100644 --- a/Tests/Integration/Fixtures/Extensions/fake_extension3/composer.json +++ b/Tests/Integration/Fixtures/Extensions/fake_extension3/composer.json @@ -3,6 +3,14 @@ "type": "typo3-cms-extension", "description": "Fake Extension 3 Test", "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "*" + }, + "autoload": { + "psr-4": { + "ApacheSolrForTypo3\\SolrFakeExtension3\\": "Classes/" + } + }, "extra": { "typo3/cms": { "extension-key": "fake_extension3" diff --git a/Tests/Integration/Fixtures/Extensions/fake_extension3/ext_localconf.php b/Tests/Integration/Fixtures/Extensions/fake_extension3/ext_localconf.php index 61adfc065c..c0e8060b99 100644 --- a/Tests/Integration/Fixtures/Extensions/fake_extension3/ext_localconf.php +++ b/Tests/Integration/Fixtures/Extensions/fake_extension3/ext_localconf.php @@ -1,11 +1,5 @@ addField('postProcessorField_stringS', 'postprocessed'); - } - - return $documents; - } -} diff --git a/Tests/Integration/IndexQueue/Helpers/DummyAdditionalIndexQueueItemIndexer.php b/Tests/Integration/IndexQueue/Helpers/DummyAdditionalIndexQueueItemIndexer.php deleted file mode 100644 index 5dc7155800..0000000000 --- a/Tests/Integration/IndexQueue/Helpers/DummyAdditionalIndexQueueItemIndexer.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ -class DummyAdditionalIndexQueueItemIndexer implements AdditionalIndexQueueItemIndexer -{ - /** - * Provides additional documents that should be indexed together with an Index Queue item. - * - * @param Item $item The item currently being indexed. - * @param int $language The language uid of the documents - * @param Document $itemDocument The original item document. - * - * @return Document[] An array of additional Apache Solr Document objects - */ - public function getAdditionalItemDocuments(Item $item, int $language, Document $itemDocument): array - { - return []; - } -} diff --git a/Tests/Integration/IndexQueue/Helpers/DummyIndexer.php b/Tests/Integration/IndexQueue/Helpers/DummyIndexer.php deleted file mode 100644 index 7ff120b9e4..0000000000 --- a/Tests/Integration/IndexQueue/Helpers/DummyIndexer.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ -class DummyIndexer -{ -} diff --git a/Tests/Integration/IndexQueue/IndexerTest.php b/Tests/Integration/IndexQueue/IndexerTest.php index 04e23088bc..963b5cc0f2 100644 --- a/Tests/Integration/IndexQueue/IndexerTest.php +++ b/Tests/Integration/IndexQueue/IndexerTest.php @@ -15,23 +15,18 @@ namespace ApacheSolrForTypo3\Solr\Tests\Integration\IndexQueue; -use ApacheSolrForTypo3\Solr\IndexQueue\AdditionalIndexQueueItemIndexer; use ApacheSolrForTypo3\Solr\IndexQueue\Indexer; use ApacheSolrForTypo3\Solr\IndexQueue\Item; use ApacheSolrForTypo3\Solr\IndexQueue\Queue; use ApacheSolrForTypo3\Solr\System\Solr\Document\Document; use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection; -use ApacheSolrForTypo3\Solr\Tests\Integration\IndexQueue\Helpers\DummyAdditionalIndexQueueItemIndexer; -use ApacheSolrForTypo3\Solr\Tests\Integration\IndexQueue\Helpers\DummyIndexer; use ApacheSolrForTypo3\Solr\Tests\Integration\IntegrationTest; -use InvalidArgumentException; use Psr\Http\Server\RequestHandlerInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Http\ServerRequestFactory; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; use TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute; use TYPO3\CMS\Core\Utility\GeneralUtility; -use UnexpectedValueException; /** * Testcase for the record indexer @@ -412,59 +407,20 @@ public function canUseConfigurationFromTemplateInRootLine(): void /** * @test */ - public function canGetAdditionalDocumentsInterfaceOnly(): void + public function canGetAdditionalDocumentsViaPsr14EventListener(): void { - $this->expectException(InvalidArgumentException::class); - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'][] = AdditionalIndexQueueItemIndexer::class; + $this->importCSVDataSet(__DIR__ . '/../Fixtures/sites_setup_and_data_set/01_integration_tree_one.csv'); $document = new Document(); - $metaData = ['item_type' => 'pages']; - $record = []; - $item = GeneralUtility::makeInstance(Item::class, $metaData, $record); - $this->callInaccessibleMethod($this->indexer, 'getAdditionalDocuments', $item, 0, $document); - } - - /** - * @test - */ - public function canGetAdditionalDocumentsNotImplementingInterface(): void - { - $this->expectException(UnexpectedValueException::class); - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'][] = DummyIndexer::class; - $document = new Document(); - $metaData = ['item_type' => 'pages']; - $record = []; - $item = GeneralUtility::makeInstance(Item::class, $metaData, $record); - $this->callInaccessibleMethod($this->indexer, 'getAdditionalDocuments', $item, 0, $document); - } - - /** - * @test - */ - public function canGetAdditionalDocumentsNonExistingClass(): void - { - $this->expectException(InvalidArgumentException::class); - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'][] = 'NonExistingClass'; - $document = new Document(); - $metaData = ['item_type' => 'pages']; - $record = []; - $item = GeneralUtility::makeInstance(Item::class, $metaData, $record); - - $this->callInaccessibleMethod($this->indexer, 'getAdditionalDocuments', $item, 0, $document); - } - - /** - * @test - */ - public function canGetAdditionalDocuments(): void - { - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'][] = DummyAdditionalIndexQueueItemIndexer::class; - $document = new Document(); - $metaData = ['item_type' => 'pages']; - $record = []; - $item = GeneralUtility::makeInstance(Item::class, $metaData, $record); - - $result = $this->callInaccessibleMethod($this->indexer, 'getAdditionalDocuments', $item, 0, $document); - self::assertSame([], $result); + $document->setField('original-document', true); + $metaData = ['item_type' => 'pages', 'root' => 1]; + $record = ['uid' => 1, 'pid' => 0, 'activate-event-listener' => true]; + $item = new Item($metaData, $record); + + $result = $this->callInaccessibleMethod($this->indexer, 'getAdditionalDocuments', $document, $item, 0); + // Result contains two documents, one from the event listener and the original one above + self::assertCount(2, $result); + self::assertSame($document, $result[0]); + self::assertEquals(['can-be-an-alternative-record' => 'additional-test-document'], $result[1]->getFields()); } /** diff --git a/Tests/Unit/IndexQueue/IndexerTest.php b/Tests/Unit/IndexQueue/IndexerTest.php index 518dcff3e4..ec48e4ec03 100644 --- a/Tests/Unit/IndexQueue/IndexerTest.php +++ b/Tests/Unit/IndexQueue/IndexerTest.php @@ -16,13 +16,15 @@ namespace ApacheSolrForTypo3\Solr\Tests\Unit\IndexQueue; use ApacheSolrForTypo3\Solr\ConnectionManager; +use ApacheSolrForTypo3\Solr\Domain\Index\Queue\IndexQueueIndexingPropertyRepository; +use ApacheSolrForTypo3\Solr\Domain\Index\Queue\QueueItemRepository; use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Builder; +use ApacheSolrForTypo3\Solr\Domain\Site\Site; +use ApacheSolrForTypo3\Solr\Event\Indexing\BeforeDocumentIsProcessedForIndexingEvent; use ApacheSolrForTypo3\Solr\FrontendEnvironment; -use ApacheSolrForTypo3\Solr\IndexQueue\AdditionalIndexQueueItemIndexer; use ApacheSolrForTypo3\Solr\IndexQueue\Exception\IndexingException; use ApacheSolrForTypo3\Solr\IndexQueue\Indexer; use ApacheSolrForTypo3\Solr\IndexQueue\Item; -use ApacheSolrForTypo3\Solr\IndexQueue\PageIndexerDocumentsModifier; use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager; use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository; use ApacheSolrForTypo3\Solr\System\Solr\Document\Document; @@ -30,11 +32,10 @@ use ApacheSolrForTypo3\Solr\System\Solr\Service\SolrWriteService; use ApacheSolrForTypo3\Solr\System\Solr\SolrConnection; use ApacheSolrForTypo3\Solr\Tests\Unit\SetUpUnitTestCase; -use InvalidArgumentException; +use Psr\EventDispatcher\EventDispatcherInterface; use ReflectionClass; -use RuntimeException; +use TYPO3\CMS\Core\Tests\Unit\Fixtures\EventDispatcher\MockEventDispatcher; use TYPO3\CMS\Core\Utility\GeneralUtility; -use UnexpectedValueException; /** * Class IndexerTest @@ -62,11 +63,14 @@ public function canTriggerIndexingAndIndicateIndexStatus(int $httpStatus, bool $ $indexer = $this->getAccessibleMock( Indexer::class, - ['itemToDocument', 'processDocuments', 'getAdditionalDocuments'], + ['itemToDocument', 'processDocuments'], [], '', false ); + $eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $eventDispatcher->expects(self::any())->method('dispatch')->willReturnArgument(0); + $indexer->_set('eventDispatcher', $eventDispatcher); $solrConnectionMock = $this->createMock(SolrConnection::class); $solrConnectionMock @@ -75,7 +79,9 @@ public function canTriggerIndexingAndIndicateIndexStatus(int $httpStatus, bool $ ->willReturn($writeServiceMock); $indexer->_set('currentlyUsedSolrConnection', $solrConnectionMock); + $siteMock = $this->createMock(Site::class); $itemMock = $this->createMock(Item::class); + $itemMock->expects(self::any())->method('getSite')->willReturn($siteMock); $itemDocumentMock = $this->createMock(Document::class); $indexer ->expects(self::once()) @@ -83,12 +89,6 @@ public function canTriggerIndexingAndIndicateIndexStatus(int $httpStatus, bool $ ->with($itemMock, 0) ->willReturn($itemDocumentMock); - $indexer - ->expects(self::once()) - ->method('getAdditionalDocuments') - ->with($itemMock, 0, $itemDocumentMock) - ->willReturn([]); - $indexer ->expects(self::once()) ->method('processDocuments') @@ -134,20 +134,8 @@ public function canTriggerIndexingAndIndicateIndexStatusDataProvider(): \Generat * @test * @dataProvider canGetAdditionalDocumentsDataProvider */ - public function canGetAdditionalDocuments(\stdClass|string|AdditionalIndexQueueItemIndexer|null $class, ?string $expectedException, int $expectedResultCount): void + public function canGetAdditionalDocuments(\Closure|null $listener, ?string $expectedException, int $expectedResultCount): void { - if ($class !== null) { - if (is_object($class)) { - $classReference = get_class($class); - GeneralUtility::addInstance($classReference, $class); - } else { - $classReference = $class; - } - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['indexItemAddDocuments'] = [ - $classReference, - ]; - } - $indexer = $this->getAccessibleMock( Indexer::class, null, @@ -156,17 +144,37 @@ public function canGetAdditionalDocuments(\stdClass|string|AdditionalIndexQueueI false ); + $eventDispatcher = new MockEventDispatcher(); + if ($listener) { + $eventDispatcher->addListener($listener); + } + $indexer->_set('eventDispatcher', $eventDispatcher); + if ($expectedException !== null) { self::expectException($expectedException); } + $itemMock = new class ([], [], $this->createMock(IndexQueueIndexingPropertyRepository::class), $this->createMock(QueueItemRepository::class)) extends Item { + protected $site; + public function setSite(Site $site): void + { + $this->site = $site; + } + public function getSite(): ?Site + { + return $this->site; + } + }; + $itemMock->setSite($this->createMock(Site::class)); + + // new $itemMock() $documents = $indexer->_call( 'getAdditionalDocuments', - $this->createMock(Item::class), + new Document(), + $itemMock, 0, - $this->createMock(Document::class) ); - self::assertEquals($expectedResultCount, count($documents)); + self::assertCount($expectedResultCount, $documents); foreach ($documents as $document) { self::assertTrue($document instanceof Document); } @@ -177,87 +185,28 @@ public function canGetAdditionalDocuments(\stdClass|string|AdditionalIndexQueueI */ public function canGetAdditionalDocumentsDataProvider(): \Generator { - yield 'no AdditionalIndexQueueItemIndexer registered' => [ + yield 'no listener registered' => [ null, null, - 0, - ]; - - yield 'unknown class as AdditionalIndexQueueItemIndexer registered' => [ - 'invalidClass', - InvalidArgumentException::class, - 0, - ]; - - yield 'invalid AdditionalIndexQueueItemIndexer registered' => [ - new \stdClass(), - UnexpectedValueException::class, - 0, + 1, ]; - $indexerMock = $this->createMock(AdditionalIndexQueueItemIndexer::class); - $indexerMock - ->expects(self::once()) - ->method('getAdditionalItemDocuments'); - yield 'valid AdditionalIndexQueueItemIndexer, no additional documents' => [ - $indexerMock, + yield 'valid listener, no additional documents' => [ + function (BeforeDocumentIsProcessedForIndexingEvent $event) { + // Does nothing + }, null, - 0, + 1, ]; - - $indexerMock = $this->createMock(AdditionalIndexQueueItemIndexer::class); - $indexerMock - ->expects(self::once()) - ->method('getAdditionalItemDocuments') - ->willReturn([$this->createMock(Document::class)]); - yield 'valid AdditionalIndexQueueItemIndexer, one additional documents' => [ - $indexerMock, + yield 'valid listener, adds an additional document' => [ + function (BeforeDocumentIsProcessedForIndexingEvent $event) { + $event->addDocuments([new Document()]); + }, null, - 1, + 2, ]; } - /** - * @test - * @dataProvider canCallDocumentsModifierHookDataProvider - */ - public function canCallDocumentsModifierHook(?object $modifier, ?string $expectedException): void - { - $itemMock = $this->createMock(Item::class); - if ($modifier !== null) { - $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueueIndexer']['preAddModifyDocuments'] = [ - get_class($modifier), - ]; - } else { - $modifier = $this->createMock(PageIndexerDocumentsModifier::class); - $modifier->expects(self::never())->method('modifyDocuments'); - } - GeneralUtility::addInstance(get_class($modifier), $modifier); - - if ($expectedException !== null) { - self::expectException($expectedException); - } - - Indexer::preAddModifyDocuments($itemMock, 0, []); - } - - /** - * Data provider for "canCallDocumentsModifierHook" - */ - public function canCallDocumentsModifierHookDataProvider(): \Generator - { - yield 'no modifier' => [null, null]; - - yield 'invalid modifier' => [new \stdClass(), RuntimeException::class]; - - $modifierMock = $this->createMock(PageIndexerDocumentsModifier::class); - $modifierMock - ->expects(self::once()) - ->method('modifyDocuments') - ->willReturn([]); - yield 'valid modifier' => [$modifierMock, null]; - } - /** * @test * @skip diff --git a/Tests/Unit/IndexQueue/PageIndexerTest.php b/Tests/Unit/IndexQueue/PageIndexerTest.php index f53779cf72..61b0690922 100644 --- a/Tests/Unit/IndexQueue/PageIndexerTest.php +++ b/Tests/Unit/IndexQueue/PageIndexerTest.php @@ -29,6 +29,7 @@ use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository; use ApacheSolrForTypo3\Solr\Tests\Unit\SetUpUnitTestCase; use PHPUnit\Framework\MockObject\MockObject; +use Psr\EventDispatcher\EventDispatcherInterface; class PageIndexerTest extends SetUpUnitTestCase { @@ -64,6 +65,7 @@ protected function getPageIndexerWithMockedDependencies(array $options = []): Pa $this->connectionManagerMock, $this->frontendEnvironmentMock, $this->solrLogManagerMock, + $this->createMock(EventDispatcherInterface::class), ] ) ->onlyMethods(['getPageIndexerRequest', 'getAccessRootlineByPageId', 'getUriStrategy']) diff --git a/composer.json b/composer.json index 2e60998cf3..8dba6a87a0 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ "phpunit/phpunit": "^9.5", "typo3/cms-fluid-styled-content": "*", "typo3/coding-standards": "~0.7.1", - "typo3/testing-framework": "^7.0", + "typo3/testing-framework": "^7.0.2", "phpstan/phpstan": "^1.10", "phpstan/phpstan-phpunit": "^1.3" }, @@ -59,6 +59,8 @@ "autoload-dev": { "psr-4": { "ApacheSolrForTypo3\\Solr\\Tests\\": "Tests/", + "ApacheSolrForTypo3\\SolrFakeExtension2\\": "Tests/Integration/Fixtures/Extensions/fake_extension2/Classes/", + "ApacheSolrForTypo3\\SolrFakeExtension3\\": "Tests/Integration/Fixtures/Extensions/fake_extension3/Classes/", "TYPO3\\CMS\\Core\\Tests\\": ".Build/vendor/typo3/cms-core/Tests/" } },