Index: modules/user/user.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v retrieving revision 1.33 diff -u -p -r1.33 user.admin.inc --- modules/user/user.admin.inc 16 Nov 2008 15:10:49 -0000 1.33 +++ modules/user/user.admin.inc 5 Jan 2009 01:37:09 -0000 @@ -15,8 +15,8 @@ function user_admin($callback_arg = '') $output = drupal_get_form('user_register'); break; default: - if (!empty($_POST['accounts']) && isset($_POST['operation']) && ($_POST['operation'] == 'delete')) { - $output = drupal_get_form('user_multiple_delete_confirm'); + if (!empty($_POST['accounts']) && isset($_POST['operation']) && ($_POST['operation'] == 'cancel')) { + $output = drupal_get_form('user_multiple_cancel_confirm'); } else { $output = drupal_get_form('user_filter_form'); @@ -235,6 +235,20 @@ function user_admin_settings() { $form['registration']['user_email_verification'] = array('#type' => 'checkbox', '#title' => t('Require e-mail verification when a visitor creates an account'), '#default_value' => variable_get('user_email_verification', TRUE), '#description' => t('If this box is checked, new users will be required to validate their e-mail address prior to logging into the site, and will be assigned a system-generated password. With it unchecked, users will be logged in immediately upon registering, and may select their own passwords during registration.')); $form['registration']['user_registration_help'] = array('#type' => 'textarea', '#title' => t('User registration guidelines'), '#default_value' => variable_get('user_registration_help', ''), '#description' => t('This text is displayed at the top of the user registration form and is useful for helping or instructing your users.')); + // Cancel account settings. + module_load_include('inc', 'user', 'user.pages'); + $form['cancel'] = array( + '#type' => 'fieldset', + '#title' => t('Cancel account settings'), + ); + $form['cancel']['user_cancel_method'] = array( + '#type' => 'radios', + '#title' => t('When cancelling a user account'), + '#options' => user_cancel_methods(), + '#default_value' => variable_get('user_cancel_method', USER_CANCEL_BLOCK), + '#description' => t('This default applies to all users who want to cancel their accounts. Users with the %select-cancel-method or %administer-users permissions can override this default method.', array('%select-cancel-method' => t('Select method for cancelling account'), '%administer-users' => t('Administer users'), '@permissions-url' => url('admin/user/permissions'))), + ); + // User e-mail settings. $form['email'] = array( '#type' => 'fieldset', @@ -243,7 +257,7 @@ function user_admin_settings() { ); // These email tokens are shared for all settings, so just define // the list once to help ensure they stay in sync. - $email_token_help = t('Available variables are:') . ' !username, !site, !password, !uri, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url.'; + $email_token_help = t('Available variables are:') . ' !username, !site, !password, !uri, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url, !cancel_url.'; $form['email']['admin_created'] = array( '#type' => 'fieldset', @@ -375,28 +389,48 @@ function user_admin_settings() { '#rows' => 3, ); - $form['email']['deleted'] = array( + $form['email']['cancel_confirm'] = array( + '#type' => 'fieldset', + '#title' => t('Cancel account confirmation email'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => t('Customize e-mail messages sent to users when they attempt to cancel their accounts.') . ' ' . $email_token_help, + ); + $form['email']['cancel_confirm']['user_mail_cancel_confirm_subject'] = array( + '#type' => 'textfield', + '#title' => t('Subject'), + '#default_value' => _user_mail_text('cancel_confirm_subject'), + '#maxlength' => 180, + ); + $form['email']['cancel_confirm']['user_mail_cancel_confirm_body'] = array( + '#type' => 'textarea', + '#title' => t('Body'), + '#default_value' => _user_mail_text('cancel_confirm_body'), + '#rows' => 3, + ); + + $form['email']['canceled'] = array( '#type' => 'fieldset', - '#title' => t('Account deleted email'), + '#title' => t('Account canceled email'), '#collapsible' => TRUE, '#collapsed' => TRUE, - '#description' => t('Enable and customize e-mail messages sent to users when their accounts are deleted.') . ' ' . $email_token_help, + '#description' => t('Enable and customize e-mail messages sent to users when their accounts are canceled.') . ' ' . $email_token_help, ); - $form['email']['deleted']['user_mail_status_deleted_notify'] = array( + $form['email']['canceled']['user_mail_status_canceled_notify'] = array( '#type' => 'checkbox', - '#title' => t('Notify user when account is deleted.'), - '#default_value' => variable_get('user_mail_status_deleted_notify', FALSE), + '#title' => t('Notify user when account is canceled.'), + '#default_value' => variable_get('user_mail_status_canceled_notify', FALSE), ); - $form['email']['deleted']['user_mail_status_deleted_subject'] = array( + $form['email']['canceled']['user_mail_status_canceled_subject'] = array( '#type' => 'textfield', '#title' => t('Subject'), - '#default_value' => _user_mail_text('status_deleted_subject'), + '#default_value' => _user_mail_text('status_canceled_subject'), '#maxlength' => 180, ); - $form['email']['deleted']['user_mail_status_deleted_body'] = array( + $form['email']['canceled']['user_mail_status_canceled_body'] = array( '#type' => 'textarea', '#title' => t('Body'), - '#default_value' => _user_mail_text('status_deleted_body'), + '#default_value' => _user_mail_text('status_canceled_body'), '#rows' => 3, ); Index: modules/user/user.install =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.install,v retrieving revision 1.15 diff -u -p -r1.15 user.install --- modules/user/user.install 20 Nov 2008 06:56:17 -0000 1.15 +++ modules/user/user.install 3 Jan 2009 23:26:52 -0000 @@ -360,6 +360,31 @@ function user_update_7002(&$sandbox) { } /** + * Update user settings for cancelling user accounts. + * + * Prior 7.x, users were not able to cancel their accounts. When administrators + * deleted an account, all contents were assigned to uid 0 (Anonymous). + */ +function user_update_7003() { + $ret = array(); + require_once DRUPAL_ROOT . drupal_get_path('module', 'user') . '/user.module'; + variable_set('user_cancel_method', USER_CANCEL_ANONYMIZE); + if ($setting = variable_get('user_mail_status_deleted_notify', FALSE)) { + variable_set('user_mail_status_canceled_notify', $setting); + variable_del('user_mail_status_deleted_notify'); + } + if ($setting = variable_get('user_mail_status_deleted_subject', FALSE)) { + variable_set('user_mail_status_canceled_subject', $setting); + variable_del('user_mail_status_deleted_subject'); + } + if ($setting = variable_get('user_mail_status_deleted_body', FALSE)) { + variable_set('user_mail_status_canceled_body', $setting); + variable_del('user_mail_status_deleted_body'); + } + return $ret; +} + +/** * @} End of "defgroup user-updates-6.x-to-7.x" * The next series of updates should start at 8000. */ Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.952 diff -u -p -r1.952 user.module --- modules/user/user.module 4 Jan 2009 16:10:48 -0000 1.952 +++ modules/user/user.module 5 Jan 2009 16:41:21 -0000 @@ -17,6 +17,27 @@ define('USERNAME_MAX_LENGTH', 60); define('EMAIL_MAX_LENGTH', 64); /** + * Cancel account method: Disable user account and keep all contents. + */ +define('USER_CANCEL_BLOCK', 0); + +/** + * Cancel account method: Disable user account and unpublish all contents. + */ +define('USER_CANCEL_BLOCK_UNPUBLISH', 1); + +/** + * Cancel account method: Remove user account and anonymize all contents. + */ +define('USER_CANCEL_ANONYMIZE', 2); + +/** + * Cancel account method: Remove user account and remove all contents. + */ +define('USER_CANCEL_DELETE', 3); + + +/** * Invokes hook_user() in every module. * * We cannot use module_invoke() for this, because the arguments need to @@ -587,6 +608,14 @@ function user_perm() { 'title' => t('Change own username'), 'description' => t('Select a different username.'), ), + 'cancel account' => array( + 'title' => t('Cancel account'), + 'description' => t('Remove or disable own user account and unpublish, anonymize, or remove own submissions depending on the configured user settings.', array('@user-settings-url' => url('admin/user/settings'))), + ), + 'select account cancelling method' => array( + 'title' => t('Select method for cancelling own account'), + 'description' => t('Select the method for cancelling own user account.'), + ), ); } @@ -948,6 +977,13 @@ function user_edit_access($account) { return (($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0; } +/** + * Menu access callback; limit access to cancel account pages. + */ +function user_cancel_access($account) { + return ((($GLOBALS['user']->uid == $account->uid) && user_access('cancel account')) || user_access('administer users')) && $account->uid > 0; +} + function user_load_self($arg) { $arg[1] = user_load($GLOBALS['user']->uid); return $arg; @@ -1082,12 +1118,21 @@ function user_menu() { 'weight' => -10, ); - $items['user/%user/delete'] = array( - 'title' => 'Delete', + $items['user/%user/cancel'] = array( + 'title' => 'Cancel account', 'page callback' => 'drupal_get_form', - 'page arguments' => array('user_confirm_delete', 1), - 'access callback' => 'user_access', - 'access arguments' => array('administer users'), + 'page arguments' => array('user_cancel_confirm_form', 1), + 'access callback' => 'user_cancel_access', + 'access arguments' => array(1), + 'type' => MENU_CALLBACK, + ); + + $items['user/%user/cancel/confirm'] = array( + 'title' => 'Confirm to cancel account', + 'page callback' => 'user_cancel_confirm', + 'page arguments' => array(1, 4, 5, 6, 7), + 'access callback' => 'user_cancel_access', + 'access arguments' => array(1), 'type' => MENU_CALLBACK, ); @@ -1445,6 +1490,23 @@ function user_pass_reset_url($account) { return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE)); } +/** + * Generate a URL to confirm a cancel account request. + * + * @see user_mail_tokens() + * @see user_cancel_confirm_form_submit() + */ +function user_cancel_url($account) { + // If tokens for other mails than the cancel account confirmation mail are + // generated, we need to use default values for the generated link. + if (!isset($account->user_cancel_method, $account->user_cancel_notify)) { + $account->user_cancel_method = variable_get('user_cancel_method', USER_CANCEL_BLOCK); + $account->user_cancel_notify = variable_get('user_mail_status_canceled_notify', FALSE); + } + $timestamp = REQUEST_TIME; + return url("user/$account->uid/cancel/confirm/$timestamp/{$account->user_cancel_method}/{$account->user_cancel_notify}/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE)); +} + function user_pass_rehash($password, $timestamp, $login) { return md5($timestamp . $password . $login); } @@ -1595,21 +1657,106 @@ function _user_edit_submit($account, &$e } /** - * Delete a user. + * Cancel a user account. + * + * Since the user cancellation process needs to be run in a batch, either + * Form API will invoke it, or batch_process() needs to be invoked after calling + * this function and should define the path to redirect to. + * + * @param $edit + * An array of submitted form values. + * @param $uid + * The user ID of the user account to cancel. + * @param $method + * The cancel account method to use. * - * @param $edit An array of submitted form values. - * @param $uid The user ID of the user to delete. + * @see _user_cancel() */ -function user_delete($edit, $uid) { +function user_cancel($edit, $uid, $method) { + global $user; + $account = user_load(array('uid' => $uid)); - drupal_session_destroy_uid($uid); - _user_mail_notify('status_deleted', $account); - module_invoke_all('user_delete', $edit, $account); - db_query('DELETE FROM {users} WHERE uid = %d', $uid); - db_query('DELETE FROM {users_roles} WHERE uid = %d', $uid); - db_query('DELETE FROM {authmap} WHERE uid = %d', $uid); - $variables = array('%name' => $account->name, '%email' => '<' . $account->mail . '>'); - watchdog('user', 'Deleted user: %name %email.', $variables, WATCHDOG_NOTICE); + + if (!$account) { + drupal_set_message(t('The user account %id does not exist.', array('%id' => $uid)), 'error'); + watchdog('user', 'Attempted to cancel non-existing user account: %id.', array('%id' => $uid), WATCHDOG_ERROR); + return; + } + + // Initialize batch (to set title). + $batch = array( + 'title' => t('Cancelling account'), + 'operations' => array(), + ); + batch_set($batch); + + // Allow modules to add further sets to this batch. + module_invoke_all('user_cancel', $edit, $account, $method); + + // Finish the batch and actually cancel the account. + $batch = array( + 'title' => t('Cancelling user account'), + 'operations' => array( + array('_user_cancel', array($edit, $account, $method)), + ), + ); + batch_set($batch); + + // Batch processing is either handled via Form API or has to be invoked + // manually. +} + +/** + * Last batch processing step for cancelling a user account. + * + * Since batch and session API require a valid user account, the actual + * cancellation of a user account needs to happen last. + * + * @see user_cancel() + */ +function _user_cancel($edit, $account, $method) { + global $user; + + switch ($method) { + case USER_CANCEL_BLOCK: + case USER_CANCEL_BLOCK_UNPUBLISH: + default: + // Send account blocked notification if option was checked. + if (!empty($edit['user_cancel_notify'])) { + _user_mail_notify('status_blocked', $account); + } + db_update('users')->fields(array('status' => 0))->condition('uid', $account->uid)->execute(); + drupal_set_message(t('%name has been disabled.', array('%name' => $account->name))); + break; + + case USER_CANCEL_ANONYMIZE: + case USER_CANCEL_DELETE: + // Send account canceled notification if option was checked. + if (!empty($edit['user_cancel_notify'])) { + _user_mail_notify('status_canceled', $account); + } + db_delete('users')->condition('uid', $account->uid)->execute(); + db_delete('users_roles')->condition('uid', $account->uid)->execute(); + db_delete('authmap')->condition('uid', $account->uid)->execute(); + drupal_set_message(t('%name has been deleted.', array('%name' => $account->name))); + $variables = array('%name' => $account->name, '%email' => '<' . $account->mail . '>'); + watchdog('user', 'Deleted user: %name %email.', $variables, WATCHDOG_NOTICE); + break; + } + + // After cancelling account, ensure that user is logged out. + if ($account->uid == $user->uid) { + // Destroy the current session. + session_destroy(); + // Load the anonymous user. + $user = drupal_anonymous_user(); + } + else { + drupal_session_destroy_uid($account->uid); + } + + // Clear the cache for anonymous users. + cache_clear_all(); } /** @@ -1682,10 +1829,26 @@ function _user_mail_text($key, $language return t('Account details for !username at !site (blocked)', $variables, $langcode); case 'status_blocked_body': return t("!username,\n\nYour account on !site has been blocked.", $variables, $langcode); - case 'status_deleted_subject': - return t('Account details for !username at !site (deleted)', $variables, $langcode); - case 'status_deleted_body': - return t("!username,\n\nYour account on !site has been deleted.", $variables, $langcode); + + case 'cancel_confirm_subject': + return t('Cancel account request for !username at !site', $variables, $langcode); + case 'cancel_confirm_body': + return t("!username, + +A request to cancel your account has been made at !site. + +You may now cancel your account on !uri_brief by clicking this link or copying and pasting it into your browser: + +!cancel_url + +This link expires in one day and nothing will happen if it is not used.", $variables, $langcode); + + case 'status_canceled_subject': + return t('Account details for !username at !site (canceled)', $variables, $langcode); + case 'status_canceled_body': + return t("!username, + +Your account on !site has been canceled.", $variables, $langcode); } } } @@ -1752,8 +1915,8 @@ function user_user_operations($form_stat 'label' => t('Block the selected users'), 'callback' => 'user_user_operations_block', ), - 'delete' => array( - 'label' => t('Delete the selected users'), + 'cancel' => array( + 'label' => t('Cancel the selected user accounts'), ), ); @@ -1866,7 +2029,7 @@ function user_multiple_role_edit($accoun } } -function user_multiple_delete_confirm(&$form_state) { +function user_multiple_cancel_confirm(&$form_state) { $edit = $form_state['post']; $form['accounts'] = array('#prefix' => '', '#tree' => TRUE); @@ -1875,20 +2038,62 @@ function user_multiple_delete_confirm(&$ $user = db_result(db_query('SELECT name FROM {users} WHERE uid = %d', $uid)); $form['accounts'][$uid] = array('#type' => 'hidden', '#value' => $uid, '#prefix' => '
  • ', '#suffix' => check_plain($user) . "
  • \n"); } - $form['operation'] = array('#type' => 'hidden', '#value' => 'delete'); + + $form['operation'] = array('#type' => 'hidden', '#value' => 'cancel'); + + module_load_include('inc', 'user', 'user.pages'); + $form['user_cancel_method'] = array( + '#type' => 'radios', + '#title' => t('When cancelling these accounts'), + '#options' => user_cancel_methods(), + '#default_value' => variable_get('user_cancel_method', USER_CANCEL_BLOCK), + '#required' => TRUE, + ); + + // Allow to send the cancel account confirmation mail. + $form['user_cancel_confirm'] = array( + '#type' => 'checkbox', + '#title' => t('Require e-mail confirmation to cancel account.'), + '#default_value' => FALSE, + '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'), + ); + // Also allow to send account canceled notification mail, if enabled. + $form['user_cancel_notify'] = array( + '#type' => 'checkbox', + '#title' => t('Notify user when account is canceled.'), + '#default_value' => FALSE, + '#access' => variable_get('user_mail_status_canceled_notify', FALSE), + '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'), + ); return confirm_form($form, - t('Are you sure you want to delete these users?'), + t('Are you sure you want to cancel these user accounts?'), 'admin/user/user', t('This action cannot be undone.'), - t('Delete all'), t('Cancel')); + t('Cancel accounts'), t('Cancel')); } -function user_multiple_delete_confirm_submit($form, &$form_state) { +/** + * Submit handler for mass-account cancellation form. + * + * @see user_multiple_cancel_confirm() + * @see user_cancel_confirm_form_submit() + */ +function user_multiple_cancel_confirm_submit($form, &$form_state) { + global $user; + if ($form_state['values']['confirm']) { foreach ($form_state['values']['accounts'] as $uid => $value) { - user_delete($form_state['values'], $uid); + // Prevent user administrators from deleting themselves without confirmation. + if ($uid == $user->uid) { + $admin_form_state = $form_state; + unset($admin_form_state['values']['user_cancel_confirm']); + $admin_form_state['values']['_account'] = $user; + user_cancel_confirm_form_submit(array(), $admin_form_state); + } + else { + user_cancel($form_state['values'], $uid, $form_state['values']['user_cancel_method']); + } } - drupal_set_message(t('The users have been deleted.')); } $form_state['redirect'] = 'admin/user/user'; return; @@ -2082,6 +2287,7 @@ function user_mail_tokens($account, $lan '!username' => $account->name, '!site' => variable_get('site_name', 'Drupal'), '!login_url' => user_pass_reset_url($account), + '!cancel_url' => user_cancel_url($account), '!uri' => $base_url, '!uri_brief' => preg_replace('!^https?://!', '', $base_url), '!mailto' => $account->mail, @@ -2144,8 +2350,8 @@ function user_preferred_language($accoun * The return value from drupal_mail_send(), if ends up being called. */ function _user_mail_notify($op, $account, $language = NULL) { - // By default, we always notify except for deleted and blocked. - $default_notify = ($op != 'status_deleted' && $op != 'status_blocked'); + // By default, we always notify except for canceled and blocked. + $default_notify = ($op != 'status_canceled' && $op != 'status_blocked'); $notify = variable_get('user_mail_' . $op . '_notify', $default_notify); if ($notify) { $params['account'] = $account; Index: modules/user/user.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.pages.inc,v retrieving revision 1.25 diff -u -p -r1.25 user.pages.inc --- modules/user/user.pages.inc 30 Dec 2008 16:43:20 -0000 1.25 +++ modules/user/user.pages.inc 5 Jan 2009 16:36:26 -0000 @@ -228,9 +228,10 @@ function user_edit($account, $category = * @ingroup forms * @see user_profile_form_validate() * @see user_profile_form_submit() - * @see user_edit_delete_submit() + * @see user_cancel_confirm_form_submit() */ function user_profile_form($form_state, $account, $category = 'account') { + global $user; $edit = (empty($form_state['values'])) ? (array)$account : $form_state['values']; @@ -238,12 +239,12 @@ function user_profile_form($form_state, $form['_category'] = array('#type' => 'value', '#value' => $category); $form['_account'] = array('#type' => 'value', '#value' => $account); $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 30); - if (user_access('administer users')) { - $form['delete'] = array( + if (($account->uid == $user->uid && user_access('cancel account')) || user_access('administer users')) { + $form['cancel'] = array( '#type' => 'submit', - '#value' => t('Delete'), + '#value' => t('Cancel account'), '#weight' => 31, - '#submit' => array('user_edit_delete_submit'), + '#submit' => array('user_edit_cancel_submit'), ); } $form['#attributes']['enctype'] = 'multipart/form-data'; @@ -282,45 +283,182 @@ function user_profile_form_submit($form, } /** - * Submit function for the 'Delete' button on the user edit form. + * Submit function for the 'Cancel account' button on the user edit form. */ -function user_edit_delete_submit($form, &$form_state) { +function user_edit_cancel_submit($form, &$form_state) { $destination = ''; if (isset($_REQUEST['destination'])) { $destination = drupal_get_destination(); unset($_REQUEST['destination']); } - // Note: We redirect from user/uid/edit to user/uid/delete to make the tabs disappear. - $form_state['redirect'] = array("user/" . $form_state['values']['_account']->uid . "/delete", $destination); + // Note: We redirect from user/uid/edit to user/uid/cancel to make the tabs disappear. + $form_state['redirect'] = array("user/" . $form_state['values']['_account']->uid . "/cancel", $destination); } /** - * Form builder; confirm form for user deletion. + * Form builder; confirm form for cancelling user account. * * @ingroup forms - * @see user_confirm_delete_submit() + * @see user_edit_cancel_submit() */ -function user_confirm_delete(&$form_state, $account) { +function user_cancel_confirm_form(&$form_state, $account) { + global $user; $form['_account'] = array('#type' => 'value', '#value' => $account); + // Display cancel account method selection, if allowed. + $default_method = variable_get('user_cancel_method', USER_CANCEL_BLOCK); + $admin_access = user_access('administer users'); + $can_select_method = user_access('select account cancelling method') || $admin_access; + $form['user_cancel_method'] = array( + '#type' => 'radios', + '#title' => ($account->uid == $user->uid ? t('When cancelling your account') : t('When cancelling the account')), + '#options' => user_cancel_methods(), + '#default_value' => $default_method, + '#access' => $can_select_method, + '#required' => TRUE, + ); + + // Allow user account administrators to skip the cancel account confirmation + // mail (by default), as long as they do not attempt to cancel their own + // accounts. + $override_access = $admin_access && ($account->uid != $user->uid); + $form['user_cancel_confirm'] = array( + '#type' => 'checkbox', + '#title' => t('Require e-mail confirmation to cancel account.'), + '#default_value' => ($override_access ? FALSE : TRUE), + '#access' => $override_access, + '#description' => t('When enabled, the user must confirm the account cancellation via e-mail.'), + ); + // Also allow to send account canceled notification mail, if enabled. + $default_notify = variable_get('user_mail_status_canceled_notify', FALSE); + $form['user_cancel_notify'] = array( + '#type' => 'checkbox', + '#title' => t('Notify user when account is canceled.'), + '#default_value' => ($override_access ? FALSE : $default_notify), + '#access' => $override_access && $default_notify, + '#description' => t('When enabled, the user will receive an e-mail notification after the account has been cancelled.'), + ); + + // Prepare confirmation form page title and description. + if ($account->uid == $user->uid) { + $question = t('Are you sure you want to cancel your account?'); + } + else { + $question = t('Are you sure you want to cancel the account %name?', array('%name' => $account->name)); + } + if ($can_select_method) { + $description = t('Select the method to cancel the account above.'); + } + else { + switch ($default_method) { + case USER_CANCEL_BLOCK: + default: + $description = ''; + break; + + case USER_CANCEL_BLOCK_UNPUBLISH: + $description = t('All your content will be unpublished.'); + break; + + case USER_CANCEL_ANONYMIZE: + $description = t('All your content will be assigned to the %anonymous-name user.', array('%anonymous-name' => variable_get('anonymous', t('Anonymous')))); + break; + + case USER_CANCEL_DELETE: + $description = t('All your content will be deleted.'); + break; + } + } + return confirm_form($form, - t('Are you sure you want to delete the account %name?', array('%name' => $account->name)), + $question, 'user/' . $account->uid, - t('All submissions made by this user will be attributed to the anonymous account. This action cannot be undone.'), - t('Delete'), t('Cancel')); + $description . ' ' . t('This action cannot be undone.'), + t('Cancel account'), t('Cancel')); } /** * Submit function for the confirm form for user deletion. + * + * @see user_cancel_confirm_form() + * @see user_multiple_cancel_confirm_submit() + */ +function user_cancel_confirm_form_submit($form, &$form_state) { + global $user; + $account = $form_state['values']['_account']; + + // Cancel account immediately, if the current user has administrative + // privileges, no confirmation mail shall be sent, and the user does not + // attempt to cancel the own account. + if (user_access('administer users') && empty($form_state['values']['user_cancel_confirm']) && $account->uid != $user->uid) { + user_cancel($form_state['values'], $account->uid, $form_state['values']['user_cancel_method']); + + if (!isset($_REQUEST['destination'])) { + $form_state['redirect'] = 'admin/user/user'; + } + } + else { + // Store cancelling method and whether to notify the user in $account for + // user_cancel_url(). + $account->user_cancel_method = $form_state['values']['user_cancel_method']; + $account->user_cancel_notify = $form_state['values']['user_cancel_notify']; + _user_mail_notify('cancel_confirm', $account); + drupal_set_message(t('A confirmation request to cancel your account has been sent to your e-mail address.')); + + if (!isset($_REQUEST['destination'])) { + $form_state['redirect'] = "user/$account->uid"; + } + } +} + +/** + * Helper function to return cancel account methods. + * + * @return + * An array containing all cancel account methods, suitable to be used as FAPI + * #options array. + * + * @see user_admin_settings() + * @see user_cancel_confirm_form() */ -function user_confirm_delete_submit($form, &$form_state) { - user_delete($form_state['values'], $form_state['values']['_account']->uid); - drupal_set_message(t('%name has been deleted.', array('%name' => $form_state['values']['_account']->name))); +function user_cancel_methods() { + return array( + USER_CANCEL_BLOCK => t('Disable the account and keep all content.'), + USER_CANCEL_BLOCK_UNPUBLISH => t('Disable the account and unpublish all content.'), + USER_CANCEL_ANONYMIZE => t('Delete the account and make all content belong to the %anonymous-name user.', array('%anonymous-name' => variable_get('anonymous', t('Anonymous')))), + USER_CANCEL_DELETE => t('Delete the account and all content.'), + ); +} - if (!isset($_REQUEST['destination'])) { - $form_state['redirect'] = 'admin/user/user'; +/** + * Menu callback; Cancel a user account via email confirmation link. + * + * @see user_cancel_confirm_form() + * @see user_cancel_url() + */ +function user_cancel_confirm($account, $timestamp, $method, $notify, $hashed_pass) { + // Time out in seconds until cancel URL expires; 24 hours = 86400 seconds. + $timeout = 86400; + $current = REQUEST_TIME; + + // Basic validation of arguments. + if (!empty($timestamp) && !empty($hashed_pass)) { + // Validate expiration and hashed password/login. + if ($timestamp <= $current && $current - $timestamp < $timeout && $account->uid && $timestamp >= $account->login && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { + $edit = array(); + user_cancel($edit, $account->uid, $method); + // Since user_cancel() is not invoked via Form API, batch processing needs + // to be invoked manually and should redirect to the front page after + // completion. + batch_process(''); + } + else { + drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.')); + drupal_goto("user/$account->uid/cancel"); + } } + drupal_access_denied(); } function user_edit_validate($form, &$form_state) {