You are here

auto_entitylabel.module in Automatic Entity Label 7

Allows hiding of entity label fields and automatic label creation.

File

auto_entitylabel.module
View source
<?php

/**
 * @file
 * Allows hiding of entity label fields and automatic label creation.
 */
define('AUTO_ENTITYLABEL_DISABLED', 0);
define('AUTO_ENTITYLABEL_ENABLED', 1);
define('AUTO_ENTITYLABEL_OPTIONAL', 2);
define('AUTO_ENTITYLABEL_PLACEHOLDER', '%AutoEntityLabel%');

/**
 * Implements hook_help().
 */
function auto_entitylabel_help($path) {
  switch ($path) {
    case 'admin/help#auto_entitylabel':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('This is a small and efficient module that allows hiding of entity label fields. To prevent empty labels it can be configured to generate the label automatically by a given pattern. The module can be used for any entity type that has a label, including e.g. for node titles, comment subjects, taxonomy term names and profile2 labels.') . '</p>';
      $output .= '<p>' . t('Patterns for automatic labels are constructed with the help of tokens. Drupal core provides a basic set of <a href="@url_tokens">tokens</a>. For a token selection widget install the token. Some entity types (e.g. profile2) provide tokens via the entity_token, which is part of the entity module.', array(
        '@url_tokens' => 'https://www.drupal.org/project/token',
      )) . '</p>';
      $output .= '<p>' . t('Watch the <a href="@url_daily_dose_of_drupal" target="blank">Daily Dose of Drupal</a> screencast by <a href="@url_shane_thomas" target="blank">Shane Thomas</a> for a short introduction and demonstration of the module and some of its features.', array(
        '@url_daily_dose_of_drupal' => 'http://codekarate.com/daily-dose-of-drupal/drupal-7-automatic-entity-label-module',
        '@url_shane_thomas' => 'https://www.drupal.org/user/506260',
      )) . '</p>';
      $output .= '<h3>' . t('Usage') . '</h3>';
      $output .= '<p>' . t('The configuration can be accessed in <i>Structure</i>  » <i>Automatic label</i> screen.') . '</p>';
      $output .= '<p>' . t('<a href="@automatic_entitylabel_configuration">Click here</a> to redirect to Automatic Label configuration.', array(
        '@automatic_entitylabel_configuration' => url('admin/structure/auto-label'),
      )) . '</p>';
      $output .= '<p>' . t('Advanced users can use PHP code for automatically generating labels. See below for more information.') . '</p>';
      return $output;
  }
}

/**
 * Implements hook_permission().
 */
function auto_entitylabel_permission() {
  return array(
    'use PHP for label patterns' => array(
      'title' => t('Use PHP for label patterns'),
      'description' => t('Use PHP evaluation for automatic entity label patterns.'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Implements hook_menu().
 */
function auto_entitylabel_menu() {
  $items = array();
  $items['admin/structure/auto-label'] = array(
    'title' => 'Automatic label',
    'page callback' => 'auto_entitylabel_settings_default_page',
    'access callback' => 'user_access',
    'file' => 'auto_entitylabel.admin.inc',
    'access arguments' => array(
      'administer site configuration',
    ),
  );

  // This logic is taken from the field_ui module.
  // Create tabs for all possible bundles.
  foreach (entity_get_info() as $entity_type => $entity_info) {
    if (isset($entity_info['entity keys']['label'])) {
      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'];

          // 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.
          if (isset($bundle_info['admin']['bundle argument'])) {
            $bundle_arg = $bundle_info['admin']['bundle argument'];
          }
          else {
            $bundle_arg = $bundle_name;
          }

          // 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',
            ),
          );
          $items["{$path}/auto_label"] = array(
            'title' => 'Auto label',
            'page callback' => 'drupal_get_form',
            'page arguments' => array(
              'auto_entitylabel_settings_form',
              $entity_type,
              $bundle_arg,
            ),
            'type' => MENU_LOCAL_TASK,
            'weight' => 2,
            'file' => 'auto_entitylabel.admin.inc',
          ) + $access;

          // Prefix comment entities, because otherwise there would be multiple
          // menu items that just said "Auto label".
          if ($entity_type == 'comment') {
            $items["{$path}/auto_label"]['title'] = '@type Auto label';
            $items["{$path}/auto_label"]['title arguments'] = array(
              '@type' => $entity_info['label'],
            );
            $items["{$path}/auto_label"]['weight'] = 4;
          }
        }
      }
    }
  }
  return $items;
}

/**
 * Implements hook_form_alter().
 */
function auto_entitylabel_form_alter(&$form, &$form_state, $form_id) {
  if (isset($form['#entity_type']) && isset($form['#bundle']) && empty($form['#auto_entitylabel_processed'])) {
    $info = entity_get_info($form['#entity_type']);

    // Exit if there is no label field, e.g. users only have a "label callback".
    if (!isset($info['entity keys']['label'])) {
      return;
    }
    $key = $form['#entity_type'] . '_' . $form['#bundle'];
    $title = $info['entity keys']['label'];
    $form['#auto_entitylabel_processed'] = TRUE;

    // Integration with the title module.
    $replacement = FALSE;
    if (module_exists('title') && title_field_replacement_enabled($form['#entity_type'], $form['#bundle'], $title)) {
      $title = $info['field replacement'][$title]['instance']['field_name'];
      $replacement = TRUE;
    }
    if (auto_entitylabel_get_setting($key) == AUTO_ENTITYLABEL_ENABLED) {

      // We will autogenerate the title later, just hide the title field in the
      // meanwhile.
      if ($replacement && isset($form[$title])) {
        $form[$title][$form[$title]['#language']][0]['value']['#value'] = AUTO_ENTITYLABEL_PLACEHOLDER;
        $form[$title][$form[$title]['#language']][0]['value']['#type'] = 'value';
        $form[$title][$form[$title]['#language']][0]['value']['#required'] = FALSE;
      }
      else {
        $form[$title]['#value'] = AUTO_ENTITYLABEL_PLACEHOLDER;
        $form[$title]['#type'] = 'value';
        $form[$title]['#required'] = FALSE;
      }
    }
    elseif (auto_entitylabel_get_setting($key) == AUTO_ENTITYLABEL_OPTIONAL) {
      if ($replacement && isset($form[$title])) {
        $form[$title][$form[$title]['#language']][0]['value']['#required'] = FALSE;
      }
      else {
        $form[$title]['#required'] = FALSE;
      }
    }
  }
}

/**
 * Implements hook_entity_insert().
 */
function auto_entitylabel_entity_insert($entity, $type) {
  if (auto_entitylabel_is_needed($entity, $type, TRUE)) {
    list($id, , ) = entity_extract_ids($type, $entity);
    $entity_list =& drupal_static(__FUNCTION__, array());
    $entity_list[$type][] = $id;
  }
}

/**
 * Implements hook_exit().
 *
 * @see auto_entitylabel_entity_insert()
 */
function auto_entitylabel_exit($destination = NULL) {
  $entity_list =& drupal_static('auto_entitylabel_entity_insert', array());

  // Loop through the entity types and then individual entity IDs.
  foreach ($entity_list as $entity_type => $entity_ids) {
    foreach ($entity_ids as $entity_id) {
      $entity = entity_load_single($entity_type, $entity_id);
      $settings = _auto_entitylabel_get_settings($entity, $entity_type);
      if ($entity) {

        // Store the old label.
        $old_label = $entity->{$settings['title']};

        // Update the entity label.
        auto_entitylabel_set_title($entity, $entity_type);

        // Save it only if the title has changed.
        if ($entity->{$settings['title']} != $old_label) {
          entity_save($entity_type, $entity);
        }
      }
    }
  }
}

/**
 * Implements hook_field_attach_form().
 */
function auto_entitylabel_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  auto_entitylabel_form_alter($form, $form_state, NULL);
}

/**
 * Implements hook_entity_presave().
 */
function auto_entitylabel_entity_presave($entity, $type) {
  if (auto_entitylabel_is_needed($entity, $type)) {
    auto_entitylabel_set_title($entity, $type);
  }
}

/**
 * Implements hook_field_attach_presave().
 *
 * Required for compatibility with entity_translation module, which does not
 * invoke hook_entity_presave() when saving new translations.
 */
function auto_entitylabel_field_attach_presave($entity_type, $entity) {
  auto_entitylabel_entity_presave($entity, $entity_type);
}

/**
 * Returns whether the auto entitylabel has to be set on the provided entity.
 *
 * @param object $entity
 *   A Drupal entity object.
 * @param string $entity_type
 *   The entity type.
 * @param bool $reset
 *   Force set the label, even if a title already exists.
 *
 * @return bool
 *   Return TRUE if needed.
 */
function auto_entitylabel_is_needed($entity, $entity_type, $reset = FALSE) {

  // First, we'll attempt to get the entity type specific settings.
  $settings = _auto_entitylabel_get_settings($entity, $entity_type);

  // Stub out a few rules we'll need to verify in addition to the $reset flag.
  // Do we have configs for this entity type?
  $has_settings = $settings && !empty($settings['key']) && ($setting = auto_entitylabel_get_setting($settings['key']));

  // We can't do much if there are no settings.
  if ($has_settings) {

    // Is the label empty?
    $empty_label = empty($entity->{$settings['title']}) || $entity->{$settings['title']} == AUTO_ENTITYLABEL_PLACEHOLDER || empty($entity->auto_entitylabel_applied);

    // Is the auto label optional?
    $label_optional = $setting == AUTO_ENTITYLABEL_OPTIONAL;

    // Check if the label is empty or forced.
    // Then makes sure an existing label isn't overwritten if the autolabel is
    // optional.
    return ($empty_label || $reset) && !($label_optional && !$empty_label);
  }

  // The above conditions where not met so we'll return "false".
  return FALSE;
}

/**
 * Sets the automatically generated entitylabel for the entity.
 */
function auto_entitylabel_set_title(&$entity, $type) {
  $settings = _auto_entitylabel_get_settings($entity, $type);

  // Generate title in different languages?
  $multilingual = FALSE;

  // Support for title module.
  $entity_info = entity_get_info($type);
  list(, , $bundle) = entity_extract_ids($type, $entity);
  $title_field_name = FALSE;
  $title_languages = array();
  if (module_exists('title') && title_field_replacement_enabled($type, $bundle, $settings['title'])) {
    $title_field_name = $entity_info['field replacement'][$settings['title']]['instance']['field_name'];
    $field_info = field_info_field($title_field_name);
    $title_languages = field_available_languages($type, $field_info);
    $multilingual = count($title_languages) > 1;
  }

  // Remove LANGUAGE_NONE from array of languages.
  if (($key = array_search(LANGUAGE_NONE, $title_languages, TRUE)) && auto_entitylabel_entity_language($type, $entity) !== LANGUAGE_NONE) {
    unset($title_languages[$key]);
  }

  // Generate titles.
  $titles = array();
  $pattern = variable_get('auto_entitylabel_pattern_' . $settings['key'], '');
  if (trim($pattern)) {
    $entity->changed = REQUEST_TIME;
    if ($multilingual) {
      foreach ($title_languages as $language) {
        $titles[$language] = _auto_entitylabel_patternprocessor($pattern, $entity, $type, $language);
      }
    }
    else {
      $titles[LANGUAGE_NONE] = _auto_entitylabel_patternprocessor($pattern, $entity, $type);
    }
  }
  elseif ($type == 'node' && !empty($entity->nid)) {
    $titles[LANGUAGE_NONE] = t('@bundle @node-id', array(
      '@bundle' => $bundle,
      '@node-id' => $entity->nid,
    ));
  }
  else {
    $titles[LANGUAGE_NONE] = t('@bundle', array(
      '@bundle' => $bundle,
    ));
  }
  $clone = clone $entity;
  drupal_alter('auto_entitylabel_title', $titles, $clone);

  // Ensure the generated title isn't too long.
  $max_length = auto_entitylabel_max_length($type, $settings['title']);
  foreach ($titles as $k => $v) {
    $titles[$k] = drupal_substr($v, 0, $max_length);
  }

  // Save titles on entity (field)
  if (module_exists('title') && title_field_replacement_enabled($type, $bundle, $settings['title'])) {
    foreach ($titles as $lang => $title) {
      if (!isset($entity->{$title_field_name}[$lang][0]['value']) || $entity->{$title_field_name}[$lang][0]['value'] != $title) {
        $entity->{$title_field_name}[$lang][0]['format'] = NULL;
        $entity->{$title_field_name}[$lang][0]['safe_value'] = check_plain($title);
        $entity->{$title_field_name}[$lang][0]['value'] = $title;
        $entity->auto_entitylabel_changed = TRUE;
      }
    }
  }

  // Save title on entity (non-field).  This needs be done even if field_title
  // above is updated, because the title module automatically syncs changes
  // from the "non field title" to the "title field". Without this line we end
  // up getting AUTO_ENTITYLABEL_PLACEHOLDER as the title.
  $entity_language = auto_entitylabel_entity_language($type, $entity);
  $title = isset($titles[$entity_language]) ? $titles[$entity_language] : $titles[LANGUAGE_NONE];
  if (!isset($entity->{$settings['title']}) || $entity->{$settings['title']} != $title) {
    $entity->{$settings['title']} = $title;
    $entity->auto_entitylabel_changed = TRUE;
  }

  // With that flag we ensure we don't apply the title two times to the same
  // node. See auto_entitylabel_is_needed().
  $entity->auto_entitylabel_applied = TRUE;
}

/**
 * Implements hook_action_info().
 */
function auto_entitylabel_action_info() {
  $info['auto_entitylabel_entity_update_action'] = array(
    'type' => 'entity',
    'label' => t('Update automatic entity labels'),
    'configurable' => FALSE,
  );
  return $info;
}

/**
 * Update action wrapper.
 *
 * @see auto_entitylabel_action_info()
 */
function auto_entitylabel_entity_update_action(&$entity, &$context = array()) {
  if (auto_entitylabel_is_needed($entity, $context['entity_type'], TRUE)) {
    auto_entitylabel_set_title($entity, $context['entity_type']);

    // Only save if the title has actually changed.
    if (!empty($entity->auto_entitylabel_changed)) {
      entity_save($context['entity_type'], $entity);
    }
  }
}

/**
 * Implements hook_node_operations().
 */
function auto_entitylabel_node_operations() {
  $operations = array(
    'entitylabel_update' => array(
      'label' => t('Update automatic node titles'),
      'callback' => 'auto_entitylabel_operations_update',
    ),
  );
  return $operations;
}

/**
 * Callback function for updating node titles.
 */
function auto_entitylabel_operations_update($nodes) {
  foreach (entity_load('node', $nodes) as $node) {
    $context = array(
      'entity_type' => 'node',
    );
    auto_entitylabel_entity_update_action($node, $context);
  }
}

/**
 * Callback function for decoding HTML entities.
 */
function _auto_entitylabel_nohtmlentities(&$replacements, $data, $options) {
  foreach ($replacements as $key => $value) {
    $replacements[$key] = decode_entities($value);
  }
}

/**
 * Helper function to generate the title according to the settings.
 *
 * @return output
 *   A title string
 */
function _auto_entitylabel_patternprocessor($pattern, $entity, $entity_type, $language = LANGUAGE_NONE) {

  // Replace tokens.
  $info = entity_get_info($entity_type);
  $languages = language_list();
  $language_obj = $language == LANGUAGE_NONE ? NULL : $languages[$language];
  $token_data = isset($info['token type']) ? array(
    $info['token type'] => $entity,
  ) : array();
  $output = token_replace($pattern, $token_data, array(
    'callback' => '_auto_entitylabel_nohtmlentities',
    'sanitize' => FALSE,
    'clear' => TRUE,
    'language' => $language_obj,
  ));

  // Evalute PHP.
  $settings = _auto_entitylabel_get_settings($entity, $entity_type);
  if (variable_get('auto_entitylabel_php_' . $settings['key'], 0)) {
    $output = auto_entitylabel_eval($output, $entity, $language);
  }

  // Strip tags.
  if ($entity_type && $entity) {
    list(, , $bundle_name) = entity_extract_ids($entity_type, $entity);
    $strip_tags = variable_get('auto_entitylabel_strip_' . $entity_type . '_' . $bundle_name, 1);
    if ($strip_tags) {
      $output = preg_replace('/[\\t\\n\\r\\0\\x0B]/', '', strip_tags($output));
    }
  }
  return $output;
}

/**
 * Gets the auto node title setting associated with the given content type.
 */
function auto_entitylabel_get_setting($type) {
  return variable_get('auto_entitylabel_' . $type, AUTO_ENTITYLABEL_DISABLED);
}

/**
 * Evaluates php code and passes $entity and $language to it.
 */
function auto_entitylabel_eval($code, $entity, $language = LANGUAGE_NONE) {
  ob_start();

  // @codingStandardsIgnoreLine
  print eval('?>' . $code);
  $output = ob_get_contents();
  ob_end_clean();
  return $output;
}

/**
 * Implements hook_field_attach_delete_bundle().
 */
function auto_entitylabel_field_attach_delete_bundle($entity_type, $bundle, $instances) {
  static $variables = array(
    'auto_entitylabel',
    'auto_entitylabel_pattern',
    'auto_entitylabel_php',
  );
  foreach ($variables as $variable) {
    variable_del($variable . '_' . $entity_type . '_' . $bundle);
  }
}

/**
 * Implements hook_field_attach_rename_bundle().
 */
function auto_entitylabel_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
  static $variables = array(
    'auto_entitylabel',
    'auto_entitylabel_pattern',
    'auto_entitylabel_php',
  );
  if ($bundle_old !== $bundle_new) {
    foreach ($variables as $variable) {
      $key_old = $variable . '_' . $entity_type . '_' . $bundle_old;
      $key_new = $variable . '_' . $entity_type . '_' . $bundle_new;
      variable_set($key_new, variable_get($key_old, ''));
      variable_del($key_old);
    }
  }
}

/**
 * Function to get settings.
 */
function _auto_entitylabel_get_settings($entity, $entity_type) {
  $entity_info = entity_get_info($entity_type);
  if ($entity_info['entity keys']['bundle'] && !empty($entity)) {
    $result['key'] = $entity_type . '_' . $entity->{$entity_info['entity keys']['bundle']};
    $result['title'] = isset($entity_info['entity keys']['label']) ? $entity_info['entity keys']['label'] : 'none';
    return $result;
  }
  return FALSE;
}

/**
 * Implements hook_features_pipe_node_alter().
 *
 * Adds auto_entitylabel_* variables when exporting node types to features.
 *
 * @todo
 *   Generalize for other entity types.
 */
function auto_entitylabel_features_pipe_node_alter(&$pipe, $data, $export) {
  if (!empty($data)) {
    $variables = array(
      'auto_entitylabel_node',
      'auto_entitylabel_pattern_node',
      'auto_entitylabel_php_node',
    );
    foreach ($data as $node_type) {
      foreach ($variables as $variable_name) {
        $pipe['variable'][] = "{$variable_name}_{$node_type}";
      }
    }
  }
}

/**
 * Function to extract the maximum length.
 *
 * Tries to extract the maximum length for the given property from the
 * respective database schema field.
 *
 * Assumes that the schema field is named identical to the property.
 */
function auto_entitylabel_max_length($entity_type, $property_name) {

  // Load entity and schema info.
  $entity_info = entity_get_info($entity_type);
  $schema = drupal_get_schema($entity_info['base table']);

  // Return 'length' from schema field as maximum length, fall back to 255.
  return isset($schema['fields'][$property_name]['length']) ? $schema['fields'][$property_name]['length'] : 255;
}

/**
 * Returns the language code of the given entity.
 *
 * Backward compatibility layer to ensure that installations running an older
 * version of core where entity_language() is not available do not break.
 *
 * @param string $entity_type
 *   An entity type.
 * @param object $entity
 *   An entity object.
 * @param bool $check_language_property
 *   A boolean if TRUE, will attempt to fetch the language code from
 *   $entity->language if the entity_language() function failed or does not
 *   exist. Default is TRUE.
 */
function auto_entitylabel_entity_language($entity_type, $entity, $check_language_property = TRUE) {
  $langcode = NULL;

  // When entity_translation is installed, if overrides the behavior of the
  // entity_language() function by returning the "active form language" instead
  // of the "original entity language". In this case we need to retrieve the
  // original language directly from the translation handler.
  if (module_exists('entity_translation')) {
    $handler = entity_translation_get_handler($entity_type, $entity);
    $langcode = $handler
      ->getLanguage();
  }
  elseif (function_exists('entity_language')) {
    $langcode = entity_language($entity_type, $entity);
  }
  elseif ($check_language_property && !empty($entity->language)) {
    $langcode = $entity->language;
  }
  return !empty($langcode) ? $langcode : LANGUAGE_NONE;
}

Functions

Namesort descending Description
auto_entitylabel_action_info Implements hook_action_info().
auto_entitylabel_entity_insert Implements hook_entity_insert().
auto_entitylabel_entity_language Returns the language code of the given entity.
auto_entitylabel_entity_presave Implements hook_entity_presave().
auto_entitylabel_entity_update_action Update action wrapper.
auto_entitylabel_eval Evaluates php code and passes $entity and $language to it.
auto_entitylabel_exit Implements hook_exit().
auto_entitylabel_features_pipe_node_alter Implements hook_features_pipe_node_alter().
auto_entitylabel_field_attach_delete_bundle Implements hook_field_attach_delete_bundle().
auto_entitylabel_field_attach_form Implements hook_field_attach_form().
auto_entitylabel_field_attach_presave Implements hook_field_attach_presave().
auto_entitylabel_field_attach_rename_bundle Implements hook_field_attach_rename_bundle().
auto_entitylabel_form_alter Implements hook_form_alter().
auto_entitylabel_get_setting Gets the auto node title setting associated with the given content type.
auto_entitylabel_help Implements hook_help().
auto_entitylabel_is_needed Returns whether the auto entitylabel has to be set on the provided entity.
auto_entitylabel_max_length Function to extract the maximum length.
auto_entitylabel_menu Implements hook_menu().
auto_entitylabel_node_operations Implements hook_node_operations().
auto_entitylabel_operations_update Callback function for updating node titles.
auto_entitylabel_permission Implements hook_permission().
auto_entitylabel_set_title Sets the automatically generated entitylabel for the entity.
_auto_entitylabel_get_settings Function to get settings.
_auto_entitylabel_nohtmlentities Callback function for decoding HTML entities.
_auto_entitylabel_patternprocessor Helper function to generate the title according to the settings.

Constants

Namesort descending Description
AUTO_ENTITYLABEL_DISABLED @file Allows hiding of entity label fields and automatic label creation.
AUTO_ENTITYLABEL_ENABLED
AUTO_ENTITYLABEL_OPTIONAL
AUTO_ENTITYLABEL_PLACEHOLDER