You are here

usermerge.module in User Merge 7.2

Same filename and directory in other branches
  1. 6 usermerge.module
  2. 7 usermerge.module

Main file for the User Merge module.

File

usermerge.module
View source
<?php

/**
 * @file
 * Main file for the User Merge module.
 *
 */

/**
 * Implements hook_permission().
 */
function usermerge_permission() {
  return array(
    'merge accounts' => array(
      'title' => t('Merge accounts'),
    ),
    'administer usermerge settings' => array(
      'title' => t('Administer User Merge settings'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function usermerge_menu() {
  $items['admin/people/merge'] = array(
    'title' => 'Merge accounts',
    'page callback' => 'usermerge_page',
    'access callback' => 'user_access',
    'access arguments' => array(
      'merge accounts',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/people/usermerge'] = array(
    'title' => 'User Merge settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'usermerge_settings',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer usermerge settings',
    ),
  );
  return $items;
}

/**
 * Loads includes before calling drupal_get_form().
 *
 * @see usermerge_merge_form()
 */
function usermerge_page() {
  return drupal_get_form('usermerge_merge_form');
}

/**
 * Form to collect the two user IDs.
 */
function usermerge_merge_form($form, &$form_state) {
  if (!empty($form_state['form_page']) && $form_state['form_page'] == 'review_table') {
    return usermerge_data_review_form($form, $form_state);
  }

  // This is necessary, otherwise the title will show up as "People"
  drupal_set_title(t('Merge accounts'));
  $form['supported_actions'] = array(
    '#type' => 'fieldset',
    '#title' => t('Supported actions'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['supported_actions']['list'] = array(
    '#theme' => 'item_list',
    '#type' => 'ul',
  );
  $supported_actions = usermerge_invoke_all('usermerge_actions_supported', array());
  ksort($supported_actions);
  $form['supported_actions']['list']['#items'] = $supported_actions;
  $form['general']['usermerge_user_delete'] = array(
    '#type' => 'textfield',
    '#title' => t('The name of the account you wish to remove'),
    '#autocomplete_path' => 'user/autocomplete',
    '#required' => TRUE,
  );
  $form['general']['usermerge_user_keep'] = array(
    '#type' => 'textfield',
    '#title' => t('The name of the account you wish to keep'),
    '#autocomplete_path' => 'user/autocomplete',
    '#required' => TRUE,
  );
  $form['general']['usermerge_user_delete_action'] = array(
    '#type' => 'select',
    '#title' => t('Action to perform on the account you wish to remove'),
    '#options' => array(
      'block' => t('Block'),
      'delete' => t('Delete'),
    ),
    '#default_value' => 'block',
  );
  $form['general']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Review account data'),
    '#submit' => array(
      'usermerge_merge_form_submit',
    ),
  );
  return $form;
}

/**
 * Form that allows the selection of data to be merged.
 */
function usermerge_data_review_form($form, &$form_state) {
  drupal_set_title(t('Merge accounts: review account data'));
  $user_to_delete = $form_state['values']['user_to_delete'];
  $user_to_keep = $form_state['values']['user_to_keep'];
  $action = $form_state['page_values']['merge_form']['usermerge_user_delete_action'];

  // Invoke hook_usermerge_account_properties
  $account_properties = usermerge_invoke_all('usermerge_account_properties', $user_to_delete, $user_to_keep, $action);

  // Allow modules to alter pre-existing account properties
  drupal_alter('usermerge_account_properties', $account_properties, $user_to_delete, $user_to_keep, $action);
  $form['review'] = usermerge_invoke_all('usermerge_build_review_form_elements', array(), $account_properties, $user_to_delete, $user_to_keep);
  $form['review']['#type'] = 'container';
  $form['review']['#tree'] = TRUE;
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Merge accounts'),
  );
  $form['cancel'] = array(
    '#type' => 'markup',
    '#markup' => l(t('Cancel'), 'admin/people/merge'),
  );
  return $form;
}

/**
 * Implements hook_theme().
 */
function usermerge_theme() {
  $theme['usermerge_data_review_form_table'] = array(
    'render element' => 'element',
  );
  return $theme;
}

/**
 * Returns HTML for each data-review table.
 */
function theme_usermerge_data_review_form_table($variables) {
  $element = $variables['element'];
  $table = array(
    'header' => array(
      'property_name' => isset($element['#attributes']['property_label']) ? $element['#attributes']['property_label'] : t('Property'),
      'user_to_delete' => t('User to delete'),
      'user_to_keep' => t('User to keep'),
    ),
  );
  if (!in_array('no_merge', $element['#attributes'])) {
    $table['header']['merge'] = t('Merge');
  }
  foreach (element_children($element) as $id) {
    $table['rows'][$id]['property_name'] = '<strong>' . drupal_render($element[$id]['property_name']) . '</strong>';
    $table['rows'][$id]['user_to_delete'] = drupal_render($element[$id]['options']['user_to_delete']);
    $table['rows'][$id]['user_to_keep'] = drupal_render($element[$id]['options']['user_to_keep']);
    if (isset($table['header']['merge'])) {
      if (isset($element[$id]['options']['merge'])) {

        // Case 'force_select' or 'no_merge'
        $table['rows'][$id]['merge'] = '';

        // Case 'merge'
        if ($element[$id]['options']['#options']['merge'] == 'merge') {
          $element[$id]['options']['merge']['#title'] = t('Merge data');
          $table['rows'][$id]['merge'] = drupal_render($element[$id]['options']['merge']);
        }
      }
    }
  }
  return empty($table['rows']) ? '' : '<h3>' . $element['#title'] . "</h3>\n" . (isset($element['#description']) ? "<p>" . $element['#description'] . "</p>\n" : '') . theme('table', $table);
}

/**
 * Validates contents of form for user validation.
 */
function usermerge_merge_form_validate($form, &$form_state) {

  // Validate first step
  if (!isset($form_state['form_page'])) {

    // Can't be the same user.
    if ($form_state['values']['usermerge_user_keep'] == $form_state['values']['usermerge_user_delete']) {
      form_set_error('usermerge_user', 'You cannot have the same username in both fields.');
    }
    $user_to_delete = user_load_multiple(array(), array(
      'name' => $form_state['values']['usermerge_user_delete'],
    ));
    $user_to_keep = user_load_multiple(array(), array(
      'name' => $form_state['values']['usermerge_user_keep'],
    ));

    // Load up the users. We store these in the form_state['values'] so that we don't have to load again later.
    $form_state['values']['user_to_delete'] = array_shift($user_to_delete);
    $form_state['values']['user_to_keep'] = array_shift($user_to_keep);

    // Use the validate helper function.
    usermerge_validate_merge($form_state['values']['user_to_delete'], $form_state['values']['user_to_keep']);
  }
}

/**
 * Helper validation function used by the form submit and the API.
 *
 * @param $user_to_delete
 *   Object of user to be deleted
 * @param $user_to_keep
 *   Object of user to be kept
 */
function usermerge_validate_merge($user_to_delete, $user_to_keep) {
  $valid = TRUE;
  if (empty($user_to_delete->uid)) {
    form_set_error('usermerge_user_delete', t('This user name does not exist.'));
    $valid = FALSE;
  }
  elseif ($user_to_delete->uid == 1) {
    form_set_error('usermerge_user_delete', t('Blocking or deleting user 1 is not allowed.'));
    $valid = FALSE;
  }
  if (empty($user_to_keep->uid)) {
    form_set_error('usermerge_user_keep', t('This user name does not exist.'));
    $valid = FALSE;
  }
  return $valid;
}

/**
 * Displays the userdata-review form, or merges the selected accounts.
 */
function usermerge_merge_form_submit($form, &$form_state) {
  if (!isset($form_state['form_page'])) {
    $form_state['page_values']['merge_form'] = $form_state['values'];
    if (!empty($form_state['page_values']['review_table'])) {
      $form_state['values'] = $form_state['page_values']['review_table'];
    }
    $form_state['form_page'] = 'review_table';
    $form_state['rebuild'] = TRUE;
  }
  else {
    $form_state['values']['usermerge_user_delete_action'] = $form_state['page_values']['merge_form']['usermerge_user_delete_action'];
    usermerge_merge_accounts($form_state['page_values']['merge_form']['user_to_delete'], $form_state['page_values']['merge_form']['user_to_keep'], $form_state['values']);
  }
}

/**
 * Merges the selected accounts.
 *
 * @param object $user_to_delete
 *   A fully loaded user object from user_load() that will be merged and blocked or deleted.
 * @param object $user_to_keep
 *   A fully loaded user object from user_load() that will be merged and retained.
 * @param $form_values
 *   The values from the submitted data-review form.
 * @return boolean
 *   TRUE if they are merged, FALSE if the validation failed.
 */
function usermerge_merge_accounts($user_to_delete, $user_to_keep, $form_values) {
  if (usermerge_validate_merge($user_to_delete, $user_to_keep)) {
    $merged_account = usermerge_invoke_all('usermerge_merge_accounts', $user_to_delete, $user_to_keep, $form_values['review']);

    // This allows modules to modify the array without merging values
    drupal_alter('usermerge_merge_accounts', $merged_account, $user_to_delete, $user_to_keep);
    $merged_account = (object) $merged_account;

    // Save $merged_account
    $merged_account->pass = $user_to_keep->pass;
    $merged_account->status = 1;

    // Block or delete old account. Do this before saving the new account in
    // case the merged account is using the deleted account's mail or name.
    switch ($form_values['usermerge_user_delete_action']) {
      case 'block':
        user_block_user_action($user_to_delete);
        break;
      case 'delete':
        user_delete($user_to_delete->uid);
        break;
    }

    // Save merged account
    user_save($merged_account);
    drupal_set_message(t('%user_to_delete was successfully merged into %user_to_keep', array(
      '%user_to_delete' => $user_to_delete->name,
      '%user_to_keep' => $user_to_keep->name,
    )));
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Merges two accounts automatically, making decisions on which data should
 * be preserved.
 *
 * Modules that need to implement a straightforward way to merge accounts should
 * use this function.
 *
 * @param object $user_to_delete
 *   A fully loaded user object from user_load() that will be merged and blocked or deleted.
 * @param object $user_to_keep
 *   A fully loaded user object from user_load() that will be merged and retained.
 */
function usermerge_do($user_to_delete, $user_to_keep) {

  // Build (but not display) data-review form
  usermerge_load_includes();
  $review_form_state['values']['user_to_delete'] = $user_to_delete;
  $review_form_state['values']['user_to_keep'] = $user_to_keep;

  // This allows to call the data-review table instead of the account-selection form
  $review_form_state['form_page'] = 'review_table';
  $review_form = usermerge_merge_form(array(), $review_form_state);

  // Build form values
  // This makes a few assumptions on modules' behavior. It would be advisable to
  // make the User Merge API documentation clearer on what modules should do.
  $review_form_state['page_values']['merge_form'] = $review_form_state['values'];
  $review = array_intersect_key($review_form['review'], array_flip(element_children($review_form['review'])));
  foreach ($review as $component => $items) {
    if (isset($items['#value'])) {
      $review_form_state['values'][$component] = $items['#value'];
    }
    else {
      $items = array_intersect_key($items, array_flip(element_children($items)));
      foreach ($items as $name => $properties) {
        if (isset($properties['options']['#options'])) {

          // If the property has a merge options, merge data
          if (isset($properties['options']['#options']['merge']) && $properties['options']['#options']['merge'] == 'merge') {
            $review_form_state['values']['review'][$component][$name]['options'] = 'merge';
          }
          else {
            $review_form_state['values']['review'][$component][$name]['options'] = 'user_to_keep';
          }
        }
        else {

          // If there are no set options, check if the module sets other form fields
          unset($properties['property_name'], $properties['options']);
          if (count($properties)) {
            foreach ($properties as $property_name => $property) {
              if (isset($property['#value'])) {
                $review_form_state['values']['review'][$component][$name][$property_name] = $property['#value'];
              }
            }
          }
        }
      }
    }
  }

  // Merge accounts
  // Force-delete old account
  $review_form_state['page_values']['merge_form']['usermerge_user_delete_action'] = 'delete';
  drupal_form_submit('usermerge_merge_form', $review_form_state);
}

/**
 * Implements hook_hook_info().
 */
function usermerge_hook_info() {
  $hooks = array(
    'usermerge_actions_supported' => array(
      'group' => 'usermerge',
    ),
    'usermerge_account_properties' => array(
      'group' => 'usermerge',
    ),
    'usermerge_account_properties_alter' => array(
      'group' => 'usermerge',
    ),
    'usermerge_build_review_form_elements' => array(
      'group' => 'usermerge',
    ),
    'usermerge_merge_accounts' => array(
      'group' => 'usermerge',
    ),
    'usermerge_merge_accounts_alter' => array(
      'group' => 'usermerge',
    ),
  );
  return $hooks;
}

/**
 * Calls all includes.
 *
 * Ideally, this function should call only supplemental includes provided by
 * the module itself, but supplemental includes conflict with includes provided
 * by other modules, making the whole module unreliable quite randomly.
 */
function usermerge_load_includes() {
  foreach (module_list() as $module) {

    // Load MODULE.usermerge.inc files
    if (!($file = module_load_include('inc', $module, $module . '.usermerge'))) {

      // Load supplemental includes only if the module doesn't provide its own implementation
      $file = module_load_include('inc', 'usermerge', 'includes/' . $module . '.usermerge');
    }
  }
}

/**
 * Invokes a hook in all enabled modules that implement it.
 *
 * Replaces module_invoke_all() and bypasses the cache.
 */
function usermerge_invoke_all($hook) {
  usermerge_load_includes();
  $args = func_get_args();

  // Remove $hook from the arguments.
  unset($args[0]);
  $return = array();
  foreach (usermerge_module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    if (function_exists($function)) {
      $result = call_user_func_array($function, $args);
      if (isset($result) && is_array($result)) {
        $return = array_merge_recursive($return, $result);
      }
      elseif (isset($result)) {
        $return[] = $result;
      }
    }
  }
  return $return;
}

/**
 * Determines which modules are implementing a hook.
 *
 * Replaces module_implements().
 * Does not need to load includes, because they should all have already
 * been loaded in usermerge_invoke_all().
 */
function usermerge_module_implements($hook) {
  $modules = array();
  foreach (module_list() as $module) {
    $function = $module . '_' . $hook;
    if (function_exists($function)) {
      $modules[] = $module;
    }
  }
  return $modules;
}

/**
 * Form builder for User Merge settings.
 */
function usermerge_settings() {
  $usermerge_settings = variable_get('usermerge_settings', usermerge_settings_default());
  $core_properties = usermerge_get_user_core_properties();
  $form['core'] = array(
    '#type' => 'fieldset',
    '#title' => t('Core properties'),
    '#description' => t('By default, the module handles core properties automatically, by keeping the value from the user to keep. This is why most of them do not appear in the review table. If you want to display some core properties in the review table, enable them here by checking the relevant boxes.'),
    '#tree' => TRUE,
  );
  foreach ($core_properties as $property) {

    // For some properties there is never a choice
    if (!in_array($property, array(
      'uid',
      'created',
      'access',
      'login',
      'pass',
      'init',
      'data',
      'roles',
    ))) {
      $form['core'][$property] = array(
        '#type' => 'checkbox',
        '#title' => $property,
        // No need to localize this
        '#default_value' => !empty($usermerge_settings['core'][$property]) ? $usermerge_settings['core'][$property] : 0,
      );
    }
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  return $form;
}

/**
 * Submit handler for the  User Merge settings form.
 */
function usermerge_settings_submit($form, &$form_state) {
  form_state_values_clean($form_state);
  foreach ($form_state['values'] as $type => $values) {
    $usermerge_settings[$type] = $values;
  }
  variable_set('usermerge_settings', $usermerge_settings);
  drupal_set_message('The User Merge settings have been saved.');
}

/**
 * Default value for the User Merge settings variable.
 */
function usermerge_settings_default() {
  static $default;
  if (empty($default)) {
    $core_properties = usermerge_get_user_core_properties();
    foreach ($core_properties as $property) {
      if (!in_array($property, array(
        'uid',
        'created',
        'access',
        'login',
        'pass',
        'name',
        'mail',
        'init',
        'data',
        'roles',
      ))) {
        $default['core'][$property] = 0;
      }
    }
  }
  return $default;
}

/**
 * Returns an array of the core properties for the user entity.
 */
function usermerge_get_user_core_properties() {
  static $properties;
  if (empty($properties)) {

    // Define list of fields and other user data
    // Using the columns in the user table, so non-field data added from other modules doesn't get mixed in
    $user_entity_info = entity_get_info('user');
    $properties = $user_entity_info['schema_fields_sql']['base table'];

    // Adding roles
    $properties[] = 'roles';
  }
  return $properties;
}

Functions

Namesort descending Description
theme_usermerge_data_review_form_table Returns HTML for each data-review table.
usermerge_data_review_form Form that allows the selection of data to be merged.
usermerge_do Merges two accounts automatically, making decisions on which data should be preserved.
usermerge_get_user_core_properties Returns an array of the core properties for the user entity.
usermerge_hook_info Implements hook_hook_info().
usermerge_invoke_all Invokes a hook in all enabled modules that implement it.
usermerge_load_includes Calls all includes.
usermerge_menu Implements hook_menu().
usermerge_merge_accounts Merges the selected accounts.
usermerge_merge_form Form to collect the two user IDs.
usermerge_merge_form_submit Displays the userdata-review form, or merges the selected accounts.
usermerge_merge_form_validate Validates contents of form for user validation.
usermerge_module_implements Determines which modules are implementing a hook.
usermerge_page Loads includes before calling drupal_get_form().
usermerge_permission Implements hook_permission().
usermerge_settings Form builder for User Merge settings.
usermerge_settings_default Default value for the User Merge settings variable.
usermerge_settings_submit Submit handler for the User Merge settings form.
usermerge_theme Implements hook_theme().
usermerge_validate_merge Helper validation function used by the form submit and the API.