user_delete.module in User Delete 5
Same filename and directory in other branches
User delete - Let users delete their own account.
This module will be abandoned when http://drupal.org/node/8 is fixed.
Note: As of January 8, 2009 - 09:44 the issue is marked as fixed. And commited to Drupal 7, see http://drupal.org/node/8#comment-1188824
@author Stefan Auditor <stefan.auditor@erdfisch.de>
TODO: Double check comment backup function Allow to admin defined a default setting for normal users Integrate with batchapi
File
user_delete.moduleView source
<?php
/**
* @file
* User delete - Let users delete their own account.
*
* This module will be abandoned when http://drupal.org/node/8 is fixed.
*
* Note: As of January 8, 2009 - 09:44 the issue is marked as fixed.
* And commited to Drupal 7, see http://drupal.org/node/8#comment-1188824
*
* @author
* Stefan Auditor <stefan.auditor@erdfisch.de>
*
* TODO:
* Double check comment backup function
* Allow to admin defined a default setting for normal users
* Integrate with batchapi
*/
define('USER_DELETE_FILE_PATH', file_directory_path() . '/user_delete_backup');
/**
* Implementation of hook_perm().
*/
function user_delete_perm() {
return array(
'delete own account',
);
}
/**
* Implementation of hook_menu().
*/
function user_delete_menu($may_cache) {
global $user;
$items = array();
if (!$may_cache) {
$items[] = array(
'path' => 'user/' . arg(1) . '/delete',
'title' => t('Delete'),
'callback' => 'user_edit',
'access' => user_access('administer users') || user_access('delete own account') && arg(1) == $user->uid,
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/user/user_delete',
'title' => t('User delete'),
'description' => t("Configure the user delete action."),
'callback' => 'drupal_get_form',
'callback arguments' => 'user_delete_settings',
'access' => user_access('administer users'),
);
}
return $items;
}
/**
* Implementation of hook_form_alter().
*/
function user_delete_form_alter($form_id, &$form) {
global $user;
if ($form_id == 'user_edit') {
//access check
if (user_access('delete own account') && $form['_account']['#value']->uid == $user->uid) {
$form['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#weight' => 31,
);
}
}
if ($form_id == 'user_confirm_delete') {
$description = '';
$default_op = variable_get('user_delete_default_action', 0);
if ($default_op) {
switch ($default_op) {
case 'user_delete_block':
$description = t('The account will be disabled, all submitted content will be kept.');
break;
case 'user_delete_block_unpublish':
$description = t('The account will be disabled, all submitted content will be unpublished.');
break;
case 'user_delete_reassign':
$description = t('The account will be deleted, all submitted content will be reassigned to the <em>Anonymous user</em>. This action cannot be undone.');
break;
case 'user_delete_delete':
$description = t('The account and all submitted content will be deleted. This action cannot be undone.');
break;
}
$form['description'] = array(
'#value' => $description,
'#weight' => -10,
);
}
if (variable_get('user_delete_backup', 0)) {
$form['user_delete_remark'] = array(
'#value' => t('All data that is being deleted will be backed up for %period and automatically deleted afterwards.', array(
'%period' => format_interval(variable_get('user_delete_backup_period', 60 * 60 * 24 * 7 * 12), 2),
)),
'#weight' => 0,
);
}
if (!variable_get('user_delete_default_action', 0)) {
$form['user_delete_action'] = array(
'#type' => 'radios',
'#title' => t('When deleting the account'),
'#default_value' => 'user_delete_block',
'#options' => array(
'user_delete_block' => t('Disable the account and keep all content.'),
'user_delete_block_unpublish' => t('Disable the account and unpublish all content.'),
'user_delete_reassign' => t('Delete the account and make all content belong to the <em>Anonymous user</em>.'),
'user_delete_delete' => t('Delete the account and all content.'),
),
'#weight' => 0,
);
}
$form['#redirect'] = 'user/' . $form['uid']['#value'];
$form['#submit'] = array(
'user_delete_submit' => array(),
);
}
}
/**
* Deal with the user/content after form submission
*/
function user_delete_submit($form_id, $form_values) {
global $user;
$default_op = variable_get('user_delete_default_action', 0);
$op = $default_op ? $default_op : $form_values['user_delete_action'];
$uid = $form_values['uid'];
$account = user_load(array(
'uid' => $uid,
));
$backup = variable_get('user_delete_backup', 0);
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;
}
switch ($op) {
case 'user_delete_block':
// block user
db_query("UPDATE {users} SET status = 0 WHERE uid = %d", $uid);
drupal_set_message(t('%name has been blocked.', array(
'%name' => check_plain($account->name),
)));
break;
case 'user_delete_block_unpublish':
// block user
db_query("UPDATE {users} SET status = 0 WHERE uid = %d", $uid);
// unpublish content
db_query("UPDATE {node} SET status = 0 WHERE uid = %d", $uid);
db_query("UPDATE {comments} SET status = 1 WHERE uid = %d", $uid);
drupal_set_message(t('%name has been blocked, all submitted content from that user has been unpublished.', array(
'%name' => check_plain($account->name),
)));
break;
case 'user_delete_reassign':
// Set redirect
$redirect = variable_get('user_delete_redirect', 'node');
// delete account
user_delete($form_values, $uid);
drupal_set_message(t('User record deleted. All submitted content from %name has been reassigned to %anonymous.', array(
'%name' => check_plain($account->name),
'%anonymous' => variable_get('anonymous', t('Anonymous')),
)));
break;
case 'user_delete_delete':
// TODO: Deleting/Backing-up nodes and comments should be done with
// http://drupal.org/project/batchapi
// Set redirect
$redirect = variable_get('user_delete_redirect', 'node');
// delete comments
$result = db_query("SELECT cid FROM {comments} WHERE uid = %d", $uid);
while ($row = db_fetch_object($result)) {
// backup
if ($backup) {
$comment = _comment_load($row->cid);
user_delete_backup($account, $comment);
}
user_delete_comment_delete($row->cid);
}
// delete nodes
$result = db_query("SELECT nid FROM {node} WHERE uid = %d", $uid);
while ($row = db_fetch_object($result)) {
// backup
if ($backup) {
$node = node_load($row->nid);
user_delete_backup($account, $node);
}
user_delete_node_delete($row->nid);
}
// backup
if ($backup) {
user_delete_backup($account);
}
// delete user
user_delete($form_values, $uid);
drupal_set_message(t('User record deleted. All submitted content from %name has been deleted.', array(
'%name' => check_plain($account->name),
'!anonymous' => variable_get('anonymous', t('Anonymous')),
)));
break;
}
// After cancelling account, ensure that user is logged out.
// Destroy the current session.
db_query("DELETE FROM {sessions} WHERE uid = %d", $account->uid);
if ($account->uid == $user->uid) {
// Load the anonymous user.
$user = drupal_anonymous_user();
// Set redirect
$redirect = variable_get('user_delete_redirect', 'node');
}
// Clear the cache for anonymous users.
cache_clear_all();
// Redirect
if (!empty($redirect)) {
drupal_goto($redirect);
}
}
/**
* Implementation of hook_cron().
*/
function user_delete_cron() {
user_delete_backup_scan_expired();
}
/**
* Backup an user/node/comment object to the filesystem
*/
function user_delete_backup($account, $object = NULL) {
// check for directory
$dir = USER_DELETE_FILE_PATH;
user_delete_file_check_directory($dir, TRUE);
file_check_directory($dir, TRUE);
$backup_dir = $dir . '/' . check_plain($account->name);
user_delete_file_check_directory($backup_dir, TRUE);
if (is_numeric($object->cid)) {
$dest = $backup_dir . '/comments';
user_delete_file_check_directory($dest, TRUE);
$dest = $dest . '/comment-' . $object->cid . '.txt';
}
else {
if (is_numeric($object->nid)) {
$dest = $backup_dir . '/nodes';
user_delete_file_check_directory($dest, TRUE);
$dest = $dest . '/node-' . $object->nid . '.txt';
}
else {
$dest = $backup_dir;
$object = $account;
user_delete_file_check_directory($dest, TRUE);
$dest = $dest . '/user-' . $object->uid . '.txt';
}
}
$data = serialize((array) $object);
file_save_data($data, $dest, FILE_EXISTS_REPLACE);
}
/**
* Scan for and delete expired files
*/
function user_delete_backup_scan_expired() {
// check for directory
$dir = USER_DELETE_FILE_PATH;
if (file_check_directory($dir, TRUE)) {
file_scan_directory($dir, '.*', array(
'.',
'..',
'CVS',
), 'user_delete_backup_remove_expired', FALSE);
}
}
/**
* Check if a folder is expired and delete
*/
function user_delete_backup_remove_expired($filename) {
$period = variable_get('user_delete_backup_period', 60 * 60 * 24 * 7 * 12);
$created = filemtime($filename);
if ($created && time() >= $created + $period) {
user_delete_backup_remove_dir($filename);
}
}
/**
* Recursive delete a folder with files
*/
function user_delete_backup_remove_dir($dir) {
if (!file_exists($dir)) {
return TRUE;
}
if (!is_dir($dir)) {
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!user_delete_backup_remove_dir($dir . DIRECTORY_SEPARATOR . $item)) {
return FALSE;
}
}
return rmdir($dir);
}
/**
* Copy of node_delete() whithout access check and drupal_set_message().
* see http://api.drupal.org/api/function/node_delete/5
*/
function user_delete_node_delete($nid) {
$node = node_load($nid);
db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
// Call the node-specific callback (if any):
node_invoke($node, 'delete');
node_invoke_nodeapi($node, 'delete');
// Clear the cache so an anonymous poster can see the node being deleted.
cache_clear_all();
// Remove this node from the search index if needed.
if (function_exists('search_wipe')) {
search_wipe($node->nid, 'node');
}
//drupal_set_message(t('%title has been deleted.', array('%title' => $node->title)));
watchdog('content', t('@type: deleted %title.', array(
'@type' => t($node->type),
'%title' => $node->title,
)));
}
/**
* Copy of file_check_directory() without drupal_set_message().
* see http://api.drupal.org/api/function/file_check_directory/5
*/
function user_delete_file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
$directory = rtrim($directory, '/\\');
// Check if directory exists.
if (!is_dir($directory)) {
if ($mode & FILE_CREATE_DIRECTORY && @mkdir($directory)) {
//drupal_set_message(t('The directory %directory has been created.', array('%directory' => $directory)));
@chmod($directory, 0775);
// Necessary for non-webserver users.
}
else {
if ($form_item) {
form_set_error($form_item, t('The directory %directory does not exist.', array(
'%directory' => $directory,
)));
}
return FALSE;
}
}
// Check to see if the directory is writable.
if (!is_writable($directory)) {
if ($mode & FILE_MODIFY_PERMISSIONS && @chmod($directory, 0775)) {
//drupal_set_message(t('The permissions of directory %directory have been changed to make it writable.', array('%directory' => $directory)));
}
else {
form_set_error($form_item, t('The directory %directory is not writable', array(
'%directory' => $directory,
)));
watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array(
'%directory' => $directory,
), WATCHDOG_ERROR);
return FALSE;
}
}
if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("{$directory}/.htaccess")) {
$htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
if (($fp = fopen("{$directory}/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
fclose($fp);
chmod($directory . '/.htaccess', 0664);
}
else {
$variables = array(
'%directory' => $directory,
'!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines)),
);
form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables));
watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables, WATCHDOG_ERROR);
}
}
return TRUE;
}
/**
* Administrative settings page
*
* @return array
* a form array
*/
function user_delete_settings() {
//TODO: add additional settings based on http://drupal.org/node/8#comment-628434
$form['defaults'] = array(
'#type' => 'fieldset',
'#title' => t('Defaults'),
);
$form['defaults']['user_delete_default_action'] = array(
'#type' => 'select',
'#title' => t('Default action when deleting'),
'#default_value' => variable_get('user_delete_default_action', 0),
'#options' => array(
0 => t('Let users choose action'),
'user_delete_block' => t('Disable the account and keep all content.'),
'user_delete_block_unpublish' => t('Disable the account and unpublish all content.'),
'user_delete_reassign' => t('Delete the account and make all content belong to the Anonymous user.'),
'user_delete_delete' => t('Delete the account and all content.'),
),
);
$form['redirect'] = array(
'#type' => 'fieldset',
'#title' => t('Redirect'),
);
$form['redirect']['user_delete_redirect'] = array(
'#type' => 'textfield',
'#title' => t('Redirection page'),
'#default_value' => variable_get('user_delete_redirect', 'node'),
'#description' => t('Choose where to redirect your users after account deletion. Any valid Drupal path will do, e.g. %front or %node', array(
'%front' => '<front>',
'%node' => 'node/1',
)),
);
$form['backup'] = array(
'#type' => 'fieldset',
'#title' => t('Backup'),
);
$form['backup']['user_delete_backup'] = array(
'#type' => 'checkbox',
'#title' => t('Backup data'),
'#default_value' => variable_get('user_delete_backup', 0),
'#description' => t('Backup data that is being deleted to the filesystem.'),
);
$options = array(
60 * 60 * 24 * 7 => format_interval(60 * 60 * 24 * 7, 2),
60 * 60 * 24 * 7 * 2 => format_interval(60 * 60 * 24 * 7 * 2, 2),
60 * 60 * 24 * 7 * 4 => format_interval(60 * 60 * 24 * 7 * 4, 2),
60 * 60 * 24 * 7 * 8 => format_interval(60 * 60 * 24 * 7 * 8, 2),
60 * 60 * 24 * 7 * 12 => format_interval(60 * 60 * 24 * 7 * 12, 2),
60 * 60 * 24 * 7 * 16 => format_interval(60 * 60 * 24 * 7 * 16, 2),
60 * 60 * 24 * 7 * 24 => format_interval(60 * 60 * 24 * 7 * 24, 2),
);
$form['backup']['user_delete_backup_period'] = array(
'#type' => 'select',
'#title' => t('Keep backup time'),
'#default_value' => variable_get('user_delete_backup_period', 60 * 60 * 24 * 7 * 12),
'#options' => $options,
'#description' => t('The time frame after which the backup should be deleted from the filesystem.'),
);
return system_settings_form($form);
}
/**
* Delete comment thread
*/
function user_delete_comment_delete($cid = NULL) {
$comment = db_fetch_object(db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', $cid));
_comment_delete_thread($comment);
_comment_update_node_statistics($comment->nid);
}
Functions
Name | Description |
---|---|
user_delete_backup | Backup an user/node/comment object to the filesystem |
user_delete_backup_remove_dir | Recursive delete a folder with files |
user_delete_backup_remove_expired | Check if a folder is expired and delete |
user_delete_backup_scan_expired | Scan for and delete expired files |
user_delete_comment_delete | Delete comment thread |
user_delete_cron | Implementation of hook_cron(). |
user_delete_file_check_directory | Copy of file_check_directory() without drupal_set_message(). see http://api.drupal.org/api/function/file_check_directory/5 |
user_delete_form_alter | Implementation of hook_form_alter(). |
user_delete_menu | Implementation of hook_menu(). |
user_delete_node_delete | Copy of node_delete() whithout access check and drupal_set_message(). see http://api.drupal.org/api/function/node_delete/5 |
user_delete_perm | Implementation of hook_perm(). |
user_delete_settings | Administrative settings page |
user_delete_submit | Deal with the user/content after form submission |
Constants
Name | Description |
---|---|
USER_DELETE_FILE_PATH | @file User delete - Let users delete their own account. |