View source
<?php
namespace Drupal\search_api\Form;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
use Drupal\Core\Url;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Processor\ConfigurablePropertyInterface;
use Drupal\search_api\Processor\ProcessorPropertyInterface;
use Drupal\search_api\Utility\DataTypeHelperInterface;
use Drupal\search_api\Utility\FieldsHelperInterface;
use Drupal\search_api\Utility\Utility;
use Symfony\Component\DependencyInjection\ContainerInterface;
class IndexAddFieldsForm extends EntityForm {
use UnsavedConfigurationFormTrait;
protected $fieldsHelper;
protected $dataTypeHelper;
protected $entity;
protected $parameters;
protected $unmappedFields = [];
protected $formIdAttribute;
protected $messenger;
public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldsHelperInterface $fields_helper, DataTypeHelperInterface $data_type_helper, RendererInterface $renderer, DateFormatterInterface $date_formatter, MessengerInterface $messenger, array $parameters) {
$this->entityTypeManager = $entity_type_manager;
$this->fieldsHelper = $fields_helper;
$this->dataTypeHelper = $data_type_helper;
$this->renderer = $renderer;
$this->dateFormatter = $date_formatter;
$this->messenger = $messenger;
$this->parameters = $parameters;
}
public static function create(ContainerInterface $container) {
$entity_type_manager = $container
->get('entity_type.manager');
$fields_helper = $container
->get('search_api.fields_helper');
$data_type_helper = $container
->get('search_api.data_type_helper');
$renderer = $container
->get('renderer');
$date_formatter = $container
->get('date.formatter');
$request_stack = $container
->get('request_stack');
$messenger = $container
->get('messenger');
$parameters = $request_stack
->getCurrentRequest()->query
->all();
return new static($entity_type_manager, $fields_helper, $data_type_helper, $renderer, $date_formatter, $messenger, $parameters);
}
public function getBaseFormId() {
return NULL;
}
public function getFormId() {
return 'search_api_index_add_fields';
}
public function getParameter($name, $default = NULL) {
return $this->parameters[$name] ?? $default;
}
public function buildForm(array $form, FormStateInterface $form_state) {
$index = $this->entity;
$form_state
->disableCache();
$this
->checkEntityEditable($form, $index);
$args['%index'] = $index
->label();
$form['#title'] = $this
->t('Add fields to index %index', $args);
$this->formIdAttribute = Html::getUniqueId($this
->getFormId());
$form['#id'] = $this->formIdAttribute;
$form['messages'] = [
'#type' => 'status_messages',
];
$datasources = [
'' => NULL,
];
$datasources += $this->entity
->getDatasources();
foreach ($datasources as $datasource_id => $datasource) {
$item = $this
->getDatasourceListItem($datasource);
if ($item) {
$form['datasources']['datasource_' . $datasource_id] = $item;
}
}
$form['actions'] = $this
->actionsElement($form, $form_state);
if ($this->unmappedFields) {
$unmapped_fields = [];
foreach ($this->unmappedFields as $type => $fields) {
foreach ($fields as $field) {
$unmapped_fields[] = new FormattableMarkup('@field (type "@type")', [
'@field' => $field,
'@type' => $type,
]);
}
}
$form['unmapped_types'] = [
'#type' => 'details',
'#title' => $this
->t('Skipped fields'),
'fields' => [
'#theme' => 'item_list',
'#items' => $unmapped_fields,
'#prefix' => $this
->t('The following fields cannot be indexed since there is no type mapping for them:'),
'#suffix' => $this
->t("If you think one of these fields should be available for indexing, please report this in the module's <a href=':url'>issue queue</a>. (Make sure to first search for an existing issue for this field.) Please note that entity-valued fields generally can be indexed by either indexing their parent reference field, or their child entity ID field.", [
':url' => Url::fromUri('https://www.drupal.org/project/issues/search_api')
->toString(),
]),
],
];
}
return $form;
}
protected function getDatasourceListItem(DatasourceInterface $datasource = NULL) {
$datasource_id = $datasource ? $datasource
->getPluginId() : NULL;
$datasource_id_param = $datasource_id ?: '';
$properties = $this->entity
->getPropertyDefinitions($datasource_id);
if ($properties) {
$active_property_path = '';
$active_datasource = $this
->getParameter('datasource');
if ($active_datasource !== NULL && $active_datasource == $datasource_id_param) {
$active_property_path = $this
->getParameter('property_path', '');
}
$base_url = $this->entity
->toUrl('add-fields');
$base_url
->setOption('query', [
'datasource' => $datasource_id_param,
]);
$item = $this
->getPropertiesList($properties, $active_property_path, $base_url, $datasource_id);
$item['#title'] = $datasource ? $datasource
->label() : $this
->t('General');
return $item;
}
return NULL;
}
protected function getPropertiesList(array $properties, $active_property_path, Url $base_url, $datasource_id, $parent_path = '', $label_prefix = '') {
$list = [];
$active_item = '';
if ($active_property_path) {
list($active_item, $active_property_path) = explode(':', $active_property_path, 2) + [
1 => '',
];
}
$type_mapping = $this->dataTypeHelper
->getFieldTypeMapping();
$query_base = $base_url
->getOption('query');
foreach ($properties as $key => $property) {
if ($property instanceof ProcessorPropertyInterface && $property
->isHidden()) {
continue;
}
$this_path = $parent_path ? $parent_path . ':' : '';
$this_path .= $key;
$label = $property
->getLabel();
$property = $this->fieldsHelper
->getInnerProperty($property);
$can_be_indexed = TRUE;
$nested_properties = [];
$parent_child_type = NULL;
if ($property instanceof ComplexDataDefinitionInterface) {
$can_be_indexed = FALSE;
$nested_properties = $this->fieldsHelper
->getNestedProperties($property);
$main_property = $property
->getMainPropertyName();
if ($main_property && isset($nested_properties[$main_property])) {
$parent_child_type = $property
->getDataType() . '.';
$property = $nested_properties[$main_property];
$parent_child_type .= $property
->getDataType();
unset($nested_properties[$main_property]);
$can_be_indexed = TRUE;
}
if (isset($nested_properties['entity'])) {
$entity_property = $nested_properties['entity'];
if ($entity_property instanceof DataReferenceDefinitionInterface) {
$target = $entity_property
->getTargetDefinition();
if ($target instanceof EntityDataDefinitionInterface) {
if (!$this->fieldsHelper
->isContentEntityType($target
->getEntityTypeId())) {
unset($nested_properties['entity']);
}
}
}
}
foreach ($nested_properties as $nested_key => $nested_property) {
if ($nested_property instanceof ProcessorPropertyInterface && $nested_property
->isHidden()) {
unset($nested_properties[$nested_key]);
}
}
}
$type = $property
->getDataType();
if ($parent_child_type && !empty($type_mapping[$parent_child_type])) {
$type = $parent_child_type;
}
elseif (empty($type_mapping[$type])) {
if (!isset($type_mapping[$type])) {
$this->unmappedFields[$type][] = $label_prefix . $label;
}
$can_be_indexed = FALSE;
}
if (!($nested_properties || $can_be_indexed)) {
continue;
}
$item = [
'#type' => 'container',
'#attributes' => [
'class' => [
'container-inline',
],
],
];
$nested_list = [];
if ($nested_properties) {
if ($key == $active_item) {
$link_url = clone $base_url;
$query_base['property_path'] = $parent_path;
$link_url
->setOption('query', $query_base);
$item['expand_link'] = [
'#type' => 'link',
'#title' => '(-) ',
'#attributes' => [
'data-disable-refocus' => [
'true',
],
],
'#url' => $link_url,
'#ajax' => [
'wrapper' => $this->formIdAttribute,
],
];
$nested_list = $this
->getPropertiesList($nested_properties, $active_property_path, $base_url, $datasource_id, $this_path, $label_prefix . $label . ' » ');
}
else {
$link_url = clone $base_url;
$query_base['property_path'] = $this_path;
$link_url
->setOption('query', $query_base);
$item['expand_link'] = [
'#type' => 'link',
'#title' => '(+) ',
'#attributes' => [
'data-disable-refocus' => [
'true',
],
],
'#url' => $link_url,
'#ajax' => [
'wrapper' => $this->formIdAttribute,
],
];
}
}
$label_markup = Html::escape($label);
$escaped_path = Html::escape($this_path);
$label_markup = "{$label_markup} <small>(<code>{$escaped_path}</code>)</small>";
$item['label']['#markup'] = $label_markup . ' ';
if ($can_be_indexed) {
$item['add'] = [
'#type' => 'submit',
'#name' => Utility::createCombinedId($datasource_id, $this_path),
'#value' => $this
->t('Add'),
'#submit' => [
'::addField',
'::save',
],
'#attributes' => [
'data-disable-refocus' => [
'true',
],
],
'#property' => $property,
'#prefixed_label' => $label_prefix . $label,
'#data_type' => $type_mapping[$type],
'#ajax' => [
'wrapper' => $this->formIdAttribute,
],
];
}
if ($nested_list) {
$item['properties'] = $nested_list;
}
$list[$key] = $item;
}
if ($list) {
uasort($list, [
static::class,
'compareFieldLabels',
]);
$list['#theme'] = 'search_api_form_item_list';
}
return $list;
}
public static function compareFieldLabels(array $a, array $b) {
$a_title = (string) $a['label']['#markup'];
$b_title = (string) $b['label']['#markup'];
return strnatcasecmp($a_title, $b_title);
}
protected function actions(array $form, FormStateInterface $form_state) {
return [
'done' => [
'#type' => 'link',
'#title' => $this
->t('Done'),
'#url' => $this->entity
->toUrl('fields'),
'#attributes' => [
'class' => [
'button',
],
],
],
];
}
public function addField(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
if (!$button) {
return;
}
$property = $button['#property'];
list($datasource_id, $property_path) = Utility::splitCombinedId($button['#name']);
$field = $this->fieldsHelper
->createFieldFromProperty($this->entity, $property, $datasource_id, $property_path, NULL, $button['#data_type']);
$field
->setLabel($button['#prefixed_label']);
$this->entity
->addField($field);
if ($property instanceof ConfigurablePropertyInterface) {
$parameters = [
'search_api_index' => $this->entity
->id(),
'field_id' => $field
->getFieldIdentifier(),
];
$options = [];
$route = $this
->getRequest()->attributes
->get('_route');
if ($route === 'entity.search_api_index.add_fields_ajax') {
$options['query'] = [
MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax',
'modal_redirect' => 1,
];
}
$form_state
->setRedirect('entity.search_api_index.field_config', $parameters, $options);
}
$args['%label'] = $field
->getLabel();
$this->messenger
->addStatus($this
->t('Field %label was added to the index.', $args));
}
}