Skip to content

Commit c5f373e

Browse files
authored
Merge pull request #93 from catalyst/issue42
issue #42: add user enrolment condition
2 parents 075c03e + 33f6554 commit c5f373e

File tree

5 files changed

+942
-4
lines changed

5 files changed

+942
-4
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ Conditions are simple predicates which assert something about a user in the syst
5959
* Cohort membership (if a user is a member of cohort(s)).
6060
* Course completed (if a user has completed a course).
6161
* Course not completed (if a user has not completed a course).
62-
* User last login (time since a user last logged in).
6362
* User created time (time since a user was created).
64-
* User standard profile fields (e.g. first name, last name, username, auth method and etc).
6563
* User custom profile fields (text and menu types are supported).
64+
* User enrolment (if a user is enrolled into a course).
65+
* User last login (time since a user last logged in).
6666
* User role (if a user has a role in a given context)
67+
* User standard profile fields (e.g. first name, last name, username, auth method and etc).
6768

6869
## Rules
6970

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
<?php
2+
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
16+
17+
namespace tool_dynamic_cohorts\local\tool_dynamic_cohorts\condition;
18+
19+
use tool_dynamic_cohorts\condition_base;
20+
use tool_dynamic_cohorts\condition_sql;
21+
use context_course;
22+
23+
/**
24+
* Condition based on user's enrolment.
25+
*
26+
* @package tool_dynamic_cohorts
27+
* @copyright 2024 Catalyst IT
28+
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29+
*/
30+
class user_enrolment extends condition_base {
31+
32+
/**
33+
* Operator for not enrolled users.
34+
*/
35+
public const OPERATOR_NOT_ENROLLED = 0;
36+
37+
/**
38+
* Operator for enrolled users.
39+
*/
40+
public const OPERATOR_ENROLLED = 1;
41+
42+
/**
43+
* Condition name.
44+
*
45+
* @return string
46+
*/
47+
public function get_name(): string {
48+
return get_string('condition:user_enrolment', 'tool_dynamic_cohorts');
49+
}
50+
51+
/**
52+
* Gets a list of all roles.
53+
*
54+
* @return array
55+
*/
56+
protected function get_roles(): array {
57+
$roles = [
58+
0 => get_string('any'),
59+
];
60+
foreach (role_get_names() as $role) {
61+
if ($role->archetype === 'guest') {
62+
continue;
63+
}
64+
$roles[$role->id] = $role->localname;
65+
}
66+
67+
return $roles;
68+
}
69+
70+
/**
71+
* Returns a list of enrolment methods.
72+
*
73+
* @return array
74+
*/
75+
protected function get_enrolment_methods(): array {
76+
$enrolmentmethods = ['' => get_string('any')];
77+
foreach (enrol_get_plugins(false) as $method) {
78+
$name = $method->get_name();
79+
$enrolmentmethods[$name] = get_string('pluginname', 'enrol_' . $name);
80+
}
81+
82+
return $enrolmentmethods;
83+
}
84+
85+
/**
86+
* Gets a list of operators.
87+
*
88+
* @return array A list of operators.
89+
*/
90+
protected function get_operators(): array {
91+
return [
92+
self::OPERATOR_ENROLLED => get_string('enrolled', 'tool_dynamic_cohorts'),
93+
self::OPERATOR_NOT_ENROLLED => get_string('notenrolled', 'tool_dynamic_cohorts'),
94+
];
95+
}
96+
97+
/**
98+
* Add config form elements.
99+
*
100+
* @param \MoodleQuickForm $mform
101+
*/
102+
public function config_form_add(\MoodleQuickForm $mform): void {
103+
// Operator.
104+
$mform->addElement(
105+
'select',
106+
'operator',
107+
get_string('operator', 'tool_dynamic_cohorts'),
108+
$this->get_operators()
109+
);
110+
111+
// Course.
112+
$mform->addElement('course', 'courseid', get_string('course'));
113+
$mform->setType('courseid', PARAM_INT);
114+
$mform->hideIf('courseid', 'contextlevel', 'in', [CONTEXT_SYSTEM, CONTEXT_COURSECAT]);
115+
116+
// Enrolment method.
117+
$mform->addElement(
118+
'select',
119+
'enrolmethod',
120+
get_string('enrolmethod', 'tool_dynamic_cohorts'),
121+
$this->get_enrolment_methods()
122+
);
123+
$mform->setType('enrolmethod', PARAM_COMPONENT);
124+
125+
// Role.
126+
$mform->addElement('select', 'roleid', get_string('role'), $this->get_roles());
127+
$mform->setType('roleid', PARAM_INT);
128+
}
129+
130+
/**
131+
* Validate config form elements.
132+
*
133+
* @param array $data Data to validate.
134+
* @return array
135+
*/
136+
public function config_form_validate(array $data): array {
137+
$errors = [];
138+
139+
if (empty($data['courseid'])) {
140+
$errors['courseid'] = get_string('required');
141+
}
142+
143+
return $errors;
144+
}
145+
146+
/**
147+
* Gets operator.
148+
*
149+
* @return int
150+
*/
151+
protected function get_operator_value(): int {
152+
return $this->get_config_data()['operator'] ?? self::OPERATOR_ENROLLED;
153+
}
154+
155+
/**
156+
* Gets configured role ID.
157+
*
158+
* @return int
159+
*/
160+
protected function get_roleid_value(): int {
161+
return $this->get_config_data()['roleid'] ?? 0;
162+
}
163+
164+
/**
165+
* Gets configured enrolment method.
166+
*
167+
* @return string
168+
*/
169+
protected function get_enrolment_method_value(): string {
170+
return $this->get_config_data()['enrolmethod'] ?? '';
171+
}
172+
173+
/**
174+
* Gets configured course ID.
175+
*
176+
* @return int
177+
*/
178+
protected function get_courseid_value(): int {
179+
return $this->get_config_data()['courseid'] ?? 0;
180+
}
181+
182+
/**
183+
* Human-readable description of the configured condition.
184+
*
185+
* @return string
186+
*/
187+
public function get_config_description(): string {
188+
global $DB;
189+
190+
$coursename = $DB->get_field('course', 'fullname', ['id' => $this->get_courseid_value()]);
191+
$coursename = format_string($coursename, true, ['context' => \context_system::instance(), 'escape' => false]);
192+
193+
return get_string('condition:user_enrolment_description', 'tool_dynamic_cohorts', (object) [
194+
'operator' => strtolower($this->get_operators()[$this->get_operator_value()]),
195+
'role' => $this->get_roles()[$this->get_roleid_value()],
196+
'coursename' => $coursename,
197+
'courseid' => $this->get_courseid_value(),
198+
'enrolmethod' => $this->get_enrolment_methods()[$this->get_enrolment_method_value()],
199+
]);
200+
}
201+
202+
/**
203+
* Human-readable description of the broken condition.
204+
*
205+
* @return string
206+
*/
207+
public function get_broken_description(): string {
208+
global $DB;
209+
210+
// Missing role.
211+
if (!array_key_exists($this->get_roleid_value(), $this->get_roles())) {
212+
return get_string('missingrole', 'tool_dynamic_cohorts');
213+
}
214+
215+
// Missing course.
216+
if (!$DB->get_record('course', ['id' => $this->get_courseid_value()])) {
217+
return get_string('missingcourse', 'tool_dynamic_cohorts');
218+
}
219+
220+
// Missing enrolment method.
221+
if (!array_key_exists($this->get_enrolment_method_value(), $this->get_enrolment_methods())) {
222+
return get_string('missingenrolmentmethod', 'tool_dynamic_cohorts', $this->get_enrolment_method_value());
223+
}
224+
225+
return parent::get_broken_description();
226+
}
227+
228+
/**
229+
* Gets SQL data for building SQL.
230+
*
231+
* @return condition_sql
232+
*/
233+
public function get_sql(): condition_sql {
234+
$sql = new condition_sql('', '1=0', []);
235+
236+
if (!$this->is_broken()) {
237+
$join = '';
238+
$params = [];
239+
240+
// Enrolment tables.
241+
$uetable = condition_sql::generate_table_alias();
242+
$enroltable = condition_sql::generate_table_alias();
243+
244+
// Course parameter.
245+
$courseidparam = condition_sql::generate_param_alias();
246+
$params[$courseidparam] = $this->get_courseid_value();
247+
248+
// In case we are filtering by enrolment method.
249+
$enrolmethodwhere = '';
250+
$enrolmethod = $this->get_enrolment_method_value();
251+
if (!empty($enrolmethod)) {
252+
$enrolmethodparam = condition_sql::generate_param_alias();
253+
$params[$enrolmethodparam] = $enrolmethod;
254+
$enrolmethodwhere = "AND $enroltable.enrol = :{$enrolmethodparam}";
255+
}
256+
257+
// In case we are filtering by role.
258+
$rolesql = '';
259+
$rolewhere = '';
260+
$roleid = $this->get_roleid_value();
261+
if (!empty($roleid)) {
262+
$outertable = condition_sql::generate_table_alias();
263+
$ratable = condition_sql::generate_table_alias();
264+
$roleidparam = condition_sql::generate_param_alias();
265+
$params[$roleidparam] = $roleid;
266+
267+
$context = context_course::instance($this->get_courseid_value());
268+
$contexidtparam = condition_sql::generate_param_alias();
269+
$params[$contexidtparam] = $context->id;
270+
271+
$rolesql = "LEFT JOIN (SELECT $ratable.userid
272+
FROM {role_assignments} $ratable
273+
WHERE $ratable.roleid = :{$roleidparam}
274+
AND $ratable.contextid = :{$contexidtparam}
275+
) {$outertable} ON {$uetable}.userid = {$outertable}.userid ";
276+
277+
$rolewhere = "AND {$outertable}.userid is NOT NULL";
278+
}
279+
280+
$operator = $this->get_operator_value() == self::OPERATOR_ENROLLED ? 'EXISTS' : 'NOT EXISTS';
281+
282+
$where = "{$operator} (SELECT 1 FROM {user_enrolments} {$uetable}
283+
JOIN {enrol} {$enroltable} ON ({$enroltable}.id = {$uetable}.enrolid AND {$enroltable}.status = 0)
284+
$rolesql
285+
WHERE {$uetable}.userid = u.id
286+
AND $enroltable.courseid = :{$courseidparam}
287+
AND {$uetable}.status = 0
288+
$enrolmethodwhere
289+
$rolewhere)";
290+
291+
$sql = new condition_sql($join, $where, $params);
292+
}
293+
294+
return $sql;
295+
}
296+
297+
/**
298+
* Is condition broken.
299+
*
300+
* @return bool
301+
*/
302+
public function is_broken(): bool {
303+
global $DB;
304+
305+
if ($this->get_config_data()) {
306+
// Check role exists.
307+
if (!array_key_exists($this->get_roleid_value(), $this->get_roles())) {
308+
return true;
309+
}
310+
311+
// Check course exists.
312+
if (!$DB->get_record('course', ['id' => $this->get_courseid_value()])) {
313+
return true;
314+
}
315+
316+
// Check enrolment method exists.
317+
if (!array_key_exists($this->get_enrolment_method_value(), $this->get_enrolment_methods())) {
318+
return true;
319+
}
320+
}
321+
322+
return false;
323+
}
324+
325+
/**
326+
* Gets a list of event classes the condition will be triggered on.
327+
*
328+
* @return string[]
329+
*/
330+
public function get_events(): array {
331+
return [
332+
'core\event\role_assigned',
333+
'core\event\role_unassigned',
334+
'core\event\user_enrolment_created',
335+
'core\event\user_enrolment_updated',
336+
'core\event\user_enrolment_deleted',
337+
];
338+
}
339+
}

lang/en/tool_dynamic_cohorts.php

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
$string['condition:course_not_completed'] = 'Course not completed';
6161
$string['condition:course_not_completed_description'] = 'Users who have not completed course "{$a->course}"';
6262
$string['condition:profile_field_description'] = 'Users with {$a->field} {$a->fieldoperator} {$a->fieldvalue}';
63+
$string['condition:user_enrolment'] = 'User enrolment';
64+
$string['condition:user_enrolment_description'] = 'Users who are {$a->operator} into course "{$a->coursename}" (id {$a->courseid}) with "{$a->role}" role using "{$a->enrolmethod}" enrolment method';
6365
$string['condition:user_last_login'] = 'User last login';
6466
$string['condition:user_created'] = 'User created time';
6567
$string['condition:user_profile'] = 'User standard profile field';
@@ -82,6 +84,8 @@
8284
$string['edit_rule'] = 'Edit rule';
8385
$string['enabled'] = 'Enabled';
8486
$string['enable_confirm'] = 'Are you sure you want to enable rule {$a}?';
87+
$string['enrolmethod'] = 'Enrolment method';
88+
$string['enrolled'] = 'Enrolled';
8589
$string['ever'] = 'Ever';
8690
$string['everloggedin'] = 'Users who have logged in at least once';
8791
$string['event:conditioncreated'] = 'Condition created';
@@ -112,11 +116,13 @@
112116
$string['matchingusers'] = 'Matching users';
113117
$string['missingcourse'] = 'Missing course';
114118
$string['missingcoursecat'] = 'Missing course category';
119+
$string['missingenrolmentmethod'] = 'Missing enrolment method {$a}';
115120
$string['missingrole'] = 'Missing role';
116121
$string['name'] = 'Rule name';
117122
$string['name_help'] = 'A human readable name of this rule.';
118123
$string['never'] = 'Never';
119124
$string['neverloggedin'] = 'Users who have never logged in';
125+
$string['notenrolled'] = 'Not enrolled';
120126
$string['operator'] = 'Operator';
121127
$string['or'] = 'OR';
122128
$string['pleaseselectcohort'] = 'Please select a cohort';

0 commit comments

Comments
 (0)