Skip to content
field.attach.inc 13.7 KiB
Newer Older
 * Field attach API, allowing entities (nodes, users, ...) to be 'fieldable'.
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\Entity\EntityFormDisplay;
/**
 * @defgroup field_attach Field Attach API
 * @{
 * Operates on Field API data attached to Drupal entities.
 * Field Attach API functions load, store, display, generate Field API
 * structures, and perform a variety of other functions for field data attached
 * to individual entities.
 *
 * Field Attach API functions generally take $entity_type and $entity arguments
 * along with additional function-specific arguments. $entity_type is the type
 * of the fieldable entity, such as 'node' or 'user', and $entity is the entity
 * itself.
 *
 * An entity plugin's annotation is how entity types define if and how
 * Field API should operate on their entity objects. Notably, the 'fieldable'
 * property needs to be set to TRUE.
 *
 * The Field Attach API uses the concept of bundles: the set of fields for a
 * given entity is defined on a per-bundle basis. The collection of bundles for
 * an entity type is added to the entity definition with
 * hook_entity_info_alter(). For instance, node_entity_info_alter() exposes
 * each node type as its own bundle. This means that the set of fields of a
 * node is determined by the node type.
 *
 * The Field API reads the bundle name for a given entity from a particular
 * property of the entity object, and hook_entity_info_alter() defines which
 * property to use. For instance, node_entity_info_alter() specifies:
 * @code
 *   $info['entity_keys']['bundle'] = 'type'
 * @endcode
 * This indicates that for a particular node object, the bundle name can be
 * found in $node->type. This property can be omitted if the entity type only
 * exposes a single bundle (all entities of this type have the same collection
 * of fields). This is the case for the 'user' entity type.
 *
 * Most Field Attach API functions define a corresponding hook function that
 * allows any module to act on Field Attach operations for any entity after the
 * operation is complete, and access or modify all the field, form, or display
 * data for that entity and operation. For example, field_attach_view() invokes
 * hook_field_attach_view_alter().
 * @link field_language Field language API @endlink provides information about
 * the structure of field objects.
 *
 * See @link field Field API @endlink for information about the other parts of
 * the Field API.
 * Invokes a method on all the fields of a given entity.
 *
 * @param string $method
 *   The name of the method to invoke.
 * @param callable $target_function
 *   A function that receives an $instance object and returns the object on
 *   which the method should be invoked.
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The fully formed $entity_type entity.
 * @param mixed $a
 *   (optional) A parameter for the invoked method. Defaults to NULL.
 *   (optional) A parameter for the invoked method. Defaults to NULL.
 *   (optional) An associative array of additional options, with the following
 *   keys:
 *   - field_name: The name of the field whose operation should be invoked. By
 *     default, the operation is invoked on all the fields in the entity's
 *     bundle. NOTE: This option is not compatible with the 'deleted' option;
 *     the 'field_id' option should be used instead.
 *   - field_id: The ID of the field whose operation should be invoked. By
 *     default, the operation is invoked on all the fields in the entity's'
 *     bundles.
 *   - deleted: If TRUE, the function will operate on deleted fields as well
 *     as non-deleted fields. If unset or FALSE, only non-deleted fields are
 *     operated on.
 *   - langcode: A language code or an array of language codes keyed by field
 *     name. It will be used to narrow down to a single value the available
 *     languages to act on.
 *
 * @return array
 *   An array of returned values.
function field_invoke_method($method, $target_function, EntityInterface $entity, &$a = NULL, &$b = NULL, array $options = array()) {
  // Merge default options.
  $default_options = array(
    'deleted' => FALSE,
    'langcode' => NULL,
  );
  $options += $default_options;

  $entity_type = $entity->entityType();
  // Determine the list of instances to iterate on.
  $instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options);

  // Iterate through the instances and collect results.
  $return = array();
  foreach ($instances as $instance) {

    // Let the function determine the target object on which the method should be
    $target = call_user_func($target_function, $instance);
      $field = $instance->getField();

      // Determine the list of languages to iterate on.
      $available_langcodes = field_available_languages($entity_type, $field);
      $langcodes = _field_language_suggestion($available_langcodes, $options['langcode'], $field_name);

      foreach ($langcodes as $langcode) {
        if ($entity->hasTranslation($langcode)) {
          $items = $entity->getTranslation($langcode)->get($field_name);
          $items->filterEmptyValues();
          $result = $target->$method($items, $a, $b);

          if (isset($result)) {
            // For methods with array results, we merge results together.
            // For methods with scalar results, we collect results in an array.
            if (is_array($result)) {
              $return = array_merge($return, $result);
            }
            else {
              $return[] = $result;
            }
/**
 * Invokes a method across fields on multiple entities.
 *
 * @param string $method
 *   The name of the method to invoke.
 * @param callable $target_function
 *   A function that receives an $instance object and returns the object on
 *   which the method should be invoked.
 * @param array $entities
 *   An array of entities, keyed by entity ID.
 * @param mixed $a
 *   (optional) A parameter for the invoked method. Defaults to NULL.
 * @param mixed $b
 *   (optional) A parameter for the invoked method. Defaults to NULL.
 * @param $options
 *   (optional) An associative array of additional options, with the following
 *   keys:
 *   - field_name: The name of the field whose operation should be invoked. By
 *     default, the operation is invoked on all the fields in the entity's
 *     bundle. NOTE: This option is not compatible with the 'deleted' option;
 *     the 'field_id' option should be used instead.
 *   - field_id: The ID of the field whose operation should be invoked. By
 *     default, the operation is invoked on all the fields in the entity's'
 *     bundles.
 *   - deleted: If TRUE, the function will operate on deleted fields as well
 *     as non-deleted fields. If unset or FALSE, only non-deleted fields are
 *     operated on.
 *   - langcode: A language code or an array of language codes keyed by field
 *     name. It will be used to narrow down to a single value the available
 *     languages to act on.
 *
 * @return array
 *   An array of returned values keyed by entity ID.
 *
 * @see field_invoke_method()
 */
function field_invoke_method_multiple($method, $target_function, array $entities, &$a = NULL, &$b = NULL, array $options = array()) {
  // Merge default options.
  $default_options = array(
    'deleted' => FALSE,
    'langcode' => NULL,
  );
  $options += $default_options;

  $instances = array();
  $grouped_items = array();
  $grouped_targets = array();
  $return = array();

  // Go through the entities and collect the instances on which the method
  // should be called.
  foreach ($entities as $entity) {
    $id = $entity->id();
    $entity_type = $entity->entityType();

    // Determine the list of instances to iterate on.
    $entity_instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options);

    foreach ($entity_instances as $instance) {
      $instance_uuid = $instance->uuid();
      $field_name = $instance->getFieldName();

      // Let the closure determine the target object on which the method should
      // be called.
      if (empty($grouped_targets[$instance_uuid])) {
        $grouped_targets[$instance_uuid] = call_user_func($target_function, $instance);
      if (method_exists($grouped_targets[$instance_uuid], $method)) {
        // Add the instance to the list of instances to invoke the hook on.
        if (!isset($instances[$instance_uuid])) {
          $instances[$instance_uuid] = $instance;
        }

        // Unless a language code suggestion is provided we iterate on all the
        // available language codes.
        $field = $instance->getField();
        $available_langcodes = field_available_languages($entity_type, $field);
        $langcode = !empty($options['langcode'][$id]) ? $options['langcode'][$id] : $options['langcode'];
        $langcodes = _field_language_suggestion($available_langcodes, $langcode, $field_name);
        foreach ($langcodes as $langcode) {
          if ($entity->hasTranslation($langcode)) {
            // Group the items corresponding to the current field.
            $items = $entity->getTranslation($langcode)->get($field_name);
            $items->filterEmptyValues();
            $grouped_items[$instance_uuid][$langcode][$id] = $items;
          }
        }
      }
    }
    // Initialize the return value for each entity.
    $return[$id] = array();
  }

  // For each instance, invoke the method and collect results.
  foreach ($instances as $instance_uuid => $instance) {
    // Iterate over all the field translations.
    foreach ($grouped_items[$instance_uuid] as $items) {
      $results = $grouped_targets[$instance_uuid]->$method($items, $a, $b);

      if (isset($results)) {
        // Collect results by entity.
        // For hooks with array results, we merge results together.
        // For hooks with scalar results, we collect results in an array.
        foreach ($results as $id => $result) {
          if (is_array($result)) {
            $return[$id] = array_merge($return[$id], $result);
          }
          else {
            $return[$id][] = $result;
          }
        }
      }
    }
  }

  return $return;
}

 *
 * @param $entity_type
 *   The entity type.
 * @param $bundle
 *   The bundle name.
 * @param $options
 *   An associative array of options, as provided to field_invoke_method(). Only
 *   the following keys are considered:
 *   - deleted
 *   - field_name
 *   - field_id
 *   See field_invoke_method() for details.
 *
 * @return
 *   The array of selected instance definitions.
 */
function _field_invoke_get_instances($entity_type, $bundle, $options) {
  if ($options['deleted']) {
    // Deleted fields are not included in field_info_instances(), and need to
    // be fetched from the database with field_read_instances().
    $params = array('entity_type' => $entity_type, 'bundle' => $bundle);
    if (isset($options['field_id'])) {
      // Single-field mode by field id: field_read_instances() does the filtering.
      // Single-field mode by field name is not compatible with the 'deleted'
      // option.
      $params['field_id'] = $options['field_id'];
    }
    $instances = field_read_instances($params, array('include_deleted' => TRUE));
  }
  elseif (isset($options['field_name'])) {
    // Single-field mode by field name: field_info_instance() does the
    // filtering.
    $instances = array(field_info_instance($entity_type, $options['field_name'], $bundle));
  }
  else {
    $instances = field_info_instances($entity_type, $bundle);
    if (isset($options['field_id'])) {
      // Single-field mode by field id: we need to loop on each instance to
      // find the right one.
      foreach ($instances as $instance) {
        if ($instance->getField()->uuid() == $options['field_id']) {
          $instances = array($instance);
          break;
        }
      }
    }
  }

  return $instances;
}

 * Defines a 'target function' for field_invoke_method().
 *
 * Used to invoke methods on an instance's widget.
 *
 * @param \Drupal\entity\Entity\EntityFormDisplay $form_display
 * @return callable $target_function
 *   A 'target function' for field_invoke_method().
function _field_invoke_widget_target($form_display) {
  return function ($instance) use ($form_display) {
    return $form_display->getRenderer($instance->getFieldName());
 * Populates the template variables with the available field values.
 *
 * The $variables array will be populated with all the field instance values
 * associated with the given entity type, keyed by field name; in case of
 * translatable fields the language currently chosen for display will be
 * selected.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity with fields to render.
 * @param $element
 *   The structured array containing the values ready for rendering.
 * @param $variables
 *   The variables array is passed by reference and will be populated with field
 *   values.
 */
function field_attach_preprocess(EntityInterface $entity, $element, &$variables) {
  foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $field_name => $instance) {
    if (isset($element[$field_name]['#language'])) {
      $langcode = $element[$field_name]['#language'];
      $variables[$field_name] = $entity->getTranslation($langcode)->{$field_name}->getValue();
  // Let other modules make changes to the $variables array.
    'element' => $element,
  );
  drupal_alter('field_attach_preprocess', $variables, $context);
 * @} End of "defgroup field_attach".