You are here

field_tools.module in Field tools 8

Same filename and directory in other branches
  1. 7 field_tools.module

field_tools.module Contains useful tools for working with fields.

File

field_tools.module
View source
<?php

/**
 * @file field_tools.module
 * Contains useful tools for working with fields.
 */

/**
 * Implements hook_help().
 */
function field_tools_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match) {
  switch ($route_name) {

    // Main module help.
    case 'help.page.field_tools':
      return t("Field Tools provides additional UI admin tools for working with fields.");
  }

  // Clone form.
  if (substr($route_name, -strlen('_field_tools_clone_form')) == '_field_tools_clone_form') {
    return t('Apply this field to the other bundles by copying the current field.') . ' ' . t('Form and view display options will be copied to displays on the destination bundles where display mode names are the same as those on the source.');
  }

  // Bulk field clone form.
  if (substr($route_name, 0, strlen('field_tools.field_bulk_clone_')) == 'field_tools.field_bulk_clone_') {
    return t('Clone fields from this bundle to other bundles. Form and view display options will be copied to displays on the destination bundles where display mode names are the same as those on the source.');
  }

  // Bulk display clone form.
  if (substr($route_name, 0, strlen('field_tools.displays_clone_')) == 'field_tools.displays_clone_') {
    return t('Clone form and view displays from this bundle to other bundles. Field settings on the target are overwritten, except for fields only on the target, which are ignored.');
  }

  // Bulk field settings copy form.
  if (substr($route_name, 0, strlen('field_tools.displays_settings_copy_')) == 'field_tools.displays_settings_copy_') {
    return t("Copy settings for individual fields from this bundle's form and view displays to the displays with the same names other bundles.");
  }

  // TODO: update what follows for 8.x once the functionality is updated.
  return;

  // Provide help any path that ends in 'tools/clone-to'.
  if (count($args_reverse) > 1 && $args_reverse[1] == 'tools' && $args_reverse[0] == 'clone-to') {
    return t("Select the fields you want to copy this bundle. You can only select one instance of a particular field.");
  }
}

/**
 * Implements hook_entity_type_build().
 */
function field_tools_entity_type_build(array &$entity_types) {

  // Add the clone form to field config entities.
  $entity_types['field_config']
    ->setFormClass('clone', 'Drupal\\field_tools\\Form\\FieldConfigCloneForm');

  // We can't add link templates to field entities for things such as the clone
  // form, as then the route can't be dynamic based on the target entity type.
  // Core Field module does this by providing the link template on the fly in
  // FieldConfig::linkTemplates(), but we can't hack into that.
  // Add a delete form and link to field storage config.
  $entity_types['field_storage_config']
    ->setFormClass('delete', Drupal\field_tools\Form\FieldStorageConfigDeleteForm::class);
  $entity_types['field_storage_config']
    ->setLinkTemplate('delete-form', '/admin/reports/fields/tools/{field_storage_config}/delete');
  $entity_types['field_storage_config']
    ->setHandlerClass('access', Drupal\field_tools\EntityHandler\FieldStorageConfigAccess::class);

  // @todo Core forgot to add a direct way to manipulate route_provider, so
  // we have to do it the sloppy way for now.
  $route_providers = $entity_types['field_storage_config']
    ->getRouteProviderClasses() ?: [];
  if (empty($route_providers['field_tools'])) {

    // This will only provide the delete route, as all the other link templates
    // aren't defined.
    $route_providers['field_tools'] = \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider::class;
    $entity_types['field_storage_config']
      ->setHandlerClass('route_provider', $route_providers);
  }
}

/**
 * Implements hook_entity_operation().
 */
function field_tools_entity_operation(\Drupal\Core\Entity\EntityInterface $entity) {
  $operations = [];
  if ($entity
    ->getEntityTypeId() == 'field_config') {
    $target_entity_type_id = $entity
      ->getTargetEntityTypeId();
    $parameters = [
      'field_config' => $entity
        ->id(),
    ];

    // Because we're not going via entity URIs but direct to the route, we
    // don't get the handling from FieldConfig::urlRouteParameters(), so
    // need to do it ourselves.
    $entity_type = \Drupal::entityTypeManager()
      ->getDefinition($target_entity_type_id);
    $bundle_parameter_key = $entity_type
      ->getBundleEntityType() ?: 'bundle';
    $parameters[$bundle_parameter_key] = $entity
      ->getTargetBundle();

    // Field clone operation.
    $operations['clone'] = array(
      'title' => t('Clone'),
      'url' => \Drupal\Core\Url::fromRoute("entity.field_config.{$target_entity_type_id}_field_tools_clone_form", $parameters),
      'weight' => 50,
    );
  }

  // Add operation links to bundle entity lists for the tools tabs.
  $bundle_of_entity_type_id = $entity
    ->getEntityType()
    ->getBundleOf();
  if ($bundle_of_entity_type_id) {
    $bundle_of_entity_type = \Drupal::entityTypeManager()
      ->getDefinition($bundle_of_entity_type_id);
    if ($bundle_of_entity_type
      ->get('field_ui_base_route')) {
      $entity_type_id = $entity
        ->getEntityTypeId();
      $parameters = [
        $entity_type_id => $entity
          ->id(),
      ];
      $operations['clone_fields'] = array(
        'title' => t('Clone fields'),
        'url' => \Drupal\Core\Url::fromRoute("field_tools.field_bulk_clone_{$bundle_of_entity_type_id}", $parameters),
        'weight' => 50,
      );
    }
  }
  return $operations;
}

/**
 * Implements hook_menu().
 */
function D7field_tools_menu() {

  // Create tabs for all possible bundles.
  foreach (entity_get_info() as $entity_type => $entity_info) {
    if ($entity_info['fieldable']) {
      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
        if (isset($bundle_info['admin'])) {

          // Extract path information from the bundle.
          $path = $bundle_info['admin']['path'];

          // Determine whether this path caters for several bundles (usually all)
          // of one entity type, or just one.
          if (isset($bundle_info['admin']['bundle argument'])) {

            // Different bundles can appear on the same path (e.g. %node_type and
            // %comment_node_type). To allow field_ui_menu_load() to extract the
            // actual bundle object from the translated menu router path
            // arguments, we need to identify the argument position of the bundle
            // name string ('bundle argument') and pass that position to the menu
            // loader. The position needs to be casted into a string; otherwise it
            // would be replaced with the bundle name string.
            $bundle_arg = $bundle_info['admin']['bundle argument'];
            $bundle_pos = (string) $bundle_arg;
          }
          else {

            // Otherwise, this path is for a single bundle. Things are much simpler!
            $bundle_arg = $bundle_name;
            $bundle_pos = '0';
          }

          // This is the position of the %field_ui_menu placeholder in the
          // items below.
          $field_position = count(explode('/', $path)) + 1;

          // Extract access information, providing defaults.
          $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array(
            'access callback',
            'access arguments',
          )));
          $access += array(
            'access callback' => 'user_access',
            'access arguments' => array(
              'administer site configuration',
            ),
          );

          // Menu item for cloning a bundle's fields en masse.
          // In some cases, $path is the same for every bundle, eg nodes, and
          // hence doing this here is redundant.
          $items["{$path}/tools"] = array(
            'title' => 'Tools',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'field_tools_bundle_fields_clone_from_form',
              $entity_type,
              $bundle_arg,
            ),
            'type' => MENU_LOCAL_TASK,
            'file' => 'field_tools.admin.inc',
            'weight' => 10,
          ) + $access;

          // Tweak comment tools to differentiate them from node tools as they
          // sit in the same sets of tabs.
          if ($entity_type == 'comment') {
            $items["{$path}/tools"]['title'] = 'Comment tools';
            $items["{$path}/tools"]['weight'] = 11;
          }
          $items["{$path}/tools/clone-from"] = array(
            'title' => 'Clone fields from this bundle',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'field_tools_bundle_fields_clone_from_form',
              $entity_type,
              $bundle_arg,
            ),
            'type' => MENU_DEFAULT_LOCAL_TASK,
            'file' => 'field_tools.admin.inc',
            'weight' => 10,
          ) + $access;
          $items["{$path}/tools/clone-to"] = array(
            'title' => 'Clone fields to this bundle',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'field_tools_bundle_fields_clone_to_form',
              $entity_type,
              $bundle_arg,
            ),
            'type' => MENU_LOCAL_TASK,
            'file' => 'field_tools.admin.inc',
            'weight' => 20,
          ) + $access;

          // Menu item for cloning a single field.
          $items["{$path}/fields/%field_ui_menu/clone"] = array(
            'load arguments' => array(
              $entity_type,
              $bundle_arg,
              $bundle_pos,
              '%map',
            ),
            'title' => 'Clone',
            'page callback' => 'drupal_get_form',
            // The numeric $field_position gets us the field instance from the
            // %field_ui_menu menu loader.
            'page arguments' => array(
              'field_tools_field_clone_form',
              $field_position,
            ),
            'type' => MENU_LOCAL_TASK,
            'file' => 'field_tools.admin.inc',
            'weight' => 10,
          ) + $access;
        }
      }
    }
  }

  // Field tools overview.
  $items["admin/reports/fields/tools"] = array(
    'title' => 'Tools',
    'page callback' => 'field_tools_field_overview',
    'type' => MENU_LOCAL_TASK,
    'weight' => -1,
    'file' => 'field_tools.admin.inc',
  ) + $access;
  $items["admin/reports/fields/references"] = array(
    'title' => 'References',
    'page callback' => 'field_tools_field_references_overview',
    'type' => MENU_LOCAL_TASK,
    'weight' => 5,
    'file' => 'field_tools.admin.inc',
  ) + $access;
  if (module_exists('graphapi')) {
    $items["admin/reports/fields/graph"] = array(
      'title' => 'Graph',
      'page callback' => 'field_tools_field_references_graph',
      'type' => MENU_LOCAL_TASK,
      'weight' => 6,
      'file' => 'field_tools.admin.inc',
    ) + $access;
  }
  $items["admin/reports/fields/field/%"] = array(
    'title' => 'Field instances',
    'page callback' => 'field_tools_field_page',
    'page arguments' => array(
      4,
    ),
    'file' => 'field_tools.admin.inc',
  ) + $access;
  $items["admin/reports/fields/field/%field_tools_field_menu/edit"] = array(
    'title' => 'Edit instance',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'field_tools_field_edit_form',
      4,
    ),
    'file' => 'field_tools.admin.inc',
  ) + $access;
  $items["admin/reports/fields/field/%field_tools_field_menu/delete"] = array(
    'title' => 'Delete instances',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'field_tools_field_delete_form',
      4,
    ),
    'file' => 'field_tools.admin.inc',
  ) + $access;
  return $items;
}

/**
 * Menu loader for a field.
 *
 * @param $field_name
 *  The machine name of a field.
 */
function D7field_tools_field_menu_load($field_name) {
  if ($field = field_info_field($field_name)) {
    return $field;
  }
  return FALSE;
}

/**
 * Helper to get a list of all instances of a field.
 *
 * @param $field_name
 *  The name of a field.
 *
 *
 * @return
 *  An array of entity types and bundles this field has instances on. Keys are
 *  entity types, values are arrays of bundle names.
 */
function D7field_tools_info_instances($field_name) {
  $instances = field_info_instances();
  $field_bundles = array();
  foreach ($instances as $entity_type => $bundles) {
    foreach ($bundles as $bundle => $bundle_instances) {
      if (isset($bundle_instances[$field_name])) {
        $field_bundles[$entity_type][] = $bundle;
      }
    }
  }
  return $field_bundles;
}

/**
 * Helper function to determine whether a field can be attached an entity type.
 *
 * Certain fields like comment_body are restricted to certain entity types.
 *
 * @param string $entity_type
 *  The entity type to check.
 * @param array $field_info
 *  The field info to check.
 *
 * @return boolean
 *  Whether the field can be attached to the entity type.
 */
function D7_field_tools_entity_can_attach_field($entity_type, $field_info) {
  return !(!empty($field_info['entity_types']) && !in_array($entity_type, $field_info['entity_types']));
}

/**
 * Helper function to tell if a field is already attached to a bundle.
 *
 * @param string $entity_type
 *   The entity type to check.
 * @param string $bundle_type
 *   The bundle type to check.
 * @param array $field_info
 *   The field info array for the field to check.
 *
 * @return boolean
 *   Whether the field is already attached to this bundle.
 */
function D7_field_tools_field_already_attached($entity_type, $bundle_type, $field_info) {
  return array_key_exists($entity_type, $field_info['bundles']) && in_array($bundle_type, $field_info['bundles'][$entity_type]);
}

/**
 * Implements hook_form_FORM_ID_alter(): field_ui_field_overview_form
 */
function D7field_tools_form_field_ui_field_overview_form_alter(&$form, $form_state) {

  // make sure we have an array to add our after_build callback function to
  if (!isset($form['#after_build']) || !is_array($form['#after_build'])) {
    $form['#after_build'] = array();
  }
  $form['#after_build'][] = 'field_tools_alter_fields_ui_table';
}

/**
 * Inserts values into an array after a given key.
 *
 * Values from $insert_array are inserted after (or before) $key in $array. If
 * $key is not found, $insert_array is appended to $array using array_merge().
 *
 * From https://drupal.org/node/66183.
 *
 * @param $array
 *   The array to insert into. Passed by reference and altered in place.
 * @param $key
 *   The key of $array to insert after
 * @param $insert_array
 *   An array whose values should be inserted.
 * @param $before
 *   If TRUE, insert before the given key, rather than after it.
 *   Defaults to inserting after.
*/
function D7field_tools_array_insert(&$array, $key, $insert_array, $before = FALSE) {
  $done = FALSE;
  foreach ($array as $array_key => $array_val) {
    if (!$before) {
      $new_array[$array_key] = $array_val;
    }
    if ($array_key == $key && !$done) {
      foreach ($insert_array as $insert_array_key => $insert_array_val) {
        $new_array[$insert_array_key] = $insert_array_val;
      }
      $done = TRUE;
    }
    if ($before) {
      $new_array[$array_key] = $array_val;
    }
  }
  if (!$done) {
    $new_array = array_merge($array, $insert_array);
  }

  // Put the new array in the place of the original.
  $array = $new_array;
}

Functions

Namesort descending Description
D7field_tools_array_insert Inserts values into an array after a given key.
D7field_tools_field_menu_load Menu loader for a field.
D7field_tools_form_field_ui_field_overview_form_alter Implements hook_form_FORM_ID_alter(): field_ui_field_overview_form
D7field_tools_info_instances Helper to get a list of all instances of a field.
D7field_tools_menu Implements hook_menu().
D7_field_tools_entity_can_attach_field Helper function to determine whether a field can be attached an entity type.
D7_field_tools_field_already_attached Helper function to tell if a field is already attached to a bundle.
field_tools_entity_operation Implements hook_entity_operation().
field_tools_entity_type_build Implements hook_entity_type_build().
field_tools_help Implements hook_help().