*
* 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
*/
function user_delete_access($account) {
global $user;
return (user_access('administer users') || (user_access('delete own account') && $account->uid == $user->uid));
}
/**
* Implementation of hook_menu_alter().
*/
function user_delete_menu_alter(&$callbacks) {
$callbacks['user/%user/delete']['access callback'] = '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) {
$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 Anonymous user. 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 Anonymous user.'),
'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);
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' => '
'. 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: !htaccess
", $variables));
watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: !htaccess
", $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();
}