search_api-8.x-1.15/src/Entity/Server.php
src/Entity/Server.php
<?php namespace Drupal\search_api\Entity; use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\search_api\Event\DeterminingServerFeaturesEvent; use Drupal\search_api\Event\SearchApiEvents; use Drupal\search_api\IndexInterface; use Drupal\search_api\LoggerTrait; use Drupal\search_api\Query\QueryInterface; use Drupal\search_api\SearchApiException; use Drupal\search_api\ServerInterface; use Drupal\search_api\Utility\Utility; /** * Defines the search server configuration entity. * * @ConfigEntityType( * id = "search_api_server", * label = @Translation("Search server"), * label_collection = @Translation("Search servers"), * label_singular = @Translation("search server"), * label_plural = @Translation("search servers"), * label_count = @PluralTranslation( * singular = "@count search server", * plural = "@count search servers", * ), * handlers = { * "storage" = "Drupal\search_api\Entity\SearchApiConfigEntityStorage", * "form" = { * "default" = "Drupal\search_api\Form\ServerForm", * "edit" = "Drupal\search_api\Form\ServerForm", * "delete" = "Drupal\search_api\Form\ServerDeleteConfirmForm", * "disable" = "Drupal\search_api\Form\ServerDisableConfirmForm", * "clear" = "Drupal\search_api\Form\ServerClearConfirmForm", * }, * }, * admin_permission = "administer search_api", * config_prefix = "server", * entity_keys = { * "id" = "id", * "label" = "name", * "uuid" = "uuid", * "status" = "status", * }, * config_export = { * "id", * "name", * "description", * "backend", * "backend_config", * }, * links = { * "canonical" = "/admin/config/search/search-api/server/{search_api_server}", * "add-form" = "/admin/config/search/search-api/add-server", * "edit-form" = "/admin/config/search/search-api/server/{search_api_server}/edit", * "delete-form" = "/admin/config/search/search-api/server/{search_api_server}/delete", * "disable" = "/admin/config/search/search-api/server/{search_api_server}/disable", * "enable" = "/admin/config/search/search-api/server/{search_api_server}/enable", * } * ) */ class Server extends ConfigEntityBase implements ServerInterface { use InstallingTrait; use LoggerTrait; /** * The ID of the server. * * @var string */ protected $id; /** * The displayed name of the server. * * @var string */ protected $name; /** * The displayed description of the server. * * @var string */ protected $description = ''; /** * The ID of the backend plugin. * * @var string */ protected $backend; /** * The backend plugin configuration. * * @var array */ protected $backend_config = []; /** * The backend plugin instance. * * @var \Drupal\search_api\Backend\BackendInterface */ protected $backendPlugin; /** * The features this server supports. * * @var string[]|null */ protected $features; /** * {@inheritdoc} */ public function getDescription() { return $this->description; } /** * {@inheritdoc} */ public function hasValidBackend() { $backend_plugin_definition = \Drupal::service('plugin.manager.search_api.backend')->getDefinition($this->getBackendId(), FALSE); return !empty($backend_plugin_definition); } /** * {@inheritdoc} */ public function getBackendId() { return $this->backend; } /** * {@inheritdoc} */ public function getBackend() { if (!$this->backendPlugin) { $backend_plugin_manager = \Drupal::service('plugin.manager.search_api.backend'); $config = $this->backend_config; $config['#server'] = $this; if (!($this->backendPlugin = $backend_plugin_manager->createInstance($this->getBackendId(), $config))) { $backend_id = $this->getBackendId(); $label = $this->label(); throw new SearchApiException("The backend with ID '$backend_id' could not be retrieved for server '$label'."); } } return $this->backendPlugin; } /** * {@inheritdoc} */ public function getBackendConfig() { return $this->backend_config; } /** * {@inheritdoc} */ public function setBackendConfig(array $backend_config) { $this->backend_config = $backend_config; $this->getBackend()->setConfiguration($backend_config); return $this; } /** * {@inheritdoc} */ public function getIndexes(array $properties = []) { $storage = \Drupal::entityTypeManager()->getStorage('search_api_index'); return $storage->loadByProperties(['server' => $this->id()] + $properties); } /** * {@inheritdoc} */ public function viewSettings() { return $this->hasValidBackend() ? $this->getBackend()->viewSettings() : []; } /** * {@inheritdoc} */ public function isAvailable() { return $this->hasValidBackend() ? $this->getBackend()->isAvailable() : FALSE; } /** * {@inheritdoc} */ public function supportsFeature($feature) { return in_array($feature, $this->getSupportedFeatures()); } /** * {@inheritdoc} */ public function getSupportedFeatures() { if (!isset($this->features)) { $this->features = []; if ($this->hasValidBackend()) { $this->features = $this->getBackend()->getSupportedFeatures(); } $description = 'This hook is deprecated in search_api 8.x-1.14 and will be removed in 9.x-1.0. Please use the "search_api.determining_server_features" event instead. See https://www.drupal.org/node/3059866'; \Drupal::moduleHandler() ->alterDeprecated($description, 'search_api_server_features', $this->features, $this); /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher */ $eventDispatcher = \Drupal::getContainer()->get('event_dispatcher'); $eventDispatcher->dispatch(SearchApiEvents::DETERMINING_SERVER_FEATURES, new DeterminingServerFeaturesEvent($this->features, $this)); } return $this->features; } /** * {@inheritdoc} */ public function supportsDataType($type) { if ($this->hasValidBackend()) { return $this->getBackend()->supportsDataType($type); } return FALSE; } /** * {@inheritdoc} */ public function getDiscouragedProcessors() { if ($this->hasValidBackend()) { return $this->getBackend()->getDiscouragedProcessors(); } return []; } /** * {@inheritdoc} */ public function getBackendDefinedFields(IndexInterface $index) { if ($this->hasValidBackend()) { return $this->getBackend()->getBackendDefinedFields($index); } return []; } /** * {@inheritdoc} */ public function addIndex(IndexInterface $index) { $server_task_manager = \Drupal::getContainer()->get('search_api.server_task_manager'); // When freshly adding an index to a server, it doesn't make any sense to // execute possible other tasks for that server/index combination. // (removeIndex() is implicit when adding an index which was already added.) $server_task_manager->delete($this, $index); try { if ($server_task_manager->execute($this)) { $this->getBackend()->addIndex($index); return; } } catch (SearchApiException $e) { $vars = [ '%server' => $this->label(), '%index' => $index->label(), ]; $this->logException($e, '%type while adding index %index to server %server: @message in %function (line %line of %file).', $vars); } $task_manager = \Drupal::getContainer() ->get('search_api.task_manager'); $task_manager->addTask(__FUNCTION__, $this, $index); } /** * {@inheritdoc} */ public function updateIndex(IndexInterface $index) { $server_task_manager = \Drupal::getContainer()->get('search_api.server_task_manager'); try { if ($server_task_manager->execute($this)) { $this->getBackend()->updateIndex($index); return; } } catch (SearchApiException $e) { $vars = [ '%server' => $this->label(), '%index' => $index->label(), ]; $this->logException($e, '%type while updating the fields of index %index on server %server: @message in %function (line %line of %file).', $vars); } $task_manager = \Drupal::getContainer() ->get('search_api.task_manager'); $task_manager->addTask(__FUNCTION__, $this, $index, isset($index->original) ? $index->original : NULL); } /** * {@inheritdoc} */ public function removeIndex($index) { $server_task_manager = \Drupal::getContainer()->get('search_api.server_task_manager'); // When removing an index from a server, it doesn't make any sense anymore // to delete items from it, or react to other changes. $server_task_manager->delete($this, $index); try { if ($server_task_manager->execute($this)) { $this->getBackend()->removeIndex($index); return; } } catch (SearchApiException $e) { $vars = [ '%server' => $this->label(), '%index' => is_object($index) ? $index->label() : $index, ]; $this->logException($e, '%type while removing index %index from server %server: @message in %function (line %line of %file).', $vars); } $task_manager = \Drupal::getContainer() ->get('search_api.task_manager'); $data = NULL; if (!is_object($index)) { $data = $index; $index = NULL; } $task_manager->addTask(__FUNCTION__, $this, $index, $data); } /** * {@inheritdoc} */ public function indexItems(IndexInterface $index, array $items) { $server_task_manager = \Drupal::getContainer()->get('search_api.server_task_manager'); if ($server_task_manager->execute($this)) { return $this->getBackend()->indexItems($index, $items); } $index_label = $index->label(); throw new SearchApiException("Could not index items on index '$index_label' because pending server tasks could not be executed."); } /** * {@inheritdoc} */ public function deleteItems(IndexInterface $index, array $item_ids) { if ($index->isReadOnly()) { $vars = [ '%index' => $index->label(), ]; $this->getLogger()->warning('Trying to delete items from index %index which is marked as read-only.', $vars); return; } $server_task_manager = \Drupal::getContainer()->get('search_api.server_task_manager'); try { if ($server_task_manager->execute($this)) { $this->getBackend()->deleteItems($index, $item_ids); // Clear search api list caches. Cache::invalidateTags(['search_api_list:' . $index->id()]); return; } } catch (SearchApiException $e) { $vars = [ '%server' => $this->label(), ]; $this->logException($e, '%type while deleting items from server %server: @message in %function (line %line of %file).', $vars); } $task_manager = \Drupal::getContainer() ->get('search_api.task_manager'); $task_manager->addTask(__FUNCTION__, $this, $index, $item_ids); } /** * {@inheritdoc} */ public function deleteAllIndexItems(IndexInterface $index, $datasource_id = NULL) { if ($index->isReadOnly()) { $vars = [ '%index' => $index->label(), ]; $this->getLogger()->warning('Trying to delete items from index %index which is marked as read-only.', $vars); return; } $server_task_manager = \Drupal::getContainer()->get('search_api.server_task_manager'); if (!$datasource_id) { // If we're deleting all items of the index, there's no point in keeping // any other "delete items" tasks. $types = [ 'deleteItems', 'deleteAllIndexItems', ]; $server_task_manager->delete($this, $index, $types); } try { if ($server_task_manager->execute($this)) { $this->getBackend()->deleteAllIndexItems($index, $datasource_id); // Clear search api list caches. Cache::invalidateTags(['search_api_list:' . $index->id()]); return; } } catch (SearchApiException $e) { $vars = [ '%server' => $this->label(), '%index' => $index->label(), ]; $this->logException($e, '%type while deleting items of index %index from server %server: @message in %function (line %line of %file).', $vars); } $task_manager = \Drupal::getContainer() ->get('search_api.task_manager'); $task_manager->addTask(__FUNCTION__, $this, $index, $datasource_id); } /** * {@inheritdoc} */ public function deleteAllItems() { $failed = []; $properties['status'] = TRUE; $properties['read_only'] = FALSE; foreach ($this->getIndexes($properties) as $index) { try { $this->getBackend()->deleteAllIndexItems($index); Cache::invalidateTags(['search_api_list:' . $index->id()]); } catch (SearchApiException $e) { $args = [ '%index' => $index->label(), ]; $this->logException($e, '%type while deleting all items from index %index: @message in %function (line %line of %file).', $args); $failed[] = $index->label(); } } if (!empty($e)) { $server_name = $this->label(); $failed = implode(', ', $failed); throw new SearchApiException("Deleting all items from server '$server_name' failed for the following (write-enabled) indexes: $failed.", 0, $e); } $types = [ 'deleteItems', 'deleteAllIndexItems', ]; \Drupal::getContainer() ->get('search_api.server_task_manager') ->delete($this, NULL, $types); } /** * {@inheritdoc} */ public function search(QueryInterface $query) { $this->getBackend()->search($query); } /** * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage) { parent::preSave($storage); // The rest of the code only applies to updates. if (!isset($this->original)) { return; } // Retrieve active config overrides for this server. $overrides = Utility::getConfigOverrides($this); // If there are overrides for the backend or its configuration, attempt to // apply them for the preUpdate() call. if (isset($overrides['backend']) || isset($overrides['backend_config'])) { $backend_config = $this->getBackendConfig(); if (isset($overrides['backend_config'])) { $backend_config = $overrides['backend_config']; } $backend_id = $this->getBackendId(); if (isset($overrides['backend'])) { $backend_id = $overrides['backend']; } $backend_plugin_manager = \Drupal::service('plugin.manager.search_api.backend'); $backend_config['#server'] = $this; if (!($backend = $backend_plugin_manager->createInstance($backend_id, $backend_config))) { $label = $this->label(); throw new SearchApiException("The backend with ID '$backend_id' could not be retrieved for server '$label'."); } } else { $backend = $this->getBackend(); } $backend->preUpdate(); // If the server is being disabled, also disable all its indexes. if (!$this->isSyncing() && !$this->isInstallingFromExtension() && !isset($overrides['status']) && !$this->status() && $this->original->status()) { foreach ($this->getIndexes(['status' => TRUE]) as $index) { /** @var \Drupal\search_api\IndexInterface $index */ $index->setStatus(FALSE)->save(); } } } /** * {@inheritdoc} */ public function postSave(EntityStorageInterface $storage, $update = TRUE) { if ($this->hasValidBackend()) { if ($update) { $reindexing_necessary = $this->getBackend()->postUpdate(); if ($reindexing_necessary) { foreach ($this->getIndexes() as $index) { $index->reindex(); } } } else { $this->getBackend()->postInsert(); } } } /** * {@inheritdoc} */ public static function preDelete(EntityStorageInterface $storage, array $entities) { // @todo This will, via Index::onDependencyRemoval(), remove all indexes // from this server, triggering the server's removeIndex() method. This // is, at best, wasted performance and could at worst lead to a bug if // removeIndex() saves the server. We should try what happens when this is // the case, whether there really is a bug, and try to fix it somehow – // maybe clever detection of this case in removeIndex() or // Index::postSave(). $server->isUninstalling() might help? parent::preDelete($storage, $entities); // Iterate through the servers, executing the backends' preDelete() methods // and removing all their pending server tasks. foreach ($entities as $server) { /** @var \Drupal\search_api\ServerInterface $server */ if ($server->hasValidBackend()) { $server->getBackend()->preDelete(); } \Drupal::getContainer()->get('search_api.server_task_manager')->delete($server); } } /** * {@inheritdoc} */ public function toArray() { // @todo It's a bug that we have to do this. Backend configuration should // always be set via the server's setBackendConfiguration() method, // otherwise the two can diverge causing this and other problems. The // alternative would be to call $server->setBackendConfiguration() in the // backend's setConfiguration() method and use a second $propagate // parameter to avoid an infinite loop. Similar things go for the index's // various plugins. Maybe using plugin bags is the solution here? $properties = parent::toArray(); if ($this->hasValidBackend()) { $properties['backend_config'] = $this->getBackend()->getConfiguration(); } return $properties; } /** * {@inheritdoc} */ public function calculateDependencies() { parent::calculateDependencies(); // Add the backend's dependencies. if ($this->hasValidBackend()) { $this->calculatePluginDependencies($this->getBackend()); } return $this; } /** * {@inheritdoc} */ public function onDependencyRemoval(array $dependencies) { $changed = parent::onDependencyRemoval($dependencies); if ($this->hasValidBackend()) { $removed_backend_dependencies = []; $backend = $this->getBackend(); foreach ($backend->calculateDependencies() as $dependency_type => $list) { if (isset($dependencies[$dependency_type])) { $removed_backend_dependencies[$dependency_type] = array_intersect_key($dependencies[$dependency_type], array_flip($list)); } } $removed_backend_dependencies = array_filter($removed_backend_dependencies); if ($removed_backend_dependencies) { if ($backend->onDependencyRemoval($removed_backend_dependencies)) { $this->backend_config = $backend->getConfiguration(); $changed = TRUE; } } } return $changed; } /** * Implements the magic __clone() method. * * Prevents the backend plugin instance from being cloned. */ public function __clone() { $this->backendPlugin = NULL; } }