/** * Converts an array of property definitions into Search API field objects. * * Stores the resulting values in $this->fields, to be returned by subsequent * getFields() calls. * * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $properties * An array of properties on some complex data object. * @param string|null $datasource_id * (optional) The ID of the datasource to which these properties belong. * @param string $prefix * Internal use only. A prefix to use for the generated field names in this * method. * @param string $label_prefix * Internal use only. A prefix to use for the generated field labels in this * method. * * @throws \Drupal\search_api\SearchApiException * Thrown if $datasource_id is not valid datasource for this index. */ protected function convertPropertyDefinitionsToFields(array $properties, $datasource_id = NULL, $prefix = '', $label_prefix = '') { $type_mapping = Utility::getFieldTypeMapping(); $field_options = isset($this->options['fields']) ? $this->options['fields'] : array(); $enabled_additional_fields = isset($this->options['additional fields']) ? $this->options['additional fields'] : array(); // All field identifiers should start with the datasource ID. if (!$prefix && $datasource_id) { $prefix = $datasource_id . self::DATASOURCE_ID_SEPARATOR; } $datasource_label = $datasource_id ? $this->getDatasource($datasource_id)->label() . ' » ' : ''; // Loop over all properties and handle them accordingly. $recurse = array(); foreach ($properties as $property_path => $property) { $original_property = $property; if ($property instanceof PropertyInterface) { $property = $property->getWrappedProperty(); } $key = "$prefix$property_path"; $label = $label_prefix . $property->getLabel(); $description = $property->getDescription(); while ($property instanceof ListDataDefinitionInterface) { $property = $property->getItemDefinition(); } while ($property instanceof DataReferenceDefinitionInterface) { $property = $property->getTargetDefinition(); } if ($property instanceof ComplexDataDefinitionInterface) { $main_property = $property->getMainPropertyName(); $nested_properties = $property->getPropertyDefinitions(); // Don't add the additional 'entity' property for entity reference // fields which don't target a content entity type. $referenced_entity_type_label = NULL; if ($property instanceof FieldItemDataDefinition && in_array($property->getDataType(), array('field_item:entity_reference', 'field_item:image', 'field_item:file'))) { $entity_type = $this->entityManager()->getDefinition($property->getSetting('target_type')); if (!$entity_type->isSubclassOf('Drupal\Core\Entity\ContentEntityInterface')) { unset($nested_properties['entity']); } else { $referenced_entity_type_label = $entity_type->getLabel(); } } $additional = count($nested_properties) > 1; if (!empty($enabled_additional_fields[$key]) && $nested_properties) { // We allow the main property to be indexed directly, so we don't // have to add it again for the nested fields. if ($main_property) { unset($nested_properties[$main_property]); } if ($nested_properties) { $additional = TRUE; $recurse[] = array($nested_properties, $datasource_id, "$key:", "$label » "); } } if ($additional) { $additional_field = Utility::createAdditionalField($this, $key); $additional_field->setLabel("$label [$key]"); $additional_field->setDescription($description); $additional_field->setEnabled(!empty($enabled_additional_fields[$key])); $additional_field->setLocked(FALSE); if ($original_property instanceof PropertyInterface) { $additional_field->setHidden($original_property->isHidden()); } $this->fields[0]['additional fields'][$key] = $additional_field; if ($additional_field->isEnabled()) { while ($pos = strrpos($property_path, ':')) { $property_path = substr($property_path, 0, $pos); /** @var \Drupal\search_api\Item\AdditionalFieldInterface $additional_field */ $additional_field = $this->fields[0]['additional fields'][$property_path]; $additional_field->setEnabled(TRUE); $additional_field->setLocked(); } } } // If the complex data type has a main property, we can index that // directly here. Otherwise, we don't add it and continue with the next // property. if (!$main_property) { continue; } $parent_type = $property->getDataType(); $property = $property->getPropertyDefinition($main_property); if (!$property) { continue; } // If there are additional properties, add the label for the main // property to make it clear what it refers to. if ($additional) { $nested_label = $property->getLabel(); if ($referenced_entity_type_label) { $nested_label = str_replace('@label', $referenced_entity_type_label, $nested_label); } $label .= ' » ' . $nested_label; } } $type = $property->getDataType(); // Try to see if there's a mapping for a parent.child data type. if (isset($parent_type) && isset($type_mapping[$parent_type . '.' . $type])) { $field_type = $type_mapping[$parent_type . '.' . $type]; } elseif (!empty($type_mapping[$type])) { $field_type = $type_mapping[$type]; } else { // Failed to map this type, skip. if (!isset($type_mapping[$type])) { $this->unmappedFields[$type][$key] = $key; } continue; } $field = Utility::createField($this, $key); $field->setType($field_type); $field->setLabel($label); $field->setLabelPrefix($datasource_label); $field->setDescription($description); $field->setIndexed(FALSE); // To make it possible to lock fields that are, technically, nested, use // the original $property for this check. if ($original_property instanceof PropertyInterface) { $field->setIndexedLocked($original_property->isIndexedLocked()); $field->setTypeLocked($original_property->isTypeLocked()); $field->setHidden($original_property->isHidden()); } $this->fields[0]['fields'][$key] = $field; if (isset($field_options[$key]) || $field->isIndexedLocked()) { $field->setIndexed(TRUE); if (isset($field_options[$key])) { $field->setType($field_options[$key]['type']); if (isset($field_options[$key]['boost'])) { $field->setBoost($field_options[$key]['boost']); } } $this->fields[1]['fields'][$key] = $field; } } foreach ($recurse as $arguments) { call_user_func_array(array($this, 'convertPropertyDefinitionsToFields'), $arguments); } // Order unindexed fields alphabetically. $sort_by_label = function(GenericFieldInterface $field1, GenericFieldInterface $field2) { return strnatcasecmp($field1->getLabel(), $field2->getLabel()); }; uasort($this->fields[0]['fields'], $sort_by_label); uasort($this->fields[0]['additional fields'], $sort_by_label); }