You are here

function field_tokens in Token 8

Same name and namespace in other branches
  1. 7 token.tokens.inc \field_tokens()

Implements hook_tokens() on behalf of field.module.

File

./token.tokens.inc, line 1677
Token callbacks for the token module.

Code

function field_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
  $replacements = [];
  $langcode = isset($options['langcode']) ? $options['langcode'] : NULL;

  // Entity tokens.
  if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {

    /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $data['entity'];
    if (!$entity instanceof ContentEntityInterface) {
      return $replacements;
    }
    if (!isset($options['langcode'])) {

      // Set the active language in $options, so that it is passed along.
      $langcode = $options['langcode'] = $entity
        ->language()
        ->getId();
    }

    // Obtain the entity with the correct language.
    $entity = \Drupal::service('entity.repository')
      ->getTranslationFromContext($entity, $langcode);
    foreach ($tokens as $name => $original) {

      // For the [entity:field_name] token.
      if (strpos($name, ':') === FALSE) {
        $field_name = $name;
        $token_name = $name;
      }
      else {
        list($field_name, $delta) = explode(':', $name, 2);
        if (!is_numeric($delta)) {
          unset($delta);
        }
        $token_name = $field_name;
      }

      // Ensure the entity has the requested field and that the token for it is
      // defined by token.module.
      if (!$entity
        ->hasField($field_name) || _token_module($data['token_type'], $token_name) != 'token') {
        continue;
      }
      $display_options = 'token';

      // Do not continue if the field is empty.
      if ($entity
        ->get($field_name)
        ->isEmpty()) {
        continue;
      }

      // Handle [entity:field_name] and [entity:field_name:0] tokens.
      if ($field_name === $name || isset($delta)) {
        $view_display = token_get_token_view_display($entity);
        if (!$view_display) {

          // We don't have the token view display and should fall back on
          // default formatters. If the field has specified a specific formatter
          // to be used by default with tokens, use that, otherwise use the
          // default formatter.

          /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
          $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
          $field_type_definition = $field_type_manager
            ->getDefinition($entity
            ->getFieldDefinition($field_name)
            ->getType());
          $display_options = [
            'type' => !empty($field_type_definition['default_token_formatter']) ? $field_type_definition['default_token_formatter'] : $field_type_definition['default_formatter'],
            'label' => 'hidden',
          ];
        }

        // Render only one delta.
        if (isset($delta)) {
          if ($field_delta = $entity->{$field_name}[$delta]) {
            $field_output = $field_delta
              ->view($display_options);
          }
          else {
            continue;
          }
        }
        else {
          $field_output = $entity->{$field_name}
            ->view($display_options);

          // If we are displaying all field items we need this #pre_render
          // callback.
          $field_output['#pre_render'][] = '\\Drupal\\token\\TokenFieldRender::preRender';
        }
        $field_output['#token_options'] = $options;
        $replacements[$original] = \Drupal::service('renderer')
          ->renderPlain($field_output);
      }
      elseif ($field_tokens = \Drupal::token()
        ->findWithPrefix($tokens, $field_name)) {

        // With multiple nested tokens for the same field name, this might
        // match the same field multiple times. Filter out those that have
        // already been replaced.
        $field_tokens = array_filter($field_tokens, function ($token) use ($replacements) {
          return !isset($replacements[$token]);
        });
        if ($field_tokens) {
          $property_token_data = [
            'field_property' => TRUE,
            $data['entity_type'] . '-' . $field_name => $entity->{$field_name},
            'field_name' => $data['entity_type'] . '-' . $field_name,
          ];

          // The optimization above only works on the root level, if there is
          // more than one nested field it would still replace all in every
          // call. Replace them one by one instead, which is slightly slower
          // but ensures that the number of replacements do not grow
          // exponentialy.
          foreach ($field_tokens as $field_token_key => $field_token_value) {
            $replacements += \Drupal::token()
              ->generate($field_name, [
              $field_token_key => $field_token_value,
            ], $property_token_data, $options, $bubbleable_metadata);
          }
        }
      }
    }

    // Remove the cloned object from memory.
    unset($entity);
  }
  elseif (!empty($data['field_property'])) {
    foreach ($tokens as $token => $original) {
      $filtered_tokens = $tokens;
      $delta = 0;
      $parts = explode(':', $token);
      if (is_numeric($parts[0])) {
        if (count($parts) > 1) {
          $delta = $parts[0];
          $property_name = $parts[1];

          // Pre-filter the tokens to select those with the correct delta.
          $filtered_tokens = \Drupal::token()
            ->findWithPrefix($tokens, $delta);

          // Remove the delta to unify between having and not having one.
          array_shift($parts);
        }
        else {

          // Token is fieldname:delta, which is invalid.
          continue;
        }
      }
      else {
        $property_name = $parts[0];
      }
      if (isset($data[$data['field_name']][$delta])) {
        $field_item = $data[$data['field_name']][$delta];
      }
      else {

        // The field has no such delta, abort replacement.
        continue;
      }
      if (isset($field_item->{$property_name}) && $field_item->{$property_name} instanceof FieldableEntityInterface) {

        // Entity reference field.
        $entity = $field_item->{$property_name};

        // Obtain the referenced entity with the correct language.
        $entity = \Drupal::service('entity.repository')
          ->getTranslationFromContext($entity, $langcode);
        if (count($parts) > 1) {
          $field_tokens = \Drupal::token()
            ->findWithPrefix($filtered_tokens, $property_name);
          $token_type = \Drupal::service('token.entity_mapper')
            ->getTokenTypeForEntityType($entity
            ->getEntityTypeId(), TRUE);
          $replacements += \Drupal::token()
            ->generate($token_type, $field_tokens, [
            $token_type => $entity,
          ], $options, $bubbleable_metadata);
        }
        else {
          $replacements[$original] = $entity
            ->label();
        }
      }
      elseif ($field_item
        ->getFieldDefinition()
        ->getType() == 'image' && ($style = ImageStyle::load($property_name))) {

        // Handle [node:field_name:image_style:property] tokens and multivalued
        // [node:field_name:delta:image_style:property] tokens. If the token is
        // of the form [node:field_name:image_style], provide the URL as a
        // replacement.
        $property_name = isset($parts[1]) ? $parts[1] : 'url';
        $entity = $field_item->entity;
        if (!empty($field_item->entity)) {
          $original_uri = $entity
            ->getFileUri();

          // Only generate the image derivative if needed.
          if ($property_name === 'width' || $property_name === 'height') {
            $dimensions = [
              'width' => $field_item->width,
              'height' => $field_item->height,
            ];
            $style
              ->transformDimensions($dimensions, $original_uri);
            $replacements[$original] = $dimensions[$property_name];
          }
          elseif ($property_name === 'uri') {
            $replacements[$original] = $style
              ->buildUri($original_uri);
          }
          elseif ($property_name === 'url') {
            $replacements[$original] = $style
              ->buildUrl($original_uri);
          }
          else {

            // Generate the image derivative, if it doesn't already exist.
            $derivative_uri = $style
              ->buildUri($original_uri);
            $derivative_exists = TRUE;
            if (!file_exists($derivative_uri)) {
              $derivative_exists = $style
                ->createDerivative($original_uri, $derivative_uri);
            }
            if ($derivative_exists) {
              $image = \Drupal::service('image.factory')
                ->get($derivative_uri);

              // Provide the replacement.
              switch ($property_name) {
                case 'mimetype':
                  $replacements[$original] = $image
                    ->getMimeType();
                  break;
                case 'filesize':
                  $replacements[$original] = $image
                    ->getFileSize();
                  break;
              }
            }
          }
        }
      }
      elseif (in_array($field_item
        ->getFieldDefinition()
        ->getType(), [
        'datetime',
        'daterange',
        'date_recur',
      ]) && in_array($property_name, [
        'date',
        'start_date',
        'end_date',
      ]) && !empty($field_item->{$property_name})) {
        $timestamp = $field_item->{$property_name}
          ->getTimestamp();

        // If the token is an exact match for the property or the delta and the
        // property, use the timestamp as-is.
        if ($property_name == $token || "{$delta}:{$property_name}" == $token) {
          $replacements[$original] = $timestamp;
        }
        else {
          $date_tokens = \Drupal::token()
            ->findWithPrefix($filtered_tokens, $property_name);
          $replacements += \Drupal::token()
            ->generate('date', $date_tokens, [
            'date' => $timestamp,
          ], $options, $bubbleable_metadata);
        }
      }
      elseif (in_array($field_item
        ->getFieldDefinition()
        ->getType(), [
        'timestamp',
        'created',
        'changed',
      ]) && in_array($property_name, [
        'date',
      ])) {
        $timestamp = $field_item->value;
        if ($property_name == $token || "{$delta}:{$property_name}" == $token) {
          $replacements[$original] = $timestamp;
        }
        else {
          $field_tokens = \Drupal::token()
            ->findWithPrefix($filtered_tokens, $property_name);
          $replacements += \Drupal::token()
            ->generate('date', $field_tokens, [
            'date' => $timestamp,
          ], $options, $bubbleable_metadata);
        }
      }
      else {
        $replacements[$original] = $field_item->{$property_name};
      }
    }
  }
  return $replacements;
}