You are here

public function DataFetcher::autocompletePropertyPath in Typed Data API enhancements 8

Provides autocomplete suggestions for an incomplete property path.

Parameters

\Drupal\Core\TypedData\DataDefinitionInterface[] $data_definitions: A map of available data definitions that should be seareched. The array keys are the first part of the property path.

string $partial_property_path: The partial property path, example: "node.uid.ent".

Return value

array[] A list of autocomplete suggestions - valid property paths for one of the provided data definitions. Each entry is an array with the following keys:

  • value: the data selector property path.
  • label: the human readable label suggestion.

Overrides DataFetcherInterface::autocompletePropertyPath

File

src/DataFetcher.php, line 166

Class

DataFetcher
Implementation of the data fetcher service.

Namespace

Drupal\typed_data

Code

public function autocompletePropertyPath(array $data_definitions, $partial_property_path) {

  // For the empty string we suggest the names of the data definitions.
  if ($partial_property_path == '') {
    return array_keys($data_definitions);
  }
  $results = [];

  // Suggest top level variables where the partial path matches the start of
  // the top level variable name.
  foreach ($data_definitions as $variable_name => $data_definition) {
    if (stripos($variable_name, $partial_property_path) === 0) {
      $results = array_merge($results, $this
        ->getAutocompleteSuggestion($data_definition, $variable_name));
    }
  }
  if (!empty($results)) {
    return $results;
  }

  // Partial path now contains more than just a top level variable. In order
  // to separate the variable parts we have to account for the syntax of
  // global context variables versus local context variables.
  // Global context variables begin with '@' and have a colon separating the
  // global context from the variable.
  $colon = strpos($partial_property_path, ':');
  if ($colon === FALSE) {

    // This is NOT a global context variable, so we only have to worry about
    // the '.' separators.
    $parts = explode('.', $partial_property_path);
    $first_part = array_shift($parts);
  }
  else {

    // This IS a global context variable, so the entire string up to and
    // including the ':' needs to be removed before we split the remainder
    // at the '.' separators.
    $parts = explode('.', substr($partial_property_path, $colon + 1));
    $first_part = substr($partial_property_path, 0, $colon + 1) . array_shift($parts);
  }
  if (!isset($data_definitions[$first_part])) {
    return [];
  }
  $last_part = array_pop($parts);
  $middle_path = implode('.', $parts);
  if ($middle_path === '') {
    $variable_definition = $data_definitions[$first_part];
  }
  else {
    try {
      $variable_definition = $this
        ->fetchDefinitionByPropertyPath($data_definitions[$first_part], $middle_path);
    } catch (InvalidArgumentException $e) {

      // Invalid property path, so no suggestions available.
      return [];
    }
  }

  // If the current data is just a reference then directly dereference the
  // target.
  if ($variable_definition instanceof DataReferenceDefinitionInterface) {
    $variable_definition = $variable_definition
      ->getTargetDefinition();
  }
  if ($variable_definition instanceof ListDataDefinitionInterface) {

    // Suggest a couple of example indices of a list if there is nothing
    // selected on it yet. Special case for fields: only make the suggestion
    // if this is a multi-valued field.
    if ($last_part === '' && !($variable_definition instanceof FieldDefinitionInterface && $variable_definition
      ->getFieldStorageDefinition()
      ->getCardinality() === 1)) {
      if ($middle_path === '') {
        $property_path = $first_part;
      }
      else {
        $property_path = "{$first_part}.{$middle_path}";
      }
      $item_definition = $variable_definition
        ->getItemDefinition();
      for ($i = 0; $i < 3; $i++) {
        $results = array_merge($results, $this
          ->getAutocompleteSuggestion($item_definition, "{$property_path}.{$i}"));
      }
    }

    // If this is a list but the selector is not an integer, we forward the
    // selection to the first element in the list.
    if (!ctype_digit($last_part)) {
      $variable_definition = $variable_definition
        ->getItemDefinition();
    }
  }
  if ($variable_definition instanceof ComplexDataDefinitionInterface) {
    foreach ($variable_definition
      ->getPropertyDefinitions() as $property_name => $property_definition) {

      // If the property starts with the part then we have a suggestion. If
      // the part after the dot is the empty string we include all properties.
      if (stripos($property_name, $last_part) === 0 || $last_part === '') {
        if ($middle_path === '') {
          $property_path = "{$first_part}.{$property_name}";
        }
        else {
          $property_path = "{$first_part}.{$middle_path}.{$property_name}";
        }
        $results = array_merge($results, $this
          ->getAutocompleteSuggestion($property_definition, $property_path));
      }
    }
  }
  usort($results, function ($a, $b) {
    return strnatcasecmp($a['value'], $b['value']);
  });
  return $results;
}