Skip to content

Commit 2618182

Browse files
committed
issue #81: add user role condition
1 parent e8a1aaa commit 2618182

File tree

5 files changed

+771
-2
lines changed

5 files changed

+771
-2
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Conditions are simple predicates which assert something about a user in the syst
6363
* User created time (time since a user was created).
6464
* User standard profile fields (e.g. first name, last name, username, auth method and etc).
6565
* User custom profile fields (text and menu types are supported).
66+
* User role (if a user is assigned to a role in a context)
6667

6768
## Rules
6869

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
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 core_course_category;
20+
use tool_dynamic_cohorts\condition_base;
21+
use tool_dynamic_cohorts\condition_sql;
22+
use context_system;
23+
use context_course;
24+
use context_coursecat;
25+
26+
/**
27+
* Condition based on user's role.
28+
*
29+
* @package tool_dynamic_cohorts
30+
* @copyright 2024 Catalyst IT
31+
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32+
*/
33+
class user_role extends condition_base {
34+
35+
/**
36+
* Condition name.
37+
*
38+
* @return string
39+
*/
40+
public function get_name(): string {
41+
return get_string('condition:user_role', 'tool_dynamic_cohorts');
42+
}
43+
44+
/**
45+
* Gets a list of all roles.
46+
*
47+
* @return array
48+
*/
49+
protected function get_all_roles(): array {
50+
$roles = [];
51+
foreach (role_get_names() as $role) {
52+
if ($role->archetype === 'guest') {
53+
continue;
54+
}
55+
$roles[$role->id] = $role->localname;
56+
}
57+
58+
return $roles;
59+
}
60+
61+
/**
62+
* Add config form elements.
63+
*
64+
* @param \MoodleQuickForm $mform
65+
*/
66+
public function config_form_add(\MoodleQuickForm $mform): void {
67+
// Role field.
68+
$mform->addElement('select', 'roleid', get_string('role'), $this->get_all_roles());
69+
70+
// Context level selection.
71+
$mform->addElement(
72+
'select',
73+
'contextlevel',
74+
get_string('context'),
75+
[
76+
CONTEXT_SYSTEM => get_string('coresystem'),
77+
CONTEXT_COURSECAT => get_string('category'),
78+
CONTEXT_COURSE => get_string('course'),
79+
]
80+
);
81+
82+
// Course.
83+
$mform->addElement('course', 'courseid', get_string('course'));
84+
$mform->setType('courseid', PARAM_INT);
85+
$mform->hideIf('courseid', 'contextlevel', 'in', [CONTEXT_SYSTEM, CONTEXT_COURSECAT]);
86+
87+
// Course category.
88+
$categories = core_course_category::make_categories_list();
89+
$mform->addElement('autocomplete', 'categoryid', get_string('coursecategory'), $categories);
90+
$mform->setType('categoryid', PARAM_INT);
91+
$mform->hideIf('categoryid', 'contextlevel', 'in', [CONTEXT_SYSTEM, CONTEXT_COURSE]);
92+
93+
$mform->addElement('checkbox', 'includechildren', '', get_string('includechildren', 'tool_dynamic_cohorts'));
94+
$mform->hideIf('includechildren', 'contextlevel', 'in', [CONTEXT_SYSTEM, CONTEXT_COURSE]);
95+
}
96+
97+
/**
98+
* Validate config form elements.
99+
*
100+
* @param array $data Data to validate.
101+
* @return array
102+
*/
103+
public function config_form_validate(array $data): array {
104+
$errors = [];
105+
106+
if (empty($data['courseid']) && $data['contextlevel'] == CONTEXT_COURSE) {
107+
$errors['courseid'] = get_string('required');
108+
}
109+
110+
if (empty($data['categoryid']) && $data['contextlevel'] == CONTEXT_COURSECAT) {
111+
$errors['categoryid'] = get_string('required');
112+
}
113+
114+
return $errors;
115+
}
116+
117+
/**
118+
* Gets configured role ID.
119+
*
120+
* @return int
121+
*/
122+
protected function get_roleid_value(): int {
123+
return $this->get_config_data()['roleid'] ?? 0;
124+
}
125+
126+
/**
127+
* Gets configured contextlevel.
128+
*
129+
* @return int
130+
*/
131+
protected function get_contextlevel_value(): int {
132+
return $this->get_config_data()['contextlevel'] ?? 0;
133+
}
134+
135+
/**
136+
* Gets configured course ID.
137+
*
138+
* @return int
139+
*/
140+
protected function get_courseid_value(): int {
141+
return $this->get_config_data()['courseid'] ?? 0;
142+
}
143+
144+
/**
145+
* Gets configured category ID.
146+
*
147+
* @return int
148+
*/
149+
protected function get_categoryid_value(): int {
150+
return $this->get_config_data()['categoryid'] ?? 0;
151+
}
152+
153+
/**
154+
* Gets configured include children value.
155+
*
156+
* @return int
157+
*/
158+
protected function get_includechildren_value(): int {
159+
return $this->get_config_data()['includechildren'] ?? 0;
160+
}
161+
162+
/**
163+
* Human-readable description of the configured condition.
164+
*
165+
* @return string
166+
*/
167+
public function get_config_description(): string {
168+
global $DB;
169+
170+
$rolename = $this->get_all_roles()[$this->get_roleid_value()];
171+
172+
switch ($this->get_contextlevel_value()) {
173+
case CONTEXT_SYSTEM:
174+
return get_string('condition:user_role_description_system', 'tool_dynamic_cohorts', $rolename);
175+
176+
case CONTEXT_COURSECAT:
177+
$children = !empty($this->get_includechildren_value()) ? get_string('includechildren', 'tool_dynamic_cohorts') : '';
178+
$categoryname = core_course_category::get($this->get_categoryid_value())->get_formatted_name();
179+
180+
return get_string('condition:user_role_description_category', 'tool_dynamic_cohorts', (object) [
181+
'role' => $rolename,
182+
'categoryname' => $categoryname,
183+
'categoryid' => $this->get_categoryid_value(),
184+
]) . ' ' . $children;
185+
186+
case CONTEXT_COURSE:
187+
$coursename = $DB->get_field('course', 'fullname', ['id' => $this->get_courseid_value()]);
188+
$coursename = format_string($coursename, true, ['context' => \context_system::instance(), 'escape' => false]);
189+
190+
return get_string('condition:user_role_description_course', 'tool_dynamic_cohorts', (object) [
191+
'role' => $rolename,
192+
'coursename' => $coursename,
193+
'courseid' => $this->get_courseid_value(),
194+
]);
195+
196+
default:
197+
return '';
198+
}
199+
}
200+
201+
/**
202+
* Human readable description of the broken condition.
203+
*
204+
* @return string
205+
*/
206+
public function get_broken_description(): string {
207+
global $DB;
208+
209+
// Missing role.
210+
if (!$DB->get_record('role', ['id' => $this->get_roleid_value()])) {
211+
return get_string('missingrole', 'tool_dynamic_cohorts');
212+
}
213+
214+
// Missing course.
215+
if ($this->get_contextlevel_value() == CONTEXT_COURSE &&
216+
!$DB->get_record('course', ['id' => $this->get_courseid_value()])) {
217+
return get_string('missingcourse', 'tool_dynamic_cohorts');
218+
}
219+
220+
// Missing course category.
221+
if ($this->get_contextlevel_value() == CONTEXT_COURSECAT &&
222+
!$DB->get_record('course_categories', ['id' => $this->get_categoryid_value()])) {
223+
return get_string('missingcoursecat', 'tool_dynamic_cohorts');
224+
}
225+
226+
return parent::get_broken_description();
227+
}
228+
229+
/**
230+
* Gets SQL data for building SQL.
231+
*
232+
* @return condition_sql
233+
*/
234+
public function get_sql(): condition_sql {
235+
global $DB;
236+
237+
$sql = new condition_sql('', '1=0', []);
238+
239+
if (!$this->is_broken()) {
240+
$roleid = $this->get_roleid_value();
241+
$roleidparam = condition_sql::generate_param_alias();
242+
$params[$roleidparam] = $roleid;
243+
244+
$ratable = condition_sql::generate_table_alias();
245+
$join = "JOIN {role_assignments} $ratable ON ($ratable.userid = u.id)";
246+
$where = "$ratable.roleid = :$roleidparam";
247+
248+
switch ($this->get_contextlevel_value()) {
249+
case CONTEXT_SYSTEM:
250+
$context = context_system::instance();
251+
$contextid = $context->id;
252+
$contextidparam = condition_sql::generate_param_alias();
253+
$params[$contextidparam] = $contextid;
254+
$where .= " AND $ratable.contextid = :$contextidparam";
255+
256+
break;
257+
case CONTEXT_COURSECAT:
258+
$context = context_coursecat::instance($this->get_categoryid_value());
259+
list($parentcontexsql, $parentcparams) = $DB->get_in_or_equal(
260+
$context->get_parent_context_ids(true),
261+
SQL_PARAMS_NAMED,
262+
condition_sql::generate_param_alias()
263+
);
264+
$params = array_merge($params, $parentcparams);
265+
266+
$childcontxtids = array_keys($context->get_child_contexts());
267+
268+
if ($this->get_includechildren_value() && !empty($childcontxtids)) {
269+
$childcontxtids = array_keys($context->get_child_contexts());
270+
list($childcontextsql, $childcparams) = $DB->get_in_or_equal(
271+
$childcontxtids,
272+
SQL_PARAMS_NAMED,
273+
condition_sql::generate_param_alias()
274+
);
275+
$params = array_merge($params, $childcparams);
276+
$where .= " AND ( $ratable.contextid $parentcontexsql OR $ratable.contextid $childcontextsql ) ";
277+
} else {
278+
$where .= " AND $ratable.contextid $parentcontexsql ";
279+
}
280+
281+
break;
282+
283+
case CONTEXT_COURSE:
284+
$context = context_course::instance($this->get_courseid_value());
285+
286+
list($contextsql, $cparams) = $DB->get_in_or_equal(
287+
$context->get_parent_context_ids(true),
288+
SQL_PARAMS_NAMED,
289+
condition_sql::generate_param_alias()
290+
);
291+
292+
$params = array_merge($params, $cparams);
293+
$where .= " AND $ratable.contextid $contextsql";
294+
295+
break;
296+
}
297+
298+
$sql = new condition_sql($join, $where, $params);
299+
}
300+
301+
return $sql;
302+
}
303+
304+
/**
305+
* Is condition broken.
306+
*
307+
* @return bool
308+
*/
309+
public function is_broken(): bool {
310+
global $DB;
311+
312+
if ($this->get_config_data()) {
313+
// Check role exists.
314+
if (!$DB->get_record('role', ['id' => $this->get_roleid_value()])) {
315+
return true;
316+
}
317+
318+
// Check course exists.
319+
if ($this->get_contextlevel_value() == CONTEXT_COURSE &&
320+
!$DB->get_record('course', ['id' => $this->get_courseid_value()])) {
321+
return true;
322+
}
323+
324+
// Check category exists.
325+
if ($this->get_contextlevel_value() == CONTEXT_COURSECAT &&
326+
!$DB->get_record('course_categories', ['id' => $this->get_categoryid_value()])) {
327+
return true;
328+
}
329+
}
330+
331+
return false;
332+
}
333+
334+
/**
335+
* Gets a list of event classes the condition will be triggered on.
336+
*
337+
* @return string[]
338+
*/
339+
public function get_events(): array {
340+
return [
341+
'core\event\role_assigned',
342+
'core\event\role_unassigned',
343+
];
344+
}
345+
}

lang/en/tool_dynamic_cohorts.php

+7
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
$string['condition:user_created'] = 'User created time';
6565
$string['condition:user_profile'] = 'User standard profile field';
6666
$string['condition:user_custom_profile'] = 'User custom profile field';
67+
$string['condition:user_role'] = 'User role';
68+
$string['condition:user_role_description_system'] = 'A user has {$a} role in system context';
69+
$string['condition:user_role_description_category'] = 'A user has {$a->role} role in category {$a->categoryname} (id {$a->categoryid})';
70+
$string['condition:user_role_description_course'] = 'A user has {$a->role} role in course {$a->coursename} (id {$a->courseid})';
6771
$string['cf_include_missing_data'] = 'Include cohorts with missing data.';
6872
$string['cf_include_missing_data_help'] = 'Cohorts may not have a custom field data set yet. This option includes those cohorts in the final result.';
6973
$string['cf_includingmissingdatadesc'] = '(including cohorts with missing data)';
@@ -87,6 +91,7 @@
8791
$string['event:rulecreated'] = 'Rule created';
8892
$string['event:ruleupdated'] = 'Rule updated';
8993
$string['event:ruledeleted'] = 'Rule deleted';
94+
$string['includechildren'] = 'including children (categories and courses)';
9095
$string['includingmissingdatadesc'] = '(including users with missing data)';
9196
$string['includeusersmissingdata'] = 'include users with missing data';
9297
$string['include_missing_data'] = 'Include users with missing data.';
@@ -106,6 +111,8 @@
106111
$string['managecohorts'] = 'Manage cohorts';
107112
$string['matchingusers'] = 'Matching users';
108113
$string['missingcourse'] = 'Missing course';
114+
$string['missingcoursecat'] = 'Missing course category';
115+
$string['missingrole'] = 'Missing role';
109116
$string['name'] = 'Rule name';
110117
$string['name_help'] = 'A human readable name of this rule.';
111118
$string['never'] = 'Never';

0 commit comments

Comments
 (0)