db = $database; $this->provider = $provider; } /** * Gibt Meta-Information zu den Providern zurück; nur von aktiven Providern * * @return array */ public function getProviderMetaData() { $meta = []; foreach ($this->provider as $provider) { $meta[] = [ 'name' => $provider->getIndexName(), 'title' => $provider->getIndexTitle(), 'module' => $provider->getModuleName(), ]; } return $meta; } /** * Tatsächliche Index-Größe ermitteln (gruppiert nach Index) (nur von aktiven Providern) * * @return array */ public function getProviderIndexSizesCurrent() { $sql = 'SELECT sig.name, COUNT(sii.id) AS `index_size` FROM `supersearch_index_group` AS `sig` LEFT JOIN `supersearch_index_item` AS `sii` ON sig.name = sii.index_name AND sii.outdated = 0 WHERE sig.active = 1 GROUP BY sig.name'; $indexSizes = $this->db->fetchPairs($sql); // Fehlende Indexe ergänzen, falls Provider registriert ist aber noch nie gelaufen ist foreach ($this->provider as $provider) { $indexName = $provider->getIndexName(); if (!isset($indexSizes[$indexName])) { $indexSizes[$indexName] = null; } } ksort($indexSizes); return $indexSizes; } /** * Potentielle Index-Größe ermitteln (gruppiert nach Index) (nur von aktiven Providern) * * @return array */ public function getProviderIndexSizesPotential() { $indexSizes = []; foreach ($this->provider as $provider) { // Möglich Index-Größe beim Provider erfragen $indexSizePotential = null; if ($provider instanceof BulkIndexProviderInterface) { $indexSizePotential = $provider->getTotalCount(); } $indexSizes[$provider->getIndexName()] = $indexSizePotential; } ksort($indexSizes); return $indexSizes; } /** * @param string $name Index-Name * * @throws ProviderMissingException * @throws InvalidReturnValueException * * @return void */ public function updateIndexFull($name) { /** @var FullIndexProviderInterface $provider */ $provider = $this->tryGetProviderByIndexName($name); if ($provider === null) { throw new ProviderMissingException(sprintf('Provider for index "%s" is missing', $name)); } if ($provider instanceof BulkIndexProviderInterface) { /** @var BulkIndexProviderInterface $provider */ $totalCount = $provider->getTotalCount(); if ($totalCount < 0) { throw new InvalidReturnValueException(sprintf( 'Method %s::getTotalCount() returned an invalid value "%s". Total count must be a positive number.', get_class($provider), $totalCount )); } $itemsPerStep = $provider->getBulkSize(); $currentOffset = 0; // Alle Index-Einträge als "veraltet" markieren $this->markIndexAsOutdated($name); do { $items = $provider->getBulkItems($currentOffset, $itemsPerStep); $this->db->beginTransaction(); foreach ($items as $item) { $this->saveItem($item); } unset($items); $this->db->commit(); $currentOffset += $itemsPerStep; } while ($currentOffset < $totalCount); // Full-Update-Zeitpunkt aktualisieren $this->updateLastFullUpdateTime($name); // Diff-Update-Zeitpunkt ebenfalls aktualisieren > Nächstes Diff-Update dann ab diesem Zeitpunkt $this->updateLastDiffUpdateTime($name); // Als "veraltet" markierte Index-Einträge löschen $this->deleteOutdatedIndexItems($name); } if ( $provider instanceof FullIndexProviderInterface && !$provider instanceof BulkIndexProviderInterface ) { $this->markIndexAsOutdated($name); $items = $provider->getAllItems(); $this->db->beginTransaction(); foreach ($items as $item) { $this->saveItem($item); } $this->db->commit(); $this->updateLastFullUpdateTime($name); $this->deleteOutdatedIndexItems($name); } } /** * @param string $name Index-Name * @param DateTimeInterface $since * * @throws ProviderMissingException * * @return void */ public function updateIndexSince($name, DateTimeInterface $since) { /** @var DiffIndexProviderInterface $provider */ $provider = $this->tryGetProviderByIndexName($name); if ($provider === null) { throw new ProviderMissingException(sprintf('Provider for index "%s" is missing', $name)); } if (!$provider instanceof DiffIndexProviderInterface) { return; } $items = $provider->getItemsSince($since); $this->db->beginTransaction(); foreach ($items as $item) { $this->saveItem($item); } $this->db->commit(); $this->updateLastDiffUpdateTime($name); } /** * @param string $name * * @return DateTimeInterface|null */ public function getLastFullIndexTime($name) { $dateString = $this->db->fetchValue( 'SELECT sig.last_full_update FROM `supersearch_index_group` AS `sig` WHERE sig.name = :index_name', ['index_name' => (string)$name] ); try { if (!empty($dateString)) { return new DateTimeImmutable($dateString); } } catch (Exception $exception) { // nope - return null } return null; } /** * @param string $name * * @return DateTimeInterface|null */ public function getLastDiffIndexTime($name) { $dateString = $this->db->fetchValue( 'SELECT sig.last_diff_update FROM `supersearch_index_group` AS `sig` WHERE sig.name = :index_name', ['index_name' => (string)$name] ); try { if (!empty($dateString)) { return new DateTimeImmutable($dateString); } } catch (Exception $exception) { // nope - return null } return null; } /** * @param IndexIdentifier $identifier * * @return void */ public function deleteIndexItem(IndexIdentifier $identifier) { $sql = 'DELETE FROM `supersearch_index_item` WHERE `index_name` = :index_name AND `index_id` = :index_id LIMIT 1'; $bindValues = [ 'index_name' => $identifier->getName(), 'index_id' => $identifier->getId(), ]; $this->db->perform($sql, $bindValues); } /** * @param IndexIdentifier $identifier * * @throws ProviderMissingException * @throws ProviderIncompatibleException * * @return void */ public function updateIndexItem(IndexIdentifier $identifier) { /** @var ItemIndexProviderInterface $provider */ $provider = $this->tryGetProviderByIndexName($identifier->getName()); if ($provider === null) { throw new ProviderMissingException(sprintf( 'Provider for index "%s" is missing.', $identifier->getName() )); } if (!$provider instanceof ItemIndexProviderInterface) { throw new ProviderIncompatibleException(sprintf( 'Provider for index "%s" is incompatible. Provider %s does not implement %s.', $identifier->getName(), get_class($provider), ItemIndexProviderInterface::class )); } $item = $provider->getItem($identifier); if ($item === null) { return; } $this->saveItem($item); } /** * Updates or creates an index item * * @param IndexItem $item * * @throws ProviderMissingException * * @return void */ private function saveItem(IndexItem $item) { $existingItemId = (int)$this->db->fetchValue( 'SELECT sii.id FROM `supersearch_index_item` AS `sii` WHERE sii.index_name = :index_name AND sii.index_id = :index_id', [ 'index_name' => $item->identifier->getName(), 'index_id' => $item->identifier->getId(), ] ); if ($existingItemId > 0) { $this->updateItem($item); } else { $this->createItem($item); } } /** * @param IndexItem $item * * @throws DatabaseExceptionInterface * * @return void */ private function updateItem(IndexItem $item) { $searchWords = ''; if (!empty($item->data->getWords())) { $searchWords = implode(' | ', $item->data->getWords()); } $additionalInfos = null; if (!empty($item->data->getAdditionalInfos())) { $additionalInfos = implode(' ## ', $item->data->getAdditionalInfos()); } $sql = 'UPDATE `supersearch_index_item` SET `project_id` = :project_id, `title` = :title, `subtitle` = :subtitle, `additional_infos` = :additional_infos, `link` = :link, `search_words` = :search_words, `created_at` = `created_at`, `updated_at` = NOW(), `outdated` = 0 WHERE `index_name` = :index_name AND `index_id` = :index_id LIMIT 1'; $bindValues = [ 'index_name' => $item->identifier->getName(), 'index_id' => $item->identifier->getId(), 'project_id' => $item->data->getProjectId(), 'title' => $item->data->getTitle(), 'link' => $item->data->getLink(), 'subtitle' => $item->data->getSubTitle(), 'search_words' => $searchWords, 'additional_infos' => $additionalInfos, ]; $this->db->perform($sql, $bindValues); } /** * @param IndexItem $item * * @return void */ private function createItem(IndexItem $item) { $searchWords = ''; if (!empty($item->data->getWords())) { $searchWords = implode(' | ', $item->data->getWords()); } $additionalInfos = null; if (!empty($item->data->getAdditionalInfos())) { $additionalInfos = implode(' ## ', $item->data->getAdditionalInfos()); } $sql = 'INSERT INTO `supersearch_index_item` (`index_name`, `index_id`, `project_id`, `title`, `subtitle`, `additional_infos`, `link`, `search_words`, `outdated`, `created_at`, `updated_at`) VALUES (:index_name, :index_id, :project_id, :title, :subtitle, :additional_infos, :link, :search_words, 0, NOW(), NULL)'; $bindValues = [ 'index_name' => $item->identifier->getName(), 'index_id' => $item->identifier->getId(), 'project_id' => $item->data->getProjectId(), 'title' => $item->data->getTitle(), 'link' => $item->data->getLink(), 'subtitle' => $item->data->getSubTitle(), 'search_words' => $searchWords, 'additional_infos' => $additionalInfos, ]; $this->db->perform($sql, $bindValues); } /** * Alle Einträge als veraltet markieren * * Beim Update/Insert eines Eintrags wird dieser wieder auf ungelöscht gestellt bevor er wirklich gelöscht wird. * * @param string $indexName * * @return void */ private function markIndexAsOutdated($indexName) { $sql = 'UPDATE `supersearch_index_item` SET `outdated` = 1 WHERE `index_name` = :index_name'; $this->db->perform($sql, ['index_name' => (string)$indexName]); } /** * @param string $indexName * * @return void */ private function deleteOutdatedIndexItems($indexName) { $sql = 'DELETE FROM `supersearch_index_item` WHERE `index_name` = :index_name AND `outdated` = 1'; $this->db->perform($sql, ['index_name' => (string)$indexName]); } /** * @param string $name * * @return SearchIndexProviderInterface|null */ private function tryGetProviderByIndexName($name) { foreach ($this->provider as $provider) { if ($name === $provider->getIndexName()) { return $provider; } } return null; } /** * @param string $name * * @return void */ private function updateLastFullUpdateTime($name) { $sql = 'UPDATE `supersearch_index_group` SET `last_full_update` = NOW() WHERE `name` = :index_name LIMIT 1'; $this->db->perform($sql, ['index_name' => (string)$name]); } /** * @param string $name * * @return void */ private function updateLastDiffUpdateTime($name) { $sql = 'UPDATE `supersearch_index_group` SET `last_diff_update` = NOW() WHERE `name` = :index_name LIMIT 1'; $this->db->perform($sql, ['index_name' => (string)$name]); } }