diff --git a/Classes/Task/CleanupIndexTask.php b/Classes/Task/CleanupIndexTask.php new file mode 100644 index 0000000000..e9c9f0f4e5 --- /dev/null +++ b/Classes/Task/CleanupIndexTask.php @@ -0,0 +1,102 @@ + + */ +class CleanupIndexTask extends AbstractSolrTask +{ + protected ?int $deleteOlderThanDays = null; + + public function getDeleteOlderThanDays(): ?int + { + return $this->deleteOlderThanDays; + } + + public function setDeleteOlderThanDays(?int $deleteOlderThanDays): void + { + $this->deleteOlderThanDays = $deleteOlderThanDays; + } + + /** + * Deletes old documents from index + * + * @return bool Returns TRUE on success, FALSE on failure. + * + * @throws DBALConnectionException + * @throws DBALException + * + * @noinspection PhpMissingReturnTypeInspection See {@link \TYPO3\CMS\Scheduler\Task\AbstractTask::execute()} + */ + public function execute() + { + $cleanUpResult = true; + $solrConfiguration = $this->getSite()->getSolrConfiguration(); + $solrServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->getSite()); + $enableCommitsSetting = $solrConfiguration->getEnableCommits(); + + foreach ($solrServers as $solrServer) { + $deleteQuery = 'siteHash:' . $this->getSite()->getSiteHash() . sprintf(' AND indexed:[* TO NOW-%dDAYS]', $this->deleteOlderThanDays ?? 1); + $solrServer->getWriteService()->deleteByQuery($deleteQuery); + + if (!$enableCommitsSetting) { + // Do not commit + continue; + } + + $response = $solrServer->getWriteService()->commit(false, false); + if ($response->getHttpStatus() != 200) { + $cleanUpResult = false; + break; + } + } + + return $cleanUpResult; + } + + /** + * This method is designed to return some additional information about the task, + * that may help to set it apart from other tasks from the same class + * This additional information is used - for example - in the Scheduler's BE module + * This method should be implemented in most task classes + * + * @return string Information to display + * + * @throws DBALException + * + * @noinspection PhpMissingReturnTypeInspection See {@link \TYPO3\CMS\Scheduler\Task\AbstractTask::getAdditionalInformation()} + */ + public function getAdditionalInformation() + { + $site = $this->getSite(); + if (is_null($site)) { + return 'Invalid site configuration for scheduler please re-create the task!'; + } + + return 'Site: ' . $this->getSite()->getLabel(); + } +} diff --git a/Classes/Task/CleanupTaskAdditionalFieldProvider.php b/Classes/Task/CleanupTaskAdditionalFieldProvider.php new file mode 100644 index 0000000000..5b410ccca7 --- /dev/null +++ b/Classes/Task/CleanupTaskAdditionalFieldProvider.php @@ -0,0 +1,197 @@ + + */ +class CleanupTaskAdditionalFieldProvider extends AbstractAdditionalFieldProvider +{ + protected array $taskInformation = []; + + /** + * Scheduler task + */ + protected ?CleanupIndexTask $task; + + protected ?SchedulerModuleController $schedulerModule = null; + + /** + * Selected site + */ + protected ?Site $site = null; + + protected SiteRepository $siteRepository; + + protected ?PageRenderer $pageRenderer = null; + + public function __construct() + { + $this->siteRepository = GeneralUtility::makeInstance(SiteRepository::class); + } + + /** + * Initialize object + * + * @throws DBALException + */ + protected function initialize( + array $taskInfo, + ?CleanupIndexTask $task, + SchedulerModuleController $schedulerModule + ): void { + /* ReIndexTask @var $task */ + $this->taskInformation = $taskInfo; + $this->task = $task; + $this->schedulerModule = $schedulerModule; + + $currentAction = $schedulerModule->getCurrentAction(); + + if ($currentAction->equals(Action::EDIT)) { + $this->site = $this->siteRepository->getSiteByRootPageId((int)$task->getRootPageId()); + } + } + + /** + * Used to define fields to provide the Solr server address when adding + * or editing a task. + * + * @param array $taskInfo reference to the array containing the info used in the add/edit form + * @param CleanupIndexTask $task when editing, reference to the current task object. Null when adding. + * @param SchedulerModuleController $schedulerModule reference to the calling object (Scheduler's BE module) + * + * @return array Array containing all the information pertaining to the additional fields + * The array is multidimensional, keyed to the task class name and each field's id + * For each field it provides an associative sub-array with the following: + * + * @throws BackendFormException + * @throws UnexpectedTYPO3SiteInitializationException + * @throws DBALException + * + * @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection + * @noinspection PhpMissingReturnTypeInspection + */ + public function getAdditionalFields( + array &$taskInfo, + $task, + SchedulerModuleController $schedulerModule + ) { + $additionalFields = []; + + if (!$this->isTaskInstanceofCleanupIndexTask($task)) { + return $additionalFields; + } + + $this->initialize($taskInfo, $task, $schedulerModule); + $siteSelectorField = GeneralUtility::makeInstance(SiteSelectorField::class); + + $additionalFields['site'] = [ + 'code' => $siteSelectorField->getAvailableSitesSelector( + 'tx_scheduler[site]', + $this->site + ), + 'label' => 'LLL:EXT:solr/Resources/Private/Language/locallang.xlf:field_site', + ]; + + $additionalFields['deleteOlderThanDays'] = [ + 'code' => '', + 'label' => 'LLL:EXT:solr/Resources/Private/Language/locallang.xlf:task.cleanupIndex.deleteOlderThanDays', + ]; + + return $additionalFields; + } + + /** + * Checks any additional data that is relevant to this task. If the task + * class is not relevant, the method is expected to return TRUE + * + * @param array $submittedData reference to the array containing the data submitted by the user + * @param SchedulerModuleController $schedulerModule reference to the calling object (Scheduler's BE module) + * + * @return bool True if validation was ok (or selected class is not relevant), FALSE otherwise + * + * @throws UnexpectedTYPO3SiteInitializationException + * + * @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection + * @noinspection PhpMissingReturnTypeInspection + */ + public function validateAdditionalFields( + array &$submittedData, + SchedulerModuleController $schedulerModule + ): bool { + $result = false; + + // validate site + $sites = $this->siteRepository->getAvailableSites(); + if (array_key_exists($submittedData['site'], $sites)) { + $result = true; + } + + return $result; + } + + /** + * Saves any additional input into the current task object if the task + * class matches. + * + * @param array $submittedData array containing the data submitted by the user + * @param CleanupIndexTask $task reference to the current task object + */ + public function saveAdditionalFields( + array $submittedData, + CleanupIndexTask|AbstractTask $task + ): void { + if (!$this->isTaskInstanceofCleanupIndexTask($task)) { + return; + } + + $task->setRootPageId((int)$submittedData['site']); + $task->setDeleteOlderThanDays($submittedData['deleteOlderThanDays'] ? (int)$submittedData['deleteOlderThanDays'] : null); + } + + /** + * Check that a task is an instance of ReIndexTask + */ + protected function isTaskInstanceofCleanupIndexTask(?AbstractTask $task): bool + { + if ((!is_null($task)) && (!($task instanceof CleanupIndexTask))) { + throw new LogicException( + '$task must be an instance of ReIndexTask, ' + . 'other instances are not supported.', + 1487500366 + ); + } + return true; + } +} diff --git a/Classes/Task/ReIndexTask.php b/Classes/Task/ReIndexTask.php index 745a545b77..4aa6c55042 100644 --- a/Classes/Task/ReIndexTask.php +++ b/Classes/Task/ReIndexTask.php @@ -17,7 +17,6 @@ namespace ApacheSolrForTypo3\Solr\Task; -use ApacheSolrForTypo3\Solr\ConnectionManager; use ApacheSolrForTypo3\Solr\Domain\Index\Queue\QueueInitializationService; use Doctrine\DBAL\ConnectionException as DBALConnectionException; use Doctrine\DBAL\Exception as DBALException; @@ -37,7 +36,7 @@ class ReIndexTask extends AbstractSolrTask protected array $indexingConfigurationsToReIndex = []; /** - * Purges/commits all Solr indexes, initializes the Index Queue + * Initializes the Index Queue * and returns TRUE if the execution was successful * * @return bool Returns TRUE on success, FALSE on failure. @@ -49,55 +48,13 @@ class ReIndexTask extends AbstractSolrTask */ public function execute() { - // clean up - $cleanUpResult = $this->cleanUpIndex(); - // initialize for re-indexing /** @var QueueInitializationService $indexQueueInitializationService */ $indexQueueInitializationService = GeneralUtility::makeInstance(QueueInitializationService::class); $indexQueueInitializationResults = $indexQueueInitializationService ->initializeBySiteAndIndexConfigurations($this->getSite(), $this->indexingConfigurationsToReIndex); - return $cleanUpResult && !in_array(false, $indexQueueInitializationResults); - } - - /** - * Removes documents of the selected types from the index. - * - * @return bool TRUE if clean up was successful, FALSE on error - * - * @throws DBALException - */ - protected function cleanUpIndex(): bool - { - $cleanUpResult = true; - $solrConfiguration = $this->getSite()->getSolrConfiguration(); - $solrServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->getSite()); - $typesToCleanUp = []; - $enableCommitsSetting = $solrConfiguration->getEnableCommits(); - - foreach ($this->indexingConfigurationsToReIndex as $indexingConfigurationName) { - $type = $solrConfiguration->getIndexQueueTypeOrFallbackToConfigurationName($indexingConfigurationName); - $typesToCleanUp[] = $type; - } - - foreach ($solrServers as $solrServer) { - $deleteQuery = 'type:(' . implode(' OR ', $typesToCleanUp) . ')' . ' AND siteHash:' . $this->getSite()->getSiteHash(); - $solrServer->getWriteService()->deleteByQuery($deleteQuery); - - if (!$enableCommitsSetting) { - // Do not commit - continue; - } - - $response = $solrServer->getWriteService()->commit(false, false); - if ($response->getHttpStatus() != 200) { - $cleanUpResult = false; - break; - } - } - - return $cleanUpResult; + return !in_array(false, $indexQueueInitializationResults); } /** diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf index bb61629b0e..a22e0cb6e9 100644 --- a/Resources/Private/Language/de.locallang.xlf +++ b/Resources/Private/Language/de.locallang.xlf @@ -36,6 +36,18 @@ Forced webroot (only needed when webroot is not PATH_site) Forced webroot (Nur notwendig wenn webroot von PATH_site abweicht) + + Index Cleanup + Index-Bereinigung + + + Delete old documents from index + Löscht alte Dokumente aus dem Index + + + Delete documents older than # days + Lösche alle Dokumente älter als # Tage + Solr Host Solr host diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index a668a93742..00a2c81ed7 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -110,6 +110,15 @@ Forced webroot (only needed when webroot is not PATH_site) + + Index Cleanup + + + Delete old documents from index + + + Delete documents older than # days + Solr Host diff --git a/ext_localconf.php b/ext_localconf.php index 242b911648..278890582c 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -15,6 +15,8 @@ use ApacheSolrForTypo3\Solr\IndexQueue\RecordMonitor; use ApacheSolrForTypo3\Solr\Routing\Enhancer\SolrFacetMaskAndCombineEnhancer; use ApacheSolrForTypo3\Solr\System\Configuration\ExtensionConfiguration; +use ApacheSolrForTypo3\Solr\Task\CleanupIndexTask; +use ApacheSolrForTypo3\Solr\Task\CleanupTaskAdditionalFieldProvider; use ApacheSolrForTypo3\Solr\Task\EventQueueWorkerTask; use ApacheSolrForTypo3\Solr\Task\EventQueueWorkerTaskAdditionalFieldProvider; use ApacheSolrForTypo3\Solr\Task\IndexQueueWorkerTask; @@ -87,6 +89,13 @@ 'additionalFields' => EventQueueWorkerTaskAdditionalFieldProvider::class, ]; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][CleanupIndexTask::class] = [ + 'extension' => 'solr', + 'title' => 'LLL:EXT:solr/Resources/Private/Language/locallang_.xlf:task.cleanupIndex.title', + 'description' => 'LLL:EXT:solr/Resources/Private/Language/locallang.xlf:task.cleanupIndex.description', + 'additionalFields' => CleanupTaskAdditionalFieldProvider::class, + ]; + if (!isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][TableGarbageCollectionTask::class]['options']['tables']['tx_solr_statistics'])) { $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][TableGarbageCollectionTask::class]['options']['tables']['tx_solr_statistics'] = [ 'dateField' => 'tstamp',