user_delete.module in User Delete 6
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 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
* 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() {
$items['admin/user/user_delete'] = array(
'title' => 'User delete',
'description' => "Configure the user delete action.",
'page callback' => 'drupal_get_form',
'page arguments' => array(
'user_delete_settings',
),
'access arguments' => array(
'administer users',
),
'file' => 'user_delete.admin.inc',
);
return $items;
}
/**
* Checks whether a user can delete an account.
*
* Unique function name required to avoid clash with core user_delete_access().
*/
function user_delete_user_delete_access($account) {
global $user;
return (user_access('administer users') || user_access('delete own account') && $account->uid == $user->uid) && $account->uid > 0;
}
/**
* Implementation of hook_menu_alter().
*/
function user_delete_menu_alter(&$callbacks) {
$callbacks['user/%user/delete']['access callback'] = 'user_delete_user_delete_access';
$callbacks['user/%user/delete']['access arguments'] = array(
1,
);
$callbacks['user/%user/delete']['type'] = MENU_CALLBACK;
}
/**
* Implementation of hook_form_alter().
*/
function user_delete_form_alter(&$form, $form_state, $form_id) {
global $user;
if ($form_id == 'user_profile_form') {
//access check
if (user_access('delete own account') && $form['#uid'] == $user->uid && arg(3) == '') {
$form['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete Account'),
'#weight' => 31,
'#submit' => array(
'user_edit_delete_submit',
),
);
}
}
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' => -10,
);
}
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['_account']['#value']->uid;
$form['#submit'] = array(
'user_delete_submit',
);
}
}
/**
* Deal with the user/content after form submission
*/
function user_delete_submit($form, &$form_state) {
global $user;
$default_op = variable_get('user_delete_default_action', 0);
$op = $default_op ? $default_op : $form_state['values']['user_delete_action'];
$uid = $form_state['values']['_account']->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);
$edit['status'] = 0;
module_invoke_all('user', 'update', $edit, $account);
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';
}
elseif (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 (user_delete_scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!user_delete_backup_remove_dir($dir . DIRECTORY_SEPARATOR . $item)) {
return FALSE;
}
}
return rmdir($dir);
}
/**
* Compat function for scandir() with php4 or php5
*/
function user_delete_scandir($dir) {
if (function_exists('scandir')) {
return scandir($dir);
}
else {
$dh = opendir($dir);
while (false !== ($filename = readdir($dh))) {
$files[] = $filename;
}
return $files;
}
}
/**
* Copy of node_delete() whithout access check and drupal_set_message().
* see http://api.drupal.org/api/function/node_delete/6
*/
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/6
*/
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;
}
/**
* Delete comment thread
*/
function user_delete_comment_delete($cid = NULL) {
include_once drupal_get_path('module', 'comment') . '/comment.admin.inc';
$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);
cache_clear_all();
}
Functions
Constants
Name | Description |
---|---|
USER_DELETE_FILE_PATH | @file User delete - Let users delete their own account. |