View source
<?php
namespace Drupal\search_api\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Utility\Error;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Utility\PluginHelperInterface;
use Drupal\search_api\Utility\Utility;
use Symfony\Component\DependencyInjection\ContainerInterface;
class IndexForm extends EntityForm {
protected $messenger;
protected $entityTypeManager;
protected $pluginHelper;
protected $originalEntity;
public function __construct(EntityTypeManagerInterface $entity_type_manager, PluginHelperInterface $plugin_helper, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->pluginHelper = $plugin_helper;
$this->messenger = $messenger;
}
public static function create(ContainerInterface $container) {
$entity_type_manager = $container
->get('entity_type.manager');
$plugin_helper = $container
->get('search_api.plugin_helper');
$messenger = $container
->get('messenger');
return new static($entity_type_manager, $plugin_helper, $messenger);
}
protected function getServerOptions() {
$options = [];
$servers = $this->entityTypeManager
->getStorage('search_api_server')
->loadMultiple();
foreach ($servers as $server_id => $server) {
$options[$server_id] = Utility::escapeHtml($server
->label());
}
return $options;
}
public function form(array $form, FormStateInterface $form_state) {
if ($form_state
->isRebuilding()) {
if (!$this->entity
->isNew()) {
$form_state
->setValue('id', $this->entity
->id());
}
$this->entity = $this
->buildEntity($form, $form_state);
}
$form = parent::form($form, $form_state);
$index = $this
->getEntity();
if ($index
->isNew()) {
$form['#title'] = $this
->t('Add search index');
}
else {
$arguments = [
'%label' => $index
->label(),
];
$form['#title'] = $this
->t('Edit search index %label', $arguments);
}
$this
->buildEntityForm($form, $form_state, $index);
return $form;
}
public function buildEntityForm(array &$form, FormStateInterface $form_state, IndexInterface $index) {
$form['#tree'] = TRUE;
$form['name'] = [
'#type' => 'textfield',
'#title' => $this
->t('Index name'),
'#description' => $this
->t('Enter the displayed name for the index.'),
'#default_value' => $index
->label(),
'#required' => TRUE,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $index
->isNew() ? NULL : $index
->id(),
'#maxlength' => 50,
'#required' => TRUE,
'#machine_name' => [
'exists' => '\\Drupal\\search_api\\Entity\\Index::load',
'source' => [
'name',
],
],
'#disabled' => !$index
->isNew(),
];
$form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css';
$form['datasources'] = [
'#type' => 'checkboxes',
'#title' => $this
->t('Datasources'),
'#description' => $this
->t('Select one or more datasources of items that will be stored in this index.'),
'#default_value' => $index
->getDatasourceIds(),
'#multiple' => TRUE,
'#required' => TRUE,
'#attributes' => [
'class' => [
'search-api-checkboxes-list',
],
],
'#ajax' => [
'trigger_as' => [
'name' => 'datasources_configure',
],
'callback' => '::buildAjaxDatasourceConfigForm',
'wrapper' => 'search-api-datasources-config-form',
'method' => 'replace',
'effect' => 'fade',
],
];
$datasource_options = [];
foreach ($this->pluginHelper
->createDatasourcePlugins($index) as $datasource_id => $datasource) {
if ($datasource
->isHidden()) {
continue;
}
$datasource_options[$datasource_id] = Utility::escapeHtml($datasource
->label());
$form['datasources'][$datasource_id]['#description'] = Utility::escapeHtml($datasource
->getDescription());
}
asort($datasource_options, SORT_NATURAL | SORT_FLAG_CASE);
$form['datasources']['#options'] = $datasource_options;
$form['datasource_configs'] = [
'#type' => 'container',
'#attributes' => [
'id' => 'search-api-datasources-config-form',
],
'#tree' => TRUE,
];
$form['datasource_configure_button'] = [
'#type' => 'submit',
'#name' => 'datasources_configure',
'#value' => $this
->t('Configure'),
'#limit_validation_errors' => [
[
'datasources',
],
],
'#submit' => [
'::submitAjaxDatasourceConfigForm',
],
'#ajax' => [
'callback' => '::buildAjaxDatasourceConfigForm',
'wrapper' => 'search-api-datasources-config-form',
],
'#attributes' => [
'class' => [
'js-hide',
],
],
];
$this
->buildDatasourcesConfigForm($form, $form_state, $index);
$form['tracker'] = [
'#type' => 'radios',
'#title' => $this
->t('Tracker'),
'#description' => $this
->t('Select the type of tracker which should be used for keeping track of item changes.'),
'#default_value' => $index
->getTrackerId(),
'#required' => TRUE,
'#ajax' => [
'trigger_as' => [
'name' => 'tracker_configure',
],
'callback' => '::buildAjaxTrackerConfigForm',
'wrapper' => 'search-api-tracker-config-form',
'method' => 'replace',
'effect' => 'fade',
],
];
$tracker_options = [];
foreach ($this->pluginHelper
->createTrackerPlugins($index) as $tracker_id => $tracker) {
if ($tracker
->isHidden()) {
continue;
}
$tracker_options[$tracker_id] = Utility::escapeHtml($tracker
->label());
$form['tracker'][$tracker_id]['#description'] = Utility::escapeHtml($tracker
->getDescription());
}
asort($tracker_options, SORT_NATURAL | SORT_FLAG_CASE);
$form['tracker']['#options'] = $tracker_options;
$form['tracker']['#access'] = !$index
->hasValidTracker() || count($tracker_options) > 1;
$form['tracker_config'] = [
'#type' => 'container',
'#attributes' => [
'id' => 'search-api-tracker-config-form',
],
'#tree' => TRUE,
];
$form['tracker_configure_button'] = [
'#type' => 'submit',
'#name' => 'tracker_configure',
'#value' => $this
->t('Configure'),
'#limit_validation_errors' => [
[
'tracker',
],
],
'#submit' => [
'::submitAjaxTrackerConfigForm',
],
'#ajax' => [
'callback' => '::buildAjaxTrackerConfigForm',
'wrapper' => 'search-api-tracker-config-form',
],
'#attributes' => [
'class' => [
'js-hide',
],
],
'#access' => count($tracker_options) > 1,
];
$this
->buildTrackerConfigForm($form, $form_state, $index);
$form['server'] = [
'#type' => 'radios',
'#title' => $this
->t('Server'),
'#description' => $this
->t('Select the server this index should use. Indexes cannot be enabled without a connection to a valid, enabled server.'),
'#options' => [
'' => '<em>' . $this
->t('- No server -') . '</em>',
] + $this
->getServerOptions(),
'#default_value' => $index
->hasValidServer() ? $index
->getServerId() : '',
];
$form['status'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Enabled'),
'#description' => $this
->t('Only enabled indexes can be used for indexing and searching. This setting will only take effect if the selected server is also enabled.'),
'#default_value' => $index
->status(),
'#disabled' => !$index
->status() && (!$index
->hasValidServer() || !$index
->getServerInstance()
->status()),
'#states' => [
'invisible' => [
':input[name="server"]' => [
'value' => '',
],
],
],
];
$form['description'] = [
'#type' => 'textarea',
'#title' => $this
->t('Description'),
'#description' => $this
->t('Enter a description for the index.'),
'#default_value' => $index
->getDescription(),
];
$form['options'] = [
'#tree' => TRUE,
'#type' => 'details',
'#title' => $this
->t('Index options'),
'#collapsed' => TRUE,
];
$form['options']['read_only'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Read only'),
'#description' => $this
->t('Do not write to this index or track the status of items in this index.'),
'#default_value' => $index
->isReadOnly(),
'#parents' => [
'read_only',
],
];
$form['options']['index_directly'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Index items immediately'),
'#description' => $this
->t('Immediately index new or updated items instead of waiting for the next cron run. This might have serious performance drawbacks and is generally not advised for larger sites.'),
'#default_value' => $index
->getOption('index_directly'),
];
$form['options']['cron_limit'] = [
'#type' => 'textfield',
'#title' => $this
->t('Cron batch size'),
'#description' => $this
->t('Set how many items will be indexed at once when indexing items during a cron run. "0" means that no items will be indexed by cron for this index, "-1" means that cron should index all items at once.'),
'#default_value' => $index
->getOption('cron_limit'),
'#size' => 4,
];
}
public function buildDatasourcesConfigForm(array &$form, FormStateInterface $form_state, IndexInterface $index) {
$selected_datasources = $form_state
->getValue('datasources');
if ($selected_datasources === NULL) {
$datasources = $index
->getDatasources();
}
else {
$datasources = $this->pluginHelper
->createDatasourcePlugins($index, $selected_datasources);
}
$form_state
->set('datasources', array_keys($datasources));
$show_message = FALSE;
foreach ($datasources as $datasource_id => $datasource) {
if ($datasource instanceof PluginFormInterface) {
$datasource_form = [];
if (!empty($form['datasource_configs'][$datasource_id])) {
$datasource_form = $form['datasource_configs'][$datasource_id];
}
$datasource_form_state = SubformState::createForSubform($datasource_form, $form, $form_state);
$form['datasource_configs'][$datasource_id] = $datasource
->buildConfigurationForm($datasource_form, $datasource_form_state);
$show_message = TRUE;
$form['datasource_configs'][$datasource_id]['#type'] = 'details';
$form['datasource_configs'][$datasource_id]['#title'] = $this
->t('Configure the %datasource datasource', [
'%datasource' => $datasource
->label(),
]);
$form['datasource_configs'][$datasource_id]['#open'] = $index
->isNew();
}
}
if ($selected_datasources && $show_message) {
$this->messenger
->addWarning($this
->t('Please configure the used datasources.'));
}
}
public function buildTrackerConfigForm(array &$form, FormStateInterface $form_state, IndexInterface $index) {
$selected_tracker = $form_state
->getValue('tracker');
if ($selected_tracker === NULL || $selected_tracker == $index
->getTrackerId()) {
if ($index
->hasValidTracker()) {
$tracker = $index
->getTrackerInstance();
}
elseif (!$index
->isNew()) {
$this->messenger
->addError($this
->t('The tracker plugin is missing or invalid.'));
}
}
else {
$tracker = $this->pluginHelper
->createTrackerPlugin($index, $selected_tracker);
}
if (empty($tracker)) {
return;
}
$form_state
->set('tracker', $tracker
->getPluginId());
if ($tracker instanceof PluginFormInterface) {
$tracker_form = $form['tracker_config'] ?? [];
$tracker_form_state = SubformState::createForSubform($tracker_form, $form, $form_state);
$form['tracker_config'] = $tracker
->buildConfigurationForm($tracker_form, $tracker_form_state);
$form['tracker_config']['#type'] = 'details';
$form['tracker_config']['#title'] = $this
->t('Configure the %plugin tracker', [
'%plugin' => $tracker
->label(),
]);
$form['tracker_config']['#description'] = Utility::escapeHtml($tracker
->getDescription());
$form['tracker_config']['#open'] = $index
->isNew();
if ($selected_tracker && $selected_tracker != $tracker
->getPluginId()) {
$this->messenger
->addWarning($this
->t('Please configure the used tracker.'));
}
}
}
public function submitAjaxDatasourceConfigForm(array $form, FormStateInterface $form_state) {
$form_state
->setValue('id', NULL);
$form_state
->setRebuild();
}
public function buildAjaxDatasourceConfigForm(array $form, FormStateInterface $form_state) {
return $form['datasource_configs'];
}
public function submitAjaxTrackerConfigForm(array $form, FormStateInterface $form_state) {
$form_state
->setValue('id', NULL);
$form_state
->setRebuild();
}
public function buildAjaxTrackerConfigForm(array $form, FormStateInterface $form_state) {
return $form['tracker_config'];
}
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$index = $this
->getEntity();
$storage = $this->entityTypeManager
->getStorage('search_api_index');
if (!$index
->isNew()) {
$this->originalEntity = $storage
->loadUnchanged($index
->id());
}
if (empty($this->originalEntity)) {
$this->originalEntity = $storage
->create([
'status' => FALSE,
]);
}
$datasource_ids = array_values(array_filter($form_state
->getValue('datasources', [])));
$form_state
->setValue('datasources', $datasource_ids);
$datasources = $this->pluginHelper
->createDatasourcePlugins($index, $datasource_ids);
$previous_datasources = $form_state
->get('datasources');
foreach ($datasources as $datasource_id => $datasource) {
if ($datasource instanceof PluginFormInterface) {
if (!in_array($datasource_id, $previous_datasources)) {
$form_state
->setRebuild();
continue;
}
$datasource_form =& $form['datasource_configs'][$datasource_id];
$datasource_form_state = SubformState::createForSubform($datasource_form, $form, $form_state);
$datasource
->validateConfigurationForm($datasource_form, $datasource_form_state);
}
}
$tracker_id = $form_state
->getValue('tracker', NULL);
if ($tracker_id == $form_state
->get('tracker')) {
$tracker = $this->pluginHelper
->createTrackerPlugin($index, $tracker_id);
if ($tracker instanceof PluginFormInterface) {
$tracker_form_state = SubformState::createForSubform($form['tracker_config'], $form, $form_state);
$tracker
->validateConfigurationForm($form['tracker_config'], $tracker_form_state);
}
}
else {
$tracker = $this->pluginHelper
->createTrackerPlugin($this->originalEntity, $tracker_id);
if ($tracker instanceof PluginFormInterface) {
$form_state
->setRebuild();
}
}
}
public function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
if ($this
->getEntity()
->isNew()) {
$actions['save_edit'] = [
'#type' => 'submit',
'#value' => $this
->t('Save and add fields'),
'#submit' => $actions['submit']['#submit'],
'#button_type' => 'primary',
'#redirect_to_url' => 'add-fields',
];
}
return $actions;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$index = $this
->getEntity();
$index
->setOptions($form_state
->getValue('options', []) + $this->originalEntity
->getOptions());
$datasource_ids = $form_state
->getValue('datasources', []);
$datasources = $this->pluginHelper
->createDatasourcePlugins($index, $datasource_ids);
foreach ($datasources as $datasource_id => $datasource) {
if ($datasource instanceof PluginFormInterface) {
$datasource_form_state = SubformState::createForSubform($form['datasource_configs'][$datasource_id], $form, $form_state);
$datasource
->submitConfigurationForm($form['datasource_configs'][$datasource_id], $datasource_form_state);
}
}
$index
->setDatasources($datasources);
$tracker_id = $form_state
->getValue('tracker', NULL);
$tracker = $this->pluginHelper
->createTrackerPlugin($index, $tracker_id);
if ($tracker_id == $form_state
->get('tracker')) {
if ($tracker instanceof PluginFormInterface) {
$tracker_form_state = SubformState::createForSubform($form['tracker_config'], $form, $form_state);
$tracker
->submitConfigurationForm($form['tracker_config'], $tracker_form_state);
}
}
$index
->setTracker($tracker);
}
public function save(array $form, FormStateInterface $form_state) {
if (!$form_state
->isRebuilding()) {
try {
$index = $this
->getEntity();
$index
->save();
$this->messenger
->addStatus($this
->t('The index was successfully saved.'));
$button = $form_state
->getTriggeringElement();
if (!empty($button['#redirect_to_url'])) {
$form_state
->setRedirectUrl($index
->toUrl($button['#redirect_to_url']));
}
else {
$form_state
->setRedirect('entity.search_api_index.canonical', [
'search_api_index' => $index
->id(),
]);
}
} catch (EntityStorageException $e) {
$form_state
->setRebuild();
$message = '%type: @message in %function (line %line of %file).';
$variables = Error::decodeException($e);
$this
->getLogger('search_api')
->error($message, $variables);
$this->messenger
->addError($this
->t('The index could not be saved.'));
}
}
}
}