Commit 9837f182 authored by Cecilia Vela Gurovic's avatar Cecilia Vela Gurovic Committed by Robert Lyon

Bug 1734178: allow user to delete own account

added settings

- institution level: reviewselfdeletion
    0 if the institution does not require approval
      from an admin to delete an account
    1 if the institution requires an admin to approve
      account deletion requests from users
    if not set, it takes the value from the site's
    default

- site level: defaultreviewselfdeletion
  (Site options->User Settings -> Review account before self-deletion)
    1 if the site's default is requiring approval
    null otherwise

Account deletion by a user

when a user accesses to the account settings, a
'Delete account' button is displayed.

This will:
- If the user belongs to an institution that requires
  approval (or does not have the settings but the site
  requires approval by default)
    then a notification will be sent to the admins
    of the institutions that require approval that
    the user belongs to
- if the user belongs to institutions and none of them
  require approval (or does not have the setting
  but the site does not require approval by default)
    then the account is deleted
- if the user does not belong to any institution
    then the action will depend on the setting of
    the 'mahara' institution or sites default if
    'mahara' doesn't have the setting

Approval by institution admins

An institution admin can see the pending deletion
requests in Admin menu-> Institution -> Pending deletions
After approving/denying a request, the user
that requested the account deletion will receive
a notification

behatnotneeded
Change-Id: I4ccd9c798cab065ec557eaddf7dfc3a51920b6d0
parent 8aea91fd
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('MENUITEM', 'settings/preferences');
require(dirname(dirname(__FILE__)) . '/init.php');
define('TITLE', get_string('deleteaccount', 'account', display_name($USER, null, false, false, true)));
if (!$USER->can_delete_self()) {
throw new AccessDeniedException(get_string('accessdenied', 'error'));
}
$cancelrequestform = pieform(array(
'name' => 'cancelrequest',
'plugintype' => 'core',
'pluginname' => 'account',
'elements' => array(
'user' => array(
'type' => 'hidden',
'value' => $USER->id,
),
'submit' => array(
'type' => 'submitcancel',
'class' => 'btn-default',
'value' => array(get_string('yes'), get_string('no')),
'goto' => get_config('wwwroot') . "account/index.php",
),
),
));
function cancelrequest_submit(Pieform $form, $values) {
global $SESSION;
if ($request = get_record('usr_pendingdeletion', 'usr', $values['user'])) {
delete_records('usr_pendingdeletion', 'id', $request->id);
$userid = $values['user'];
$user = new User;
$user->find_by_id($userid);
$admins = $user->get_approval_admins();
$user->notify_admins_pending_deletion($admins, '', 2);
$SESSION->add_ok_msg(get_string('deleterequestcanceled', 'account'));
}
redirect('/account/index.php');
}
$smarty = smarty();
$smarty->assign('cancelrequestform', $cancelrequestform);
$smarty->assign('userdisplayname', display_name($USER));
$smarty->display('account/cancelrequest.tpl');
......@@ -13,34 +13,77 @@ define('INTERNAL', 1);
define('MENUITEM', 'settings/preferences');
require(dirname(dirname(__FILE__)) . '/init.php');
define('TITLE', get_string('deleteaccount', 'account'));
define('TITLE', get_string('deleteaccount', 'account', display_name($USER, null, false, false, true)));
if (!$USER->can_delete_self()) {
throw new AccessDeniedException(get_string('accessdenied', 'error'));
}
$deleteform = pieform(array(
$deleteform = array(
'name' => 'account_delete',
'plugintype' => 'core',
'pluginname' => 'account',
'elements' => array(
);
$userid = $USER->get('id');
$user = new User;
$user->find_by_id($userid);
$requiresapproval = $user->requires_delete_approval();
if ($requiresapproval) {
$elements = array(
'reason' => array(
'type' => 'textarea',
'title' => get_string('reason'),
'cols' => 50,
'rows' => 4,
'rules' => array('required' => true),
),
'submit' => array(
'class' => 'btn-default',
'type' => 'submit',
'value' => get_string('deleteaccount', 'mahara', display_username($USER), full_name($USER)),
'type' => 'submitcancel',
'value' => array(get_string('senddeletenotification', 'mahara'), get_string('back')),
'goto' => get_config('wwwroot'). 'account/index.php',
),
),
));
);
}
else {
$elements = array(
'submit' => array(
'class' => 'btn-default',
'type' => 'submitcancel',
'value' => array(get_string('deleteaccount1', 'mahara'), get_string('back')),
'goto' => get_config('wwwroot'). 'account/index.php',
),
);
}
$deleteform['elements'] = $elements;
$deleteform = pieform($deleteform);
function account_delete_submit(Pieform $form, $values) {
global $SESSION, $USER;
global $SESSION, $USER, $user;
$userid = $USER->get('id');
$USER->logout();
delete_user($userid);
$SESSION->add_ok_msg(get_string('accountdeleted', 'account'));
redirect('/index.php');
// check if user needs approval to delete its account
if (!$user->requires_delete_approval()) {
$USER->logout();
delete_user($userid);
$SESSION->add_ok_msg(get_string('accountdeleted', 'account'));
}
else {
$admins = $user->get_approval_admins();
set_account_pending_deletion($userid, strip_tags(clean_html($values['reason'])));
$user->notify_admins_pending_deletion($admins, $values['reason']);
$SESSION->add_ok_msg(get_string('pendingdeletionemailsent', 'account'));
}
redirect('/account/index.php');
}
$smarty = smarty();
$smarty->assign('requiresapproval', $requiresapproval);
$smarty->assign('delete_form', $deleteform);
$smarty->assign('fullname', full_name($USER));
$smarty->assign('displayusername', display_username($USER));
$smarty->display('account/delete.tpl');
......@@ -116,6 +116,7 @@ if ($blogcount != 1 && $prefs->multipleblogs == 1) {
$elements['multipleblogs']['readonly'] = true;
}
$elements['submit'] = array(
'type' => 'submit',
'class' => 'btn-primary',
......@@ -281,8 +282,6 @@ function accountprefs_submit(Pieform $form, $values) {
$form->json_reply(PIEFORM_OK, $returndata);
}
$prefsform = pieform($prefsform);
$ijs = <<< EOF
......@@ -303,8 +302,13 @@ var clearPasswords = (function($) {
}
}(jQuery))
EOF;
$request = get_record('usr_pendingdeletion', 'usr', $USER->id);
$smarty = smarty();
$smarty->assign('form', $prefsform);
$smarty->assign('candeleteself', $USER->can_delete_self());
$smarty->assign('deletionsent', !empty($request));
$smarty->assign('requestdate', !empty($request) ? format_date(strtotime($request->ctime)) : '');
$smarty->assign('INLINEJAVASCRIPT', $ijs);
$smarty->display('account/index.tpl');
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('MENUITEM', 'settings/preferences');
require(dirname(dirname(__FILE__)) . '/init.php');
define('TITLE', get_string('deleteaccount', 'account', display_name($USER, null, false, false, true)));
if (!$USER->can_delete_self()) {
throw new AccessDeniedException(get_string('accessdenied', 'error'));
}
$deleteform = pieform(array(
'name' => 'account_resend',
'plugintype' => 'core',
'pluginname' => 'account',
'elements' => array(
'message' => array(
'type' => 'textarea',
'title' => get_string('message'),
'cols' => 50,
'rows' => 4,
'rules' => array('required' => true),
),
'submit' => array(
'class' => 'btn-default',
'type' => 'submit',
'value' => get_string('resenddeletionnotification', 'account'),
),
),
));
function account_resend_submit(Pieform $form, $values) {
global $SESSION, $USER;
$userid = $USER->get('id');
$user = new User;
$user->find_by_id($userid);
$admins = $user->get_approval_admins();
$user->notify_admins_pending_deletion($admins, strip_tags(clean_html($values['message'])), 1);
redirect('/account/index.php');
}
$smarty = smarty();
$smarty->assign('delete_form', $deleteform);
$smarty->display('account/resendnotification.tpl');
......@@ -326,6 +326,13 @@ $siteoptionform = array(
'help' => true,
'disabled' => in_array('institutionautosuspend', $OVERRIDDEN),
),
'defaultreviewselfdeletion' => array(
'type' => 'switchbox',
'title' => get_string('defaultreviewselfdeletion', 'admin'),
'description' => get_string('defaultreviewselfdeletiondescription', 'admin'),
'defaultvalue' => get_config('defaultreviewselfdeletion'),
'disabled' => in_array('defaultreviewselfdeletion', $OVERRIDDEN),
),
),
),
'accountsettings' => array(
......@@ -816,6 +823,7 @@ function siteoptions_submit(Pieform $form, $values) {
'staffreports', 'staffstats', 'userscandisabledevicedetection', 'watchlistnotification_delay',
'masqueradingreasonrequired', 'masqueradingnotified', 'searchuserspublic',
'eventloglevel', 'eventlogexpiry', 'eventlogenhancedsearch', 'sitefilesaccess', 'exporttoqueue', 'defaultmultipleblogs',
'defaultreviewselfdeletion'
);
if (get_config('dropdownmenuenabled')) {
$fields = array_merge($fields, array('dropdownmenu'));
......
<?php
/**
*
* @package mahara
* @subpackage core
* @author Stacey Walker
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('INSTITUTIONALADMIN', 1);
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
define('SECTION_PLUGINTYPE', 'core');
define('SECTION_PLUGINNAME', 'admin');
define('SECTION_PAGE', 'actiondeletion');
require_once('institution.php');
$id = param_integer('d');
$action = param_alpha('action');
if (!is_logged_in()) {
throw new AccessDeniedException();
}
if (!$deletion = get_record_select('usr_pendingdeletion', '"id" = ?', array($id))) {
die_info(get_string('userdeletionnosuchid', 'auth.internal'));
}
$usertodelete = new User();
$usertodelete->find_by_id($deletion->usr);
if ($action == 'approve') {
$message = get_string('approveuserdeletionmessage', 'admin', $usertodelete->username);
$submitbtn = get_string('approve', 'admin');
define('TITLE', get_string('approveuserdeletionfor', 'admin',
$usertodelete->firstname, $usertodelete->lastname, $usertodelete->email));
}
else {
$message = get_string('denyuserdeletionmessage', 'admin');
$submitbtn = get_string('deny', 'admin');
define('TITLE', get_string('denyuserdeletionfor', 'admin',
$usertodelete->firstname, $usertodelete->lastname));
$elements['message'] = array(
'type' => 'textarea',
'title' => get_string('deletiondeniedreason', 'admin'),
'description' => get_string('deletiondeniedreasondesc', 'admin'),
'cols' => 50,
'rows' => 10,
);
}
foreach ((array)$deletion as $key => $value) {
$elements[$key] = array(
'type' => 'hidden',
'value' => $value,
);
}
$elements['submit'] = array(
'type' => 'submitcancel',
'value' => array($submitbtn, get_string('cancel')),
'class' => 'btn-primary',
'goto' => get_config('wwwroot') . 'admin/users/pendingdeletions.php'
);
$form = pieform(array(
'name' => $action.'deletion',
'autofocus' => false,
'method' => 'post',
'elements' => $elements,
));
$smarty = smarty();
$smarty->assign('message', $message);
$smarty->assign('form', $form);
$smarty->display('admin/users/actiondeletion.tpl');
function denydeletion_submit(Pieform $form, $values) {
global $USER, $SESSION, $deletion, $usertodelete;
if (isset($values['message']) && !empty($values['message'])) {
$message = get_string('userdeletiondeniedmessagereason', 'auth.internal',
$usertodelete->firstname, get_config('sitename'), $values['message'], display_name($USER));
}
else {
$message = get_string('userdeletiondeniedmessage', 'auth.internal',
$usertodelete->firstname, get_config('sitename'), display_name($USER));
}
try {
delete_records('usr_pendingdeletion', 'id', $values['id']);
email_user($usertodelete, $USER,
get_string('userdeletiondeniedemailsubject', 'auth.internal', get_config('sitename')),
$message
);
}
catch (EmailException $e) {
log_warn($e);
die_info(get_string('userdeletiondeniedunsuccessful', 'admin'));
}
catch (SQLException $e) {
log_warn($e);
die_info(get_string('userdeletiondeniedunsuccessful', 'admin'));
}
$SESSION->add_ok_msg(get_string('userdeletiondeniedsuccessful', 'admin'));
redirect('/admin/users/pendingdeletions.php');
}
function approvedeletion_submit(Pieform $form, $values) {
global $SESSION, $usertodelete, $USER;
// cant delete the last site admin
$admins = get_site_admins();
$lastadminid = 0;
if (count($admins)== 1) {
$lastadminid = $admins[0]->id;
}
$usercanbedeleted = $candeleteuser = false;
// Check if user can be deleted
if (isset($values['id']) && isset($values['usr'])
&& ($values['usr'] != 0)
&& ($values['usr'] != $USER->get('id'))
&& ($values['usr'] != $lastadminid)
&& ($usrdeletion = get_record('usr_pendingdeletion', 'id', $values['id']))
&& ($usrdeletion->usr == $values['usr'])) {
$usercanbedeleted = true;
}
if ($usercanbedeleted) {
// Now check if we are allowed to delete them
$userinstitutions = $usertodelete->get('institutions');
if (empty($userinstitutions) && $USER->get('admin')) {
// we are only in 'mahara' institution so can only be deleted by site admins
$candeleteuser = true;
}
else {
foreach ($userinstitutions as $i) {
if ($USER->can_edit_institution($i->institution)) {
// If $USER can edit any of the institutions that the $user belongs then they are allowed to delete the user
$candeleteuser = true;
break;
}
}
}
}
if ($usercanbedeleted && $candeleteuser) {
delete_records('usr_pendingdeletion', 'id', $values['id']);
//delete user account
delete_user($values['usr']);
// send the user the official account deletion email
email_user(
$usertodelete,
null,
get_string('userdeletionemailsubject', 'auth.internal', get_config('sitename')),
get_string(
'userdeletionemailmessagetext',
'auth.internal',
$usertodelete->firstname,
get_config('sitename'),
get_config('sitename')
),
get_string(
'userdeletionemailmessagehtml',
'auth.internal',
$usertodelete->firstname,
get_config('sitename'),
get_config('sitename')
)
);
$SESSION->add_ok_msg(get_string('deletionapprovedsuccessfully', 'admin'));
}
else {
$SESSION->add_error_msg(get_string('deletionapprovedfailed', 'admin'));
}
redirect('/admin/users/pendingdeletions.php');
}
......@@ -228,6 +228,7 @@ if ($institution || $add) {
$data->commentsortorder = get_config_institution($institution, 'commentsortorder');
$data->commentthreaded = get_config_institution($institution, 'commentthreaded');
$data->allowinstitutionsmartevidence = get_config_institution($institution, 'allowinstitutionsmartevidence');
$data->reviewselfdeletion = get_config_institution($institution, 'reviewselfdeletion');
$lockedprofilefields = (array) get_column('institution_locked_profile_field', 'profilefield', 'name', $institution);
// TODO: Find a better way to work around Smarty's minimal looping logic
......@@ -584,6 +585,13 @@ if ($institution || $add) {
'disabled' => is_plugin_active('framework', 'module') == false,
'help' => true,
);
$elements['reviewselfdeletion'] = array(
'type' => 'switchbox',
'title' => get_string('reviewselfdeletion', 'admin'),
'description' => get_string('reviewselfdeletiondescription','admin'),
'disabled' => get_config('defaultreviewselfdeletion') == true,
'defaultvalue' => get_config('defaultreviewselfdeletion') ? get_config('defaultreviewselfdeletion') : (isset($data->reviewselfdeletion) && $data->reviewselfdeletion),
);
$elements['lockedfields'] = array(
'type' => 'fieldset',
'class' => 'last with-formgroup',
......@@ -930,6 +938,12 @@ function institution_submit(Pieform $form, $values) {
$newinstitution->allowinstitutionpublicviews = (isset($values['allowinstitutionpublicviews']) && $values['allowinstitutionpublicviews']) ? 1 : 0;
$newinstitution->allowinstitutionsmartevidence = (isset($values['allowinstitutionsmartevidence']) && $values['allowinstitutionsmartevidence']) ? 1 : 0;
// do not set 'reviewselfdeletion' if it has never been changed at institution level
// and the value is the same as site setting 'defaultreviewselfdeletion'
if (get_config_institution($institution, 'reviewselfdeletion') != null || get_config('defaultreviewselfdeletion') != $values['reviewselfdeletion']) {
$newinstitution->reviewselfdeletion = $values['reviewselfdeletion'] ? 1 : 0;
}
// TODO: Move handling of authentication instances within the Institution class as well?
if (!empty($values['authplugin'])) {
$allinstances = array_merge($values['authplugin']['instancearray'], $values['authplugin']['deletearray']);
......
<?php
/**
*
* @package mahara
* @subpackage admin
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('INSTITUTIONALADMIN', 1);
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
define('TITLE', get_string('pendingdeletions', 'admin'));
define('SECTION_PLUGINTYPE', 'core');
define('SECTION_PLUGINNAME', 'admin');
define('SECTION_PAGE', 'pendingdeletions');
define('MENUITEM', 'manageinstitutions/pendingdeletions');
require_once('institution.php');
if (!is_logged_in()) {
throw new AccessDeniedException();
}
$institutionelement = get_institution_selector();
if (empty($institutionelement)) {
$smarty = smarty();
$smarty->display('admin/users/noinstitutions.tpl');
exit;
}
$institution = param_alphanum('institution', null);
if (!$institution || !$USER->can_edit_institution($institution)) {
$institution = empty($institutionelement['value']) ? $institutionelement['defaultvalue'] : $institutionelement['value'];
}
else if (!empty($institution)) {
$institutionelement['defaultvalue'] = $institution;
}
$institutionselector = pieform(array(
'name' => 'usertypeselect',
'class' => 'form-inline',
'elements' => array(
'institution' => $institutionelement,
)
));
if ($institution == 'mahara') {
$pending = get_records_sql_array('
SELECT d.*, u.id AS userid, u.username
FROM {usr_pendingdeletion} d
JOIN {usr} u ON d.usr = u.id
WHERE NOT EXISTS (SELECT * FROM {usr_institution} ui WHERE ui.usr = u.id)
ORDER BY d.ctime ASC'
);
}
else {
$instobj = new Institution($institution);
if ($instobj->requires_user_deletion_approval()) {
$pending = get_records_sql_array('
SELECT d.*, u.id AS userid, u.username
FROM {usr_pendingdeletion} d
JOIN {usr} u ON d.usr = u.id
JOIN {usr_institution} ui ON ui.usr = u.id
WHERE ui.institution = ?
ORDER BY d.ctime ASC',
array($institution)
);
}
}
if (!isset($pending) || !$pending) {
$pending = array();
}
function build_pending_html($data, $institution) {
foreach ($data as $d) {
$d->displayname = display_name($d->userid, null, true);
}
$smarty = smarty_core();
$smarty->assign('data', isset($data) ? $data : null);
$smarty->assign('institution', $institution);
$tablerows = $smarty->fetch('admin/users/pendingdeletionlist.tpl');
return $tablerows;
}
$data = build_pending_html($pending, $institution);
$wwwroot = get_config('wwwroot');
$js = <<< EOF
jQuery(function($) {
function reloadUsers() {
window.location.href = '{$wwwroot}admin/users/pendingdeletions.php?institution='+$('#usertypeselect_institution').val();
}
$('#usertypeselect_institution').on('change', reloadUsers);
});
EOF;
$smarty = smarty();
setpageicon($smarty, 'icon-university');
$smarty->assign('INLINEJAVASCRIPT', $js);
$smarty->assign('data', $data);
$smarty->assign('institutionselector', $institutionselector);
$smarty->display('admin/users/pendingdeletions.tpl');
......@@ -140,3 +140,44 @@ $string['pendingregistrationadminemailhtml'] = "<p>Hi %s,</p>
<pre>--
Regards,
The %s Team</pre>";
// pending user account deletion
$string['userdeletionnosuchid'] = 'Sorry, this deletion request does not exist. Perhaps it has already been evaluated?';
$string['userdeletiondeniedmessage'] = 'Hello %s,
We have received your request to delete your user account on %s and
decided not to delete your data.
If you think that this decision was incorrect, please get in touch with me
via email.
Regards
%s';
$string['userdeletiondeniedmessagereason'] = 'Hello %s,
We have received your request to delete your user account on %s and decided
not to delete your data for the following reason:
%s
If you think that this decision was incorrect, please get in touch with me
via email.
Regards
%s';
$string['userdeletiondeniedemailsubject'] = 'User account deletion attempt at %s denied.';
$string['userdeletionemailsubject'] = 'Your user account was deleted from %s';
$string['userdeletionemailmessagetext'] = 'Hello %s,