/** delete a group after confirmation * * this either presents a confirmation dialog to the user OR deletes a group with * associated capacities and acls. * * Note that this routine could have been split into two routines, with the * first one displaying the confirmation dialog and the second one 'saving the changes'. * However, I think it is counter-intuitive to perform a deletion of data under * the name of 'saving'. So, I decided to use the same routine for both displaying * the dialog and acting on the dialog. * * @return void results are returned as output in $this->output * @todo since multiple tables are involved, shouldn't we use transaction/rollback/commit? * Q: How well is MySQL suited for transactions? A: Mmmmm.... Which version? Which storage engine? */ function group_delete() { global $WAS_SCRIPT_NAME, $DB, $USER; // // 0 -- sanity check // $group_id = get_parameter_int('group', NULL); if (is_null($group_id)) { logger(sprintf("%s.%s(): unspecified parameter group", __CLASS__, __FUNCTION__)); $this->output->add_message(t('error_invalid_parameters', 'admin')); $this->groups_overview(); return; } // // 1 -- bail out if the user pressed cancel button // if (isset($_POST['button_cancel'])) { $this->output->add_message(t('cancelled', 'admin')); $this->groups_overview(); return; } // 2A -- do not allow the user to remove a group associated with the user $where = array('user_id' => intval($USER->user_id), 'group_id' => $group_id); if (($record = db_select_single_record('users_groups_capacities', 'capacity_code', $where)) !== FALSE) { // Oops, the current user happens to be a member of this group $params = $this->get_group_capacity_names($group_id, $record['capacity_code']); logger(sprintf("%s.%s(): user attempts to remove group '%s' ('%d', '%s') but she is a '%s'", __CLASS__, __FUNCTION__, $params['{GROUP}'], $group_id, $params['{GROUP_FULL_NAME}'], $params['{CAPACITY}'])); $this->output->add_message(t('usermanager_delete_group_not_self', 'admin', $params)); $this->groups_overview(); return; } // 2B -- are there any files left in this user's private storage $CFG->datadir.'/groups/'.$path? if (($group = $this->get_group_record($group_id)) === FALSE) { $this->groups_overview(); return; } $path = '/groups/' . $group['path']; if (!userdir_is_empty($path)) { // At this point we know there are still files associated with this // group in the data directory. This is a show stopper; it is up to the // admin requesting this delete to get rid of the files first (eg via File Manager) logger(sprintf("%s.%s(): data directory '%s' not empty", __CLASS__, __FUNCTION__, $path)); $params = $this->get_group_capacity_names($group_id); $this->output->add_message(t('usermanager_delete_group_dir_not_empty', 'admin', $params)); $this->groups_overview(); return; } // // 3 -- user has confirmed delete? // if (isset($_POST['button_delete']) && isset($_POST['dialog']) && $_POST['dialog'] == GROUPMANAGER_DIALOG_DELETE) { $params = $this->get_group_capacity_names($group_id); // pick up name before it is gone if (userdir_delete($path) && $this->delete_group_capacities_records($group_id)) { $this->output->add_message(t('groupmanager_delete_group_success', 'admin', $params)); $retval = TRUE; } else { $this->output->add_message(t('groupmanager_delete_group_failure', 'admin', $params)); $retval = FALSE; } logger(sprintf("%s.%s(): %s deleting group '%d' %s (%s)", __CLASS__, __FUNCTION__, $retval === FALSE ? 'failure' : 'success', $group_id, $params['{GROUP}'], $params['{GROUP_FULL_NAME}'])); $this->groups_overview(); return; } // // 4 -- no delete yet, first show confirmation dialog // // Dialog is very simple: a simple text showing // - the name of the group // - the names of the associated capacities with number of users associated // - a Delete and a Cancel button // $dialogdef = array('dialog' => array('type' => F_INTEGER, 'name' => 'dialog', 'value' => GROUPMANAGER_DIALOG_DELETE, 'hidden' => TRUE), 'button_save' => dialog_buttondef(BUTTON_DELETE), 'button_cancel' => dialog_buttondef(BUTTON_CANCEL)); $params = $this->get_group_capacity_names($group_id); $header = t('groupmanager_delete_group_header', 'admin', $params); $this->output->add_content('<h2>' . $header . '</h2>'); $this->output->add_content(t('groupmanager_delete_group_explanation', 'admin')); $this->output->add_content('<ul>'); $this->output->add_content(' <li class="level0">' . t('groupmanager_delete_group_group', 'admin', $params)); $sql = sprintf("SELECT gc.capacity_code, COUNT(ugc.user_id) AS users " . "FROM %sgroups_capacities gc LEFT JOIN %susers_groups_capacities ugc " . "ON gc.group_id = ugc.group_id AND gc.capacity_code = ugc.capacity_code " . "WHERE gc.group_id = %d " . "GROUP BY gc.capacity_code " . "ORDER BY gc.sort_order", $DB->prefix, $DB->prefix, $group_id); if (($DBResult = $DB->query($sql)) !== FALSE) { $records = $DBResult->fetch_all_assoc(); $DBResult->close(); foreach ($records as $record) { $params['{CAPACITY}'] = capacity_name($record['capacity_code']); $params['{COUNT}'] = $record['users']; $line = t('groupmanager_delete_group_capacity', 'admin', $params); $this->output->add_content(' <li class="level0">' . $line); } } $this->output->add_content('</ul>'); $this->output->add_content(t('delete_are_you_sure', 'admin')); $a_params = $this->a_params(TASK_GROUP_DELETE, $group_id); $href = href($WAS_SCRIPT_NAME, $a_params); $this->output->add_content(dialog_quickform($href, $dialogdef)); $this->show_menu_group($group_id, TASK_GROUP_DELETE); $this->output->add_breadcrumb($WAS_SCRIPT_NAME, $a_params, array('title' => $header), t('groupmanager_delete_group_breadcrumb', 'admin')); }
/** delete an area from ths site after confirmation * * this either presents a confirmation dialog to the user OR deletes an area. * First the user's permissions are checked and also there should be no nodes left * in the area before anything is done. Only allowing deletion of an empty area * is safety measure: we don't want to accidently delete many many nodes from * an area in one go (see also {@link task_node_delete()}). Also, we don't want * to introduce orphaned node records (by deleting the area record without deleting * nodes). * * Note that this routine could have been split into two routines, with the * first one displaying the confirmation dialog and the second one 'saving the changes'. * However, I think it is counter-intuitive to perform a deletion of data under * the name of 'saving'. So, I decided to use the same routine for both displaying * the dialog and acting on the dialog. * * @return void results are returned as output in $this->output * @todo since multiple tables are involved, shouldn't we use transaction/rollback/commit? * Q: How well is MySQL suited for transactions? A: Mmmmm.... Which version? Which storage engine? * @uses $USER */ function area_delete() { global $USER; // 0 -- sane area_id for deletion? $area_id = get_parameter_int('area', 0); $areas = get_area_records(); if ($areas === FALSE || !isset($areas[$area_id])) { // are they trying to trick us, specifying an invalid area? logger("areamanager: weird: user tried to delete non-existing area '{$area_id}'"); $this->output->add_message(t('invalid_area', 'admin', array('{AREA}' => strval($area_id)))); $this->area_overview(); return; } // 1A -- are we allowed to perform delete operation? if (!$USER->has_site_permissions(PERMISSION_SITE_DROP_AREA)) { logger("areamanager: user attempted to delete area '{$area_id}' without permission"); $msg = t('icon_area_delete_access_denied', 'admin'); $this->output->add_message($msg); $this->output->add_popup_bottom($msg); $this->area_overview(); return; } // 1B -- bail out if the user pressed cancel button // if (isset($_POST['button_cancel'])) { $this->output->add_message(t('cancelled', 'admin')); $this->area_overview(); return; } // 2 -- basic tests (showstoppers): any nodes or files left? $error_count = 0; // sentinel // 2A -- are there any nodes left in the area? $record = db_select_single_record('nodes', 'count(*) as nodes', array('area_id' => $area_id)); if ($record === FALSE) { logger("areamanager: deletion of area '{$area_id}' failed: " . db_errormessage()); $params = array('{AREA}' => $area_id, '{AREA_FULL_NAME}' => $areas[$area_id]['title']); $this->output->add_message(t('error_deleting_area', 'admin', $params)); ++$error_count; } elseif ($record['nodes'] > 0) { $nodes = $record['nodes']; logger("areamanager: cannot delete area '{$area_id}' because {$nodes} nodes still exist in that area'"); $params = array('{AREA}' => $area_id, '{AREA_FULL_NAME}' => $areas[$area_id]['title'], '{NODES}' => strval($nodes)); $this->output->add_message(t('error_deleting_area_not_empty', 'admin', $params)); ++$error_count; } // 2B -- are there any files left in this area $path = '/areas/' . $areas[$area_id]['path']; if (!userdir_is_empty($path)) { // At this point we know there are still files associated with this // area in the data directory. This is a show stopper; it is up to the // admin requesting this delete to get rid of the files first (eg via File Manager) logger(sprintf("%s.%s(): data directory '%s' not empty", __CLASS__, __FUNCTION__, $path)); $params = array('{AREA}' => $area_id, '{AREA_FULL_NAME}' => $areas[$area_id]['title']); $this->output->add_message(t('error_deleting_area_dir_not_empty', 'admin', $params)); ++$error_count; } if ($error_count > 0) { $this->area_overview(); return; } // 3 -- actually perform the delete operation OR show the confirmation dialog if (isset($_POST['button_delete']) && isset($_POST['dialog']) && $_POST['dialog'] == AREAMANAGER_DIALOG_DELETE) { // stage 2 - actual delete confirmed $error_count = 0; if (!userdir_delete($path)) { ++$error_count; } // clean up a bunch of tables associated with this area in the correct order (to satisfy FK constraints) $where = array('area_id' => $area_id); $tables = array('themes_areas_properties', 'alerts_areas_nodes', 'acls_areas', 'acls_modules_areas', 'areas'); // db_start_transaction(); foreach ($tables as $table) { if (($rowcount = db_delete($table, $where)) === FALSE) { logger(sprintf("%s.%s(): delete area '%d' from table '%s' failed: %s", __CLASS__, __FUNCTION__, $area_id, $table, db_errormessage())); ++$error_count; } else { logger(sprintf("%s.%s(): delete area '%d' from table '%s' succeeded: %d records deleted", __CLASS__, __FUNCTION__, $area_id, $table, $rowcount), WLOG_DEBUG); } } $params = array('{AREA}' => $area_id, '{AREA_FULL_NAME}' => $areas[$area_id]['title']); if ($error_count == 0) { // db_commit_transaction() logger(sprintf("%s.%s(): successfully deleted area '%d'", __CLASS__, __FUNCTION__, $area_id)); $this->output->add_message(t('area_deleted', 'admin', $params)); } else { // db_rollback_transaction(); // break; $this->output->add_message(t('error_deleting_area', 'admin', $params)); } $areas = get_area_records(TRUE); // force re-read of areas after deletion $this->area_overview(); } else { // stage 1 - show confirmation dialog $this->show_dialog_confirm_delete($area_id, $areas); } return; }
/** delete a user after confirmation * * after some basic tests this either presents a confirmation dialog to the user OR * deletes a user with associated acls and other records. * * Note that this routine could have been split into two routines, with the * first one displaying the confirmation dialog and the second one 'saving the changes'. * However, I think it is counter-intuitive to perform a deletion of data under * the name of 'saving'. So, I decided to use the same routine for both displaying * the dialog and acting on the dialog. * * Note that the (user)files should be removed before the account can be removed, * see {@link userdir_is_empty()}. It is up to the user or the admin to remove those files. * * A special test is performed to prevent users from killing their own account (which would * immediately kick them out of admin.php never to be seen again). * * @return void results are returned as output in $this->output * @todo since multiple tables are involved, shouldn't we use transaction/rollback/commit? * Q: How well is MySQL suited for transactions? A: Mmmmm.... Which version? Which storage engine? */ function user_delete() { global $WAS_SCRIPT_NAME, $DB, $USER; // // 0 -- sanity check // $user_id = get_parameter_int('user', NULL); if (is_null($user_id)) { logger(sprintf("%s.%s(): unspecified parameter user", __CLASS__, __FUNCTION__)); $this->output->add_message(t('error_invalid_parameters', 'admin')); $this->users_overview(); return; } // // 1 -- bail out if the user pressed cancel button // if (isset($_POST['button_cancel'])) { $this->output->add_message(t('cancelled', 'admin')); $this->users_overview(); return; } // 2A -- do not allow the user to commit suicide if (intval($USER->user_id) == intval($user_id)) { logger(sprintf("%s.%s(): user attempts to kill her own user account", __CLASS__, __FUNCTION__)); $this->output->add_message(t('usermanager_delete_user_not_self', 'admin')); $this->users_overview(); return; } // 2B -- are there any files left in this user's private storage $CFG->datadir.'/users/'.$path? if (($user = $this->get_user_record($user_id)) === FALSE) { $this->users_overview(); return; } $path = '/users/' . $user['path']; if (!userdir_is_empty($path)) { // At this point we know there are still files associated with this // user in the data directory. This is a show stopper; it is up to the // admin requesting this delete to get rid of the files first (eg via File Manager) logger(sprintf("%s.%s(): data directory '%s' not empty", __CLASS__, __FUNCTION__, $path)); $params = $this->get_user_names($user_id); // pick up username $this->output->add_message(t('usermanager_delete_user_dir_not_empty', 'admin', $params)); $this->users_overview(); return; } // // 3 -- user has confirmed delete? // if (isset($_POST['button_delete']) && isset($_POST['dialog']) && intval($_POST['dialog']) == USERMANAGER_DIALOG_DELETE) { $params = $this->get_user_names($user_id); // pick up name before it is gone if (userdir_delete($path) && $this->delete_user_records($user_id)) { $this->output->add_message(t('usermanager_delete_user_success', 'admin', $params)); $retval = TRUE; } else { $this->output->add_message(t('usermanager_delete_user_failure', 'admin', $params)); $retval = FALSE; } logger(sprintf("%s.%s(): %s deleting user '%d' %s (%s)", __CLASS__, __FUNCTION__, $retval === FALSE ? 'failure' : 'success', $user_id, $params['{USERNAME}'], $params['{FULL_NAME}'])); $this->users_overview(); return; } // // 4 -- no delete yet, first show confirmation dialog // // Dialog is very simple: a simple text showing // - the name of the user // - a Delete and a Cancel button // $dialogdef = array(array('type' => F_INTEGER, 'name' => 'dialog', 'value' => USERMANAGER_DIALOG_DELETE, 'hidden' => TRUE), dialog_buttondef(BUTTON_DELETE), dialog_buttondef(BUTTON_CANCEL)); $params = $this->get_user_names($user_id); $header = t('usermanager_delete_user_header', 'admin', $params); $this->output->add_content('<h2>' . $header . '</h2>'); $this->output->add_content(t('usermanager_delete_user_explanation', 'admin')); $this->output->add_content('<ul>'); $this->output->add_content(' <li class="level0">' . t('usermanager_delete_user_user', 'admin', $params)); $this->output->add_content('</ul>'); $this->output->add_content(t('delete_are_you_sure', 'admin')); $a_params = $this->a_params(TASK_USER_DELETE, $user_id); $href = href($WAS_SCRIPT_NAME, $a_params); $this->output->add_content(dialog_quickform($href, $dialogdef)); $this->show_menu_user($user_id, TASK_USER_DELETE); $this->output->add_breadcrumb($WAS_SCRIPT_NAME, $a_params, array('title' => $header), t('usermanager_delete_user_breadcrumb', 'admin')); }