View source
<?php
namespace Drupal\search_api_autocomplete\Plugin\search_api_autocomplete\suggester;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\search_api\LoggerTrait;
use Drupal\search_api\Plugin\PluginFormTrait;
use Drupal\search_api\Processor\ProcessorPluginManager;
use Drupal\search_api\Query\QueryInterface;
use Drupal\search_api\SearchApiException;
use Drupal\search_api_autocomplete\SearchApiAutocompleteException;
use Drupal\search_api_autocomplete\Suggester\SuggesterPluginBase;
use Drupal\search_api_autocomplete\Suggestion\SuggestionFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
class LiveResults extends SuggesterPluginBase implements PluginFormInterface {
use LoggerTrait;
use PluginFormTrait;
protected $entityTypeManager;
protected $processorPluginManager;
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$plugin
->setEntityTypeManager($container
->get('entity_type.manager'));
$plugin
->setLogger($container
->get('logger.channel.search_api_autocomplete'));
$plugin
->setProcessorPluginManager($container
->get('plugin.manager.search_api.processor'));
return $plugin;
}
public function getEntityTypeManager() {
return $this->entityTypeManager ?: \Drupal::service('entity_type.manager');
}
public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
return $this;
}
public function getProcessorPluginManager() : ProcessorPluginManager {
return $this->processorPluginManager ?: \Drupal::service('plugin.manager.search_api.processor');
}
public function setProcessorPluginManager(ProcessorPluginManager $processor_plugin_manager) : self {
$this->processorPluginManager = $processor_plugin_manager;
return $this;
}
public function getLogger() {
return $this->logger ?: \Drupal::service('logger.channel.search_api_autocomplete');
}
public function defaultConfiguration() {
return [
'fields' => [],
'view_modes' => [],
'highlight' => [
'enabled' => FALSE,
'field' => '',
],
];
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css';
$search = $this
->getSearch();
$fields = $search
->getIndex()
->getFields();
$fulltext_fields = $search
->getIndex()
->getFulltextFields();
$options = [];
foreach ($fulltext_fields as $field) {
$options[$field] = $fields[$field]
->getFieldIdentifier();
}
$form['fields'] = [
'#type' => 'checkboxes',
'#title' => $this
->t('Override used fields'),
'#description' => $this
->t('Select the fields which should be searched for matches when looking for autocompletion suggestions. Leave blank to use the same fields as the underlying search.'),
'#options' => $options,
'#default_value' => array_combine($this->configuration['fields'], $this->configuration['fields']),
'#attributes' => [
'class' => [
'search-api-checkboxes-list',
],
],
];
$form['view_modes'] = [
'#type' => 'fieldset',
'#title' => $this
->t('View modes'),
'#description' => $this
->t('Please select the view modes to use for live results.'),
];
$selected_view_modes = $this->configuration['view_modes'];
foreach ($search
->getIndex()
->getDatasources() as $datasource_id => $datasource) {
foreach ($datasource
->getBundles() as $bundle => $name) {
$view_modes = $datasource
->getViewModes($bundle);
if (!$view_modes) {
$form['view_modes'][$datasource_id][$bundle] = [
'#type' => 'value',
'#value' => '',
];
continue;
}
$default_value = '';
if (isset($selected_view_modes[$datasource_id][$bundle])) {
$default_value = $selected_view_modes[$datasource_id][$bundle];
}
$form['view_modes'][$datasource_id][$bundle] = [
'#type' => 'select',
'#title' => $name,
'#options' => [
'' => $this
->t('Use only label'),
] + $view_modes,
'#default_value' => $default_value,
];
}
}
$args = [
'%highlight' => $this
->t('Highlight'),
];
try {
$args['%highlight'] = $this
->getProcessorPluginManager()
->createInstance('highlight')
->label();
} catch (PluginException $e) {
}
$form['highlight'] = [
'#type' => 'fieldset',
'#title' => $this
->t('Highlight live results'),
'#description' => $this
->t('Replace the live result with the output of a highlighted field, if available. Requires the %highlight processor to be enabled.', $args),
'#description_display' => 'before',
'#disabled' => !$this
->isHighlightingAvailable(),
'#tree' => TRUE,
];
$form['highlight']['enabled'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Enable highlighting of live results'),
'#default_value' => $this->configuration['highlight']['enabled'],
];
$form['highlight']['field'] = [
'#type' => 'select',
'#empty_value' => '',
'#title' => $this
->t('Select the field to replace live result'),
'#options' => $options,
'#default_value' => $this->configuration['highlight']['field'],
'#states' => [
'visible' => [
':input[name="suggesters[settings][live_results][highlight][enabled]"]' => [
'checked' => TRUE,
],
],
'required' => [
':input[name="suggesters[settings][live_results][highlight][enabled]"]' => [
'checked' => TRUE,
],
],
],
];
return $form;
}
protected function isHighlightingAvailable() : bool {
try {
return $this
->getSearch()
->getIndex()
->isValidProcessor('highlight');
} catch (SearchApiAutocompleteException $e) {
return FALSE;
}
}
protected function isHighlightingEnabled() : bool {
return $this->configuration['highlight']['enabled'] && $this->configuration['highlight']['field'] && $this
->isHighlightingAvailable();
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$values = $form_state
->getValues();
$values['fields'] = array_keys(array_filter($values['fields']));
$values['highlight']['enabled'] = (bool) $values['highlight']['enabled'];
$this
->setConfiguration($values);
}
public function getAutocompleteSuggestions(QueryInterface $query, $incomplete_key, $user_input) {
$fulltext_fields = $this->configuration['fields'];
$index = $query
->getIndex();
if ($fulltext_fields) {
$index_fields = $index
->getFulltextFields();
$fulltext_fields = array_intersect($fulltext_fields, $index_fields);
if ($fulltext_fields) {
$query
->setFulltextFields($fulltext_fields);
}
else {
$args = [
'@suggester' => $this
->label(),
'@search' => $this
->getSearch()
->label(),
'@index' => $index
->label(),
];
$this
->getLogger()
->warning('Only invalid fulltext fields set for suggester "@suggester" in autocomplete settings for search "@search" on index "@index".', $args);
}
}
$query
->keys($user_input);
try {
$results = $query
->execute();
} catch (SearchApiException $e) {
return [];
}
$item_ids = array_keys($results
->getResultItems());
$objects = $index
->loadItemsMultiple($item_ids);
$factory = new SuggestionFactory($user_input);
$suggestions = [];
$view_modes = $this->configuration['view_modes'];
$highlight_field = NULL;
if ($this
->isHighlightingEnabled()) {
$highlight_field = $this->configuration['highlight']['field'];
}
foreach ($results
->getResultItems() as $item_id => $item) {
if (empty($objects[$item_id])) {
continue;
}
$object = $objects[$item_id];
$item
->setOriginalObject($object);
try {
$datasource = $item
->getDatasource();
} catch (SearchApiException $e) {
continue;
}
if (!$item
->getAccessResult()
->isAllowed()) {
continue;
}
$url = $datasource
->getItemUrl($object);
if (!$url) {
continue;
}
$datasource_id = $item
->getDatasourceId();
$bundle = $datasource
->getItemBundle($object);
if ($highlight_field) {
$highlighted_fields = $item
->getExtraData('highlighted_fields');
if (isset($highlighted_fields[$highlight_field][0])) {
$highlighted_field = [
'#type' => 'markup',
'#markup' => Xss::filterAdmin($highlighted_fields[$highlight_field][0]),
];
$suggestions[] = $factory
->createUrlSuggestion($url, NULL, $highlighted_field);
continue;
}
}
if (empty($view_modes[$datasource_id][$bundle])) {
$label = $datasource
->getItemLabel($object);
$suggestions[] = $factory
->createUrlSuggestion($url, $label);
}
else {
$view_mode = $view_modes[$datasource_id][$bundle];
$render = $datasource
->viewItem($object, $view_mode);
if ($render) {
$render['#search_api_excerpt'] = $item
->getExcerpt();
$suggestions[] = $factory
->createUrlSuggestion($url, NULL, $render);
}
}
}
return $suggestions;
}
public function calculateDependencies() {
$this->dependencies = parent::calculateDependencies();
$index = $this
->getSearch()
->getIndex();
foreach ($this->configuration['view_modes'] as $datasource_id => $bundles) {
$datasource = $index
->getDatasource($datasource_id);
$entity_type_id = $datasource
->getEntityTypeId();
if (!$entity_type_id) {
continue;
}
foreach ($bundles as $bundle => $view_mode) {
if ($view_mode === '') {
continue;
}
$view_mode_entity = $this
->getEntityTypeManager()
->getStorage('entity_view_mode')
->load($entity_type_id . '.' . $view_mode);
if ($view_mode_entity) {
$key = $view_mode_entity
->getConfigDependencyKey();
$name = $view_mode_entity
->getConfigDependencyName();
$this
->addDependency($key, $name);
}
}
}
return $this->dependencies;
}
public function onDependencyRemoval(array $dependencies) {
$removed_view_modes = [];
$non_view_mode_dependencies = FALSE;
foreach ($dependencies as $objects) {
foreach ($objects as $object) {
if ($object instanceof EntityInterface && $object
->getEntityTypeId() === 'entity_view_mode') {
$removed_view_modes[$object
->id()] = TRUE;
}
else {
$non_view_mode_dependencies = TRUE;
}
}
}
$index = $this
->getSearch()
->getIndex();
foreach ($this->configuration['view_modes'] as $datasource_id => $bundles) {
$datasource = $index
->getDatasource($datasource_id);
$entity_type_id = $datasource
->getEntityTypeId();
if (!$entity_type_id) {
continue;
}
foreach ($bundles as $bundle => $view_mode) {
if ($view_mode === '') {
continue;
}
$view_mode_entity_id = $entity_type_id . '.' . $view_mode;
if (!empty($removed_view_modes[$view_mode_entity_id])) {
$this->configuration['view_modes'][$datasource_id][$bundle] = '';
}
}
}
return !$non_view_mode_dependencies;
}
}