Skip to content

Commit 2c732bc

Browse files
committed
Feature: Allow the admin to configure cohorts which should be ignored by the tool.
1 parent f149d2a commit 2c732bc

9 files changed

+271
-9
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ moodle-tool_selfsignuphardlifecycle
44
Changes
55
-------
66

7+
### Unreleased
8+
9+
* 2024-07-30 - Feature: Allow the admin to configure cohorts which should be ignored by the tool.
10+
711
### v4.1-r1
812

913
* 2024-07-28 - Upgrade: Fix a Behat test which broke von Moodle 4.1.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Here, you can optionally configure the number of days after which a user will be
5959

6060
Here, you can allow the admin to override deletion and suspension dates for individual users.
6161

62+
#### 1.6 Cohort exceptions
63+
64+
Here, you can optionally configure cohorts which should be ignored by the tool.
65+
6266
### 2. User list
6367

6468
On this page, there is a list which shows all users which are covered by this tool according to the current configuration. You will also see the current status of each user and when the next step of the user's hard lifecycle will happen.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* Admin tool "Hard life cycle for self-signup users" - Settings class file
19+
*
20+
* @package tool_selfsignuphardlifecycle
21+
* @copyright 2024 Alexander Bias, lern.link GmbH <[email protected]>
22+
* @copyright based on admin_setting_configselect_autocomplete in Moodle core.
23+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24+
*/
25+
26+
namespace tool_selfsignuphardlifecycle;
27+
28+
/**
29+
* Class used for selecting multiple options with autocompletion.
30+
*
31+
* @package tool_selfsignuphardlifecycle
32+
* @copyright 2024 Alexander Bias, lern.link GmbH <[email protected]>
33+
* @copyright based on admin_setting_configselect_autocomplete in Moodle core.
34+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35+
*/
36+
class admin_setting_configmultiselect_autocomplete extends \admin_setting_configmultiselect {
37+
// In this class, we simply inherited from admin_setting_configmultiselect and copied everything from
38+
// admin_setting_configselect_autocomplete. And the multiselect widget worked automagically with autocompletion.
39+
40+
/** @var bool $tags Should we allow typing new entries to the field? */
41+
protected $tags = false;
42+
/** @var string $ajax Name of an AMD module to send/process ajax requests. */
43+
protected $ajax = '';
44+
/** @var string $placeholder Placeholder text for an empty list. */
45+
protected $placeholder = '';
46+
/** @var bool $casesensitive Whether the search has to be case-sensitive. */
47+
protected $casesensitive = false;
48+
/** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
49+
protected $showsuggestions = true;
50+
/** @var string $noselectionstring String that is shown when there are no selections. */
51+
protected $noselectionstring = '';
52+
53+
/**
54+
* Returns XHTML select field and wrapping div(s)
55+
*
56+
* @param array $data Array of values to select by default
57+
* @param string $query
58+
* @return string XHTML field and wrapping div
59+
*/
60+
public function output_html($data, $query='') {
61+
global $PAGE;
62+
63+
$html = parent::output_html($data, $query);
64+
65+
if ($html === '') {
66+
return $html;
67+
}
68+
69+
$this->placeholder = get_string('search');
70+
71+
$params = ['#' . $this->get_id(), $this->tags, $this->ajax,
72+
$this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring];
73+
74+
// Load autocomplete wrapper for select2 library.
75+
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params);
76+
77+
return $html;
78+
}
79+
}

classes/userlist_table.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,20 @@ public function __construct($uniqueid) {
5555
// Get SQL snippets for excludings admins and guests.
5656
list($admininsql, $adminsqlparams) = tool_selfsignuphardlifecycle_get_adminandguest_sql();
5757

58+
// Get SQL subquery for ignoring cohorts.
59+
list($cohortexceptionswhere, $cohortexceptionsparams) =
60+
tool_selfsignuphardlifecycle_get_cohort_exceptions_sql();
61+
5862
// Get plugin config.
5963
$config = get_config('tool_selfsignuphardlifecycle');
6064

6165
// Set the sql for the table.
6266
$sqlfields = 'id, firstname, lastname, username, email, auth, suspended, timecreated';
63-
$sqlwhere = 'deleted = :deleted AND auth '.$authinsql.' AND id '.$admininsql;
64-
$sqlparams = array_merge($authsqlparams, $adminsqlparams);
67+
$sqlfrom = '{user}';
68+
$sqlwhere = 'deleted = :deleted AND auth '.$authinsql.' AND id '.$admininsql.' '.$cohortexceptionswhere;
69+
$sqlparams = array_merge($authsqlparams, $adminsqlparams, $cohortexceptionsparams);
6570
$sqlparams['deleted'] = 0;
66-
$this->set_sql($sqlfields, '{user}', $sqlwhere, $sqlparams);
71+
$this->set_sql($sqlfields, $sqlfrom, $sqlwhere, $sqlparams);
6772

6873
// Set the table columns (depending if user overrides are enabled or not).
6974
if (tool_selfsignuphardlifecycle_user_overrides_enabled_and_configured() == true) {

lang/en/tool_selfsignuphardlifecycle.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,14 @@
4545
$string['profileedit'] = 'Edit';
4646
$string['profileview'] = 'View';
4747
$string['setting_authmethodsheading'] = 'Authentication methods';
48+
$string['setting_cohortexceptionsheading'] = 'Cohort exceptions';
49+
$string['setting_cohortexceptions'] = 'Cohorts to ignore';
50+
$string['setting_cohortexceptions_desc'] = 'With this setting, you can configure the cohorts whose members should be ignored. Each member of one of the selected cohorts will be completely ignored by this tool.';
51+
$string['setting_cohortexceptionsnocohortyet_desc'] = 'With this setting, you can configure the cohorts whose members should be ignored. There isn\'t any usable cohort yet. Please go to <a href="{$a->url}">{$a->linktitle}</a> and create a cohort first.';
4852
$string['setting_coveredauth'] = 'Covered authentication methods';
4953
$string['setting_coveredauth_desc'] = 'With this setting, you can configure which users are covered by this tool. If you select a particular authentication method, all users with this authentication method will become candidates for (suspension and) deletion. If you do not select a particular authentication method, all users with this authentication method will not be touched by this tool in any way.';
54+
$string['setting_enablecohortexceptions'] = 'Enable cohort exceptions';
55+
$string['setting_enablecohortexceptions_desc'] = 'With this setting, you can define cohort exceptions.';
5056
$string['setting_enableuseroverrides'] = 'Enable user overrides';
5157
$string['setting_enableuseroverrides_desc'] = 'With this setting, you can allow the admin to override deletion and suspension dates for individual users.';
5258
$string['setting_enableusersuspension'] = 'Enable user suspension before deletion';

locallib.php

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
define('TOOL_SELFSIGNUPHARDLIFECYCLLE_SUSPENSIONPERIOD_DEFAULT', 100);
2828
define('TOOL_SELFSIGNUPHARDLIFECYCLLE_ENABLESUSPENSION_DEFAULT', 1);
2929
define('TOOL_SELFSIGNUPHARDLIFECYCLLE_ENABLEOVERRIDES_DEFAULT', 0);
30+
define('TOOL_SELFSIGNUPHARDLIFECYCLLE_ENABLECOHORTEXCEPTIONS_DEFAULT', 0);
3031

3132

3233
/**
@@ -51,6 +52,10 @@ function tool_selfsignuphardlifecycle_process_lifecycle() {
5152
// Get SQL snippets for covered auth methods.
5253
list($authinsql, $authsqlparams) = tool_selfsignuphardlifecycle_get_auth_sql();
5354

55+
// Get SQL subquery for ignoring cohorts.
56+
list($cohortexceptionswhere, $cohortexceptionsparams) =
57+
tool_selfsignuphardlifecycle_get_cohort_exceptions_sql();
58+
5459
// PHASE 1: Overridden users.
5560

5661
// Do only if user override is enabled.
@@ -61,6 +66,7 @@ function tool_selfsignuphardlifecycle_process_lifecycle() {
6166
$usersparams['deleted'] = 0;
6267
$usersparams['deletionoverridefieldid'] = $config->userdeletionoverridefield;
6368
$usersparams['suspensionoverridefieldid'] = $config->usersuspensionoverridefield;
69+
$usersparams = array_merge($usersparams, $cohortexceptionsparams);
6470
$userssql = 'SELECT u.*,
6571
(SELECT uid.data
6672
FROM {user_info_data} uid
@@ -73,7 +79,7 @@ function tool_selfsignuphardlifecycle_process_lifecycle() {
7379
AND uid.fieldid = :suspensionoverridefieldid
7480
) AS suspensionoverride
7581
FROM {user} u
76-
WHERE u.auth '.$authinsql.'
82+
WHERE u.auth '.$authinsql.' '.$cohortexceptionswhere.'
7783
AND u.deleted = :deleted
7884
ORDER BY u.id ASC';
7985
$usersrs = $DB->get_recordset_sql($userssql, $usersparams);
@@ -194,11 +200,13 @@ function tool_selfsignuphardlifecycle_process_lifecycle() {
194200
$deleteusersparams['timecreated'] = $userdeletiondatets;
195201
$deleteusersparams['suspended'] = 1;
196202
$deleteusersparams['deleted'] = 0;
203+
$deleteusersparams = array_merge($deleteusersparams, $cohortexceptionsparams);
197204
$deleteuserssql = 'SELECT *
198205
FROM {user}
199206
WHERE auth '.$authinsql.'
200207
AND timecreated < :timecreated '.
201-
$suspendedsqlsnippet.'
208+
$suspendedsqlsnippet.' '.
209+
$cohortexceptionswhere.'
202210
AND deleted = :deleted
203211
ORDER BY id ASC';
204212
$deleteusersrs = $DB->get_recordset_sql($deleteuserssql, $deleteusersparams);
@@ -263,10 +271,12 @@ function tool_selfsignuphardlifecycle_process_lifecycle() {
263271
$suspendusersparams['timecreated'] = $usersuspensiondatets;
264272
$suspendusersparams['suspended'] = 0;
265273
$suspendusersparams['deleted'] = 0;
274+
$suspendusersparams = array_merge($suspendusersparams, $cohortexceptionsparams);
266275
$suspenduserssql = 'SELECT *
267276
FROM {user}
268277
WHERE auth ' . $authinsql . '
269-
AND timecreated < :timecreated
278+
AND timecreated < :timecreated '.
279+
$cohortexceptionswhere.'
270280
AND suspended = :suspended
271281
AND deleted = :deleted
272282
ORDER BY id ASC';
@@ -702,3 +712,55 @@ function tool_selfsignuphardlifecycle_user_overrides_enabled_and_configured() {
702712
// Return the result.
703713
return $retvalue;
704714
}
715+
716+
/**
717+
* Helper function to get the SQL WHERE subquery for the cohorts which should be ignored by the plugin.
718+
*
719+
* @return array An array with two elements:
720+
* The first element is a SQL WHERE snippet.
721+
* The second element is a param array.
722+
*/
723+
function tool_selfsignuphardlifecycle_get_cohort_exceptions_sql() {
724+
global $DB;
725+
726+
// Get plugin config.
727+
$config = get_config('tool_selfsignuphardlifecycle');
728+
729+
// If cohort exceptions are not enabled, return an all-empty array.
730+
if ($config->enablecohortexceptions == false) {
731+
return ['', []];
732+
}
733+
734+
// Use a static array to cache the results of this function as it might be called multiple times per user.
735+
static $cohortexceptions = [];
736+
static $staticcachebuilt = false;
737+
738+
// If we did not compose the cohort exceptions yet.
739+
if ($staticcachebuilt === false) {
740+
// Explode the cohort exception configuration.
741+
$cohorts = explode(',', $config->cohortexceptions);
742+
743+
// If no cohorts are set, return an all-empty array.
744+
if (count($cohorts) < 1) {
745+
return ['', []];
746+
}
747+
748+
// Compose the WHERE subquery.
749+
list($whereinsql, $whereinparams) = $DB->get_in_or_equal($cohorts, SQL_PARAMS_NAMED, 'cohort', true);
750+
$where = 'AND NOT EXISTS (
751+
SELECT 1
752+
FROM {cohort_members}
753+
WHERE {cohort_members}.userid = {user}.id
754+
AND {cohort_members}.cohortid '.$whereinsql.'
755+
)';
756+
757+
// Compose the cohort exceptions array.
758+
$cohortexceptions = [$where, $whereinparams];
759+
760+
// Remember that we have built it.
761+
$staticcachebuilt = true;
762+
}
763+
764+
// Return the cohort exceptions array.
765+
return $cohortexceptions;
766+
}

settings.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2323
*/
2424

25+
use tool_selfsignuphardlifecycle\admin_setting_configmultiselect_autocomplete;
26+
2527
defined('MOODLE_INTERNAL') || die;
2628

2729
global $CFG;
@@ -39,6 +41,9 @@
3941
// Require the necessary libraries.
4042
require_once($CFG->dirroot . '/admin/tool/selfsignuphardlifecycle/locallib.php');
4143

44+
// Require cohort library.
45+
require_once($CFG->dirroot . '/cohort/lib.php');
46+
4247
// Create hard life cycle description static widget.
4348
$setting = new admin_setting_heading('tool_selfsignuphardlifecycle/userlifecyclestatic',
4449
'',
@@ -177,6 +182,56 @@
177182
'tool_selfsignuphardlifecycle/enableusersuspension');
178183
}
179184
unset($userprofilefieldoptions);
185+
186+
// Create cohort exceptions heading widget.
187+
$setting = new admin_setting_heading('tool_selfsignuphardlifecycle/cohortexceptionsheading',
188+
get_string('setting_cohortexceptionsheading', 'tool_selfsignuphardlifecycle', null, true),
189+
'');
190+
$page->add($setting);
191+
192+
// Create enable cohort exceptions widget.
193+
$setting = new admin_setting_configcheckbox('tool_selfsignuphardlifecycle/enablecohortexceptions',
194+
get_string('setting_enablecohortexceptions', 'tool_selfsignuphardlifecycle', null, true),
195+
get_string('setting_enablecohortexceptions_desc', 'tool_selfsignuphardlifecycle', null, true),
196+
TOOL_SELFSIGNUPHARDLIFECYCLLE_ENABLECOHORTEXCEPTIONS_DEFAULT);
197+
$page->add($setting);
198+
199+
// Get cohort options.
200+
$cohortdata = cohort_get_all_cohorts(0, 0);
201+
$cohortoptions = [];
202+
foreach ($cohortdata['cohorts'] as $cohort) {
203+
$cohortoptions[$cohort->id] = $cohort->name;
204+
}
205+
206+
// If there aren't any cohorts yet.
207+
if (count($cohortoptions) < 1) {
208+
// Build settings page link.
209+
$url = new moodle_url('/cohort/index.php');
210+
$link = ['url' => $url->out(), 'linktitle' => get_string('cohorts', 'core_cohort', null, true)];
211+
212+
// Create empty cohort exceptions field widget to trigger a settings entry in the database.
213+
$setting = new admin_setting_configempty('tool_selfsignuphardlifecycle/cohortexceptions',
214+
get_string('setting_cohortexceptions', 'tool_selfsignuphardlifecycle', null, true),
215+
get_string('setting_cohortexceptionsnocohortyet_desc', 'tool_selfsignuphardlifecycle', $link, true));
216+
$page->add($setting);
217+
$page->hide_if('tool_selfsignuphardlifecycle/cohortexceptions',
218+
'tool_selfsignuphardlifecycle/enablecohortexceptions');
219+
220+
unset ($link, $url);
221+
222+
// Otherwise, if there are cohorts.
223+
} else {
224+
// Create user deletion override field widget.
225+
$setting = new admin_setting_configmultiselect_autocomplete('tool_selfsignuphardlifecycle/cohortexceptions',
226+
get_string('setting_cohortexceptions', 'tool_selfsignuphardlifecycle', null, true),
227+
get_string('setting_cohortexceptions_desc', 'tool_selfsignuphardlifecycle', null, true),
228+
[],
229+
$cohortoptions);
230+
$page->add($setting);
231+
$page->hide_if('tool_selfsignuphardlifecycle/cohortexceptions',
232+
'tool_selfsignuphardlifecycle/enablecohortexceptions');
233+
}
234+
unset($cohortoptions);
180235
}
181236

182237
// Add settings page to navigation category.

tests/behat/tool_selfsignuphardlifecycle.feature

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Feature: The hard life cycle for self-signup users tool allows admins to get rid
66

77
Background:
88
Given the following config values are set as admin:
9-
| coveredauth | email | tool_selfsignuphardlifecycle |
10-
| userdeletionperiod | 200 | tool_selfsignuphardlifecycle |
9+
| coveredauth | email | tool_selfsignuphardlifecycle |
10+
| userdeletionperiod | 200 | tool_selfsignuphardlifecycle |
1111

1212
Scenario: Manual authenticated users remain untouched by the tool
1313
Given the following "users" exist:
@@ -249,3 +249,50 @@ Feature: The hard life cycle for self-signup users tool allows admins to get rid
249249
And I should not see "User 3" in the "#users" "css_element"
250250
And I should see "User 4" in the "#users" "css_element"
251251
And ".usersuspended" "css_element" should exist in the "User 4" "table_row"
252+
253+
@javascript
254+
Scenario: Users from ignored cohorts remain untouched by the tool
255+
Given the following "users" exist:
256+
| username | firstname | lastname | email | auth | suspended | timecreated |
257+
# User 1 will be ignored as he is a member of an ignored cohort.
258+
| user1 | User | 1 | user1@example.com | email | 0 | ## 201 days ago ## |
259+
# User 2 will be suspended as he is not a member of an ignored cohort.
260+
| user2 | User | 2 | user2@example.com | email | 0 | ## 201 days ago ## |
261+
# User 3 will be suspended as he is not a member of any cohort at all.
262+
| user3 | User | 3 | user3@example.com | email | 0 | ## 201 days ago ## |
263+
And the following "cohorts" exist:
264+
| name | idnumber |
265+
| Cohort 1 | C1 |
266+
| Cohort 2 | C2 |
267+
| Cohort 3 | C3 |
268+
And the following "cohort members" exist:
269+
| user | cohort |
270+
| user1 | C1 |
271+
| user2 | C2 |
272+
And I log in as "admin"
273+
And I navigate to "Users > Hard life cycle for self-signup users > Settings" in site administration
274+
And I set the field "Enable cohort exceptions" to "1"
275+
And I click on ".form-autocomplete-downarrow" "css_element" in the "#admin-cohortexceptions" "css_element"
276+
And I click on "Cohort 1" item in the autocomplete list
277+
And I click on "Cohort 3" item in the autocomplete list
278+
And I press the escape key
279+
And I click on "Save changes" "button"
280+
281+
When I navigate to "Users > Hard life cycle for self-signup users > User list" in site administration
282+
Then I should not see "user1" in the "#region-main" "css_element"
283+
And I should see "user2" in the "#region-main" "css_element"
284+
And I should see "user2" in the "#region-main" "css_element"
285+
286+
And I navigate to "Users > Accounts > Browse list of users" in site administration
287+
Then I should see "User 1" in the "#users" "css_element"
288+
And I should see "User 2" in the "#users" "css_element"
289+
And I should see "User 3" in the "#users" "css_element"
290+
And ".usersuspended" "css_element" should not exist in the "User 1" "table_row"
291+
And ".usersuspended" "css_element" should not exist in the "User 2" "table_row"
292+
And ".usersuspended" "css_element" should not exist in the "User 3" "table_row"
293+
And I run the scheduled task "tool_selfsignuphardlifecycle\task\process_lifecycle"
294+
And I reload the page
295+
Then I should see "User 1" in the "#users" "css_element"
296+
And I should not see "User 2" in the "#users" "css_element"
297+
And I should not see "User 3" in the "#users" "css_element"
298+
And ".usersuspended" "css_element" should not exist in the "User 1" "table_row"

0 commit comments

Comments
 (0)