You are here

field_tools.module in Field tools 7

Same filename and directory in other branches
  1. 8 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($path, $arg) {

  // system_modules() form passes us in an array of nothing but empty strings
  // which we don't want to get tripped up by.
  $args_filtered = array_filter($arg);
  if (!$args_filtered) {
    return;
  }

  // WTF: the $arg array is 12 items long with empty cruft at the end!?!
  $args_reverse = array_reverse($args_filtered);

  // Provide help for any path that ends in 'fields/%/clone'.
  if ($args_reverse[0] == 'clone' && $args_reverse[2] == 'fields') {
    return t('Apply this existing field to the other bundles by copying the current field instance.');
  }

  // Provide help for the bundle tools page.
  // Check the last path item first, so we don't perform all the bundle checking
  // on every page.
  if ($args_reverse[0] == 'tools') {

    // Check that this in indeed on a bundle admin path, rather than just any
    // old path ending in 'tools'.
    $potential_bundle_path = implode('/', array_slice($args_filtered, 0, -1));
    foreach (entity_get_info() as $entity_type => $entity_info) {
      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
        if (isset($bundle_info['admin']['real path']) && $bundle_info['admin']['real path'] == $potential_bundle_path) {

          // We're under the admin path for this bundle.
          return t("Apply some or all fields of the %bundle bundle to other bundles by copying the field instances.", array(
            '%bundle' => $bundle_info['label'],
          ));
        }
      }
    }
  }

  // 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 to this bundle. You can only select one instance of a particular field.");
  }
}

/**
 * Implements hook_menu().
 */
function field_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;

          // Menu items for import and export of fields.
          $items["{$path}/tools/export"] = array(
            'title' => 'Export fields',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'field_tools_bundle_export_form',
              $entity_type,
              $bundle_arg,
            ),
            'type' => MENU_LOCAL_TASK,
            'file' => 'field_tools.admin.inc',
            'weight' => 30,
          ) + $access;
          $items["{$path}/tools/import"] = array(
            'title' => 'Import fields',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'field_tools_bundle_import_form',
              $entity_type,
              $bundle_arg,
            ),
            'type' => MENU_LOCAL_TASK,
            'file' => 'field_tools.admin.inc',
            'weight' => 40,
          ) + $access;

          // Menu item for exporting a single field.
          $items["{$path}/fields/%field_ui_menu/export"] = array(
            'load arguments' => array(
              $entity_type,
              $bundle_arg,
              $bundle_pos,
              '%map',
            ),
            'title' => 'Export',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'field_tools_field_export_form',
              $field_position,
            ),
            'type' => MENU_LOCAL_TASK,
            'file' => 'field_tools.admin.inc',
            'weight' => 1,
          ) + $access;
          if (module_exists('field_group')) {
            $items["{$path}/groups/%field_group_menu/export"] = array(
              'load arguments' => array(
                $entity_type,
                $bundle_arg,
                $bundle_pos,
                '%map',
              ),
              'title' => 'Export',
              'page callback' => 'drupal_get_form',
              'page arguments' => array(
                'field_tools_group_export_form',
                $field_position,
              ),
              'type' => MENU_CALLBACK,
              'file' => 'field_tools.admin.inc',
              'weight' => 1,
            ) + $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 field_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 field_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 _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 _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 field_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';
}

/**
 * Form after_build callback to add an export link to a fields table.
 */
function field_tools_alter_fields_ui_table($form, $form_state) {

  // Figure out the key for the operations column in the table header, as other
  // modules may be adding more columns too.
  $header = $form['fields']['#header'];
  foreach ($header as $key => $header_cell) {
    if (is_array($header_cell) && $header_cell['data'] == t('Operations')) {
      break;
    }
  }

  // Increate the colspan of the 'operations' column.
  $form['fields']['#header'][$key]['colspan']++;

  // For each field, add an 'export' operation link after the 'delete' link.
  foreach (element_children($form['fields']) as $field_name) {
    if (!in_array($form['fields'][$field_name]['#row_type'], array(
      'field',
      'group',
    ))) {
      $export_cell = array(
        '#markup' => NULL,
      );
      $insert_array = array(
        'export' => $export_cell,
      );
      field_tools_array_insert($form['fields'][$field_name], 'delete', $insert_array);
      continue;
    }
    $bundle = $form['#bundle'];
    $entity_type = $form['#entity_type'];
    $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle);
    switch ($form['fields'][$field_name]['#row_type']) {
      case 'field':
        if (!isset($form['fields'][$field_name]['edit']) || isset($form['fields'][$field_name]['edit']['#value']) && $form['fields'][$field_name]['edit']['#value'] == t('Locked')) {
          $export_cell = array(
            '#markup' => NULL,
          );
          $insert_array = array(
            'export' => $export_cell,
          );
          field_tools_array_insert($form['fields'][$field_name], 'delete', $insert_array);
          continue 2;
        }
        $instance = field_info_instance($entity_type, $field_name, $bundle);
        $admin_field_path = $admin_path . '/fields/' . $instance['field_name'];
        $export_cell = array(
          '#type' => 'link',
          '#title' => t('export'),
          '#href' => $admin_field_path . '/export',
          '#options' => array(
            'attributes' => array(
              'title' => t('Export instance'),
            ),
          ),
        );
        $insert_array = array(
          'export' => $export_cell,
        );
        field_tools_array_insert($form['fields'][$field_name], 'delete', $insert_array);
        break;
      case 'group':
        $admin_field_path = $admin_path . '/groups/' . $field_name;
        $export_cell = array(
          '#type' => 'link',
          '#title' => t('export'),
          '#href' => $admin_field_path . '/export/form',
          '#options' => array(
            'attributes' => array(
              'title' => t('Export group'),
            ),
          ),
        );
        $insert_array = array(
          'export' => $export_cell,
        );
        field_tools_array_insert($form['fields'][$field_name], 'delete', $insert_array);
        break;
    }
  }
  return $form;
}

/**
 * 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 field_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
field_tools_alter_fields_ui_table Form after_build callback to add an export link to a fields table.
field_tools_array_insert Inserts values into an array after a given key.
field_tools_field_menu_load Menu loader for a field.
field_tools_form_field_ui_field_overview_form_alter Implements hook_form_FORM_ID_alter(): field_ui_field_overview_form
field_tools_help Implements hook_help().
field_tools_info_instances Helper to get a list of all instances of a field.
field_tools_menu Implements hook_menu().
_field_tools_entity_can_attach_field Helper function to determine whether a field can be attached an entity type.
_field_tools_field_already_attached Helper function to tell if a field is already attached to a bundle.