MDL-35381 added consistent sorting to the new selector (MDL-34657)
[moodle.git] / admin / roles / lib.php
CommitLineData
11b749ca 1<?php
1e8e4687 2
01a2ce80
PS
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
1e8e4687 17
18/**
19 * Library code used by the roles administration interfaces.
20 *
01a2ce80
PS
21 * Responds to actions:
22 * add - add a new role
23 * duplicate - like add, only initialise the new role by using an existing one.
24 * edit - edit the definition of a role
25 * view - view the definition of a role
26 *
5d354ded 27 * @package core
01a2ce80
PS
28 * @subpackage role
29 * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31 */
1e8e4687 32
33require_once($CFG->libdir.'/adminlib.php');
34require_once($CFG->dirroot.'/user/selector/lib.php');
35
1e8e4687 36// Classes for producing tables with one row per capability ====================
37
38/**
39 * This class represents a table with one row for each of a list of capabilities
40 * where the first cell in the row contains the capability name, and there is
41 * arbitrary stuff in the rest of the row. This class is used by
bed9cec8 42 * admin/roles/manage.php, override.php and check.php.
1e8e4687 43 *
44 * An ajaxy search UI shown at the top, if JavaScript is on.
45 */
46abstract class capability_table_base {
47 /** The context this table relates to. */
48 protected $context;
49
50 /** The capabilities to display. Initialised as fetch_context_capabilities($context). */
51 protected $capabilities = array();
52
53 /** Added as an id="" attribute to the table on output. */
54 protected $id;
55
56 /** Added to the class="" attribute on output. */
57 protected $classes = array('rolecap');
58
59 /** Default number of capabilities in the table for the search UI to be shown. */
60 const NUM_CAPS_FOR_SEARCH = 12;
61
62 /**
63 * Constructor
64 * @param object $context the context this table relates to.
65 * @param string $id what to put in the id="" attribute.
66 */
67 public function __construct($context, $id) {
68 $this->context = $context;
69 $this->capabilities = fetch_context_capabilities($context);
70 $this->id = $id;
71 }
72
73 /**
74 * Use this to add class="" attributes to the table. You get the rolecap by
75 * default.
76 * @param array $classnames of class names.
77 */
78 public function add_classes($classnames) {
79 $this->classes = array_unique(array_merge($this->classes, $classnames));
80 }
81
82 /**
83 * Display the table.
84 */
85 public function display() {
01a2ce80
PS
86 if (count($this->capabilities) > capability_table_base::NUM_CAPS_FOR_SEARCH) {
87 global $PAGE;
87b4981b
SH
88 $PAGE->requires->strings_for_js(array('filter','clear'),'moodle');
89 $PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id));
01a2ce80 90 }
1e8e4687 91 echo '<table class="' . implode(' ', $this->classes) . '" id="' . $this->id . '">' . "\n<thead>\n";
92 echo '<tr><th class="name" align="left" scope="col">' . get_string('capability','role') . '</th>';
93 $this->add_header_cells();
94 echo "</tr>\n</thead>\n<tbody>\n";
0df0df23 95
1e8e4687 96 /// Loop over capabilities.
97 $contextlevel = 0;
98 $component = '';
99 foreach ($this->capabilities as $capability) {
100 if ($this->skip_row($capability)) {
101 continue;
102 }
103
104 /// Prints a breaker if component or name or context level has changed
105 if (component_level_changed($capability, $component, $contextlevel)) {
106 $this->print_heading_row($capability);
107 }
108 $contextlevel = $capability->contextlevel;
109 $component = $capability->component;
110
111 /// Start the row.
112 echo '<tr class="' . implode(' ', array_unique(array_merge(array('rolecap'),
113 $this->get_row_classes($capability)))) . '">';
114
115 /// Table cell for the capability name.
8aee9bcc 116 echo '<th scope="row" class="name"><span class="cap-desc">' . get_capability_docs_link($capability) .
a90d7cf9 117 '<span class="cap-name">' . $capability->name . '</span></span></th>';
1e8e4687 118
119 /// Add the cells specific to this table.
120 $this->add_row_cells($capability);
121
122 /// End the row.
123 echo "</tr>\n";
124 }
125
126 /// End of the table.
01a2ce80 127 echo "</tbody>\n</table>\n";
1e8e4687 128 }
129
130 /**
131 * Used to output a heading rows when the context level or component changes.
132 * @param object $capability gives the new component and contextlevel.
133 */
134 protected function print_heading_row($capability) {
135 echo '<tr class="rolecapheading header"><td colspan="' . (1 + $this->num_extra_columns()) . '" class="header"><strong>' .
136 get_component_string($capability->component, $capability->contextlevel) .
137 '</strong></td></tr>';
0df0df23 138
1e8e4687 139 }
140
141 /** For subclasses to override, output header cells, after the initial capability one. */
142 protected abstract function add_header_cells();
143
144 /** For subclasses to override, return the number of cells that add_header_cells/add_row_cells output. */
145 protected abstract function num_extra_columns();
146
147 /**
4f0c2d00 148 * For subclasses to override. Allows certain capabilties
1e8e4687 149 * to be left out of the table.
150 *
151 * @param object $capability the capability this row relates to.
152 * @return boolean. If true, this row is omitted from the table.
153 */
154 protected function skip_row($capability) {
155 return false;
156 }
157
158 /**
159 * For subclasses to override. A change to reaturn class names that are added
160 * to the class="" attribute on the &lt;tr> for this capability.
161 *
162 * @param object $capability the capability this row relates to.
163 * @return array of class name strings.
164 */
165 protected function get_row_classes($capability) {
166 return array();
167 }
168
169 /**
170 * For subclasses to override. Output the data cells for this capability. The
171 * capability name cell will already have been output.
172 *
173 * You can rely on get_row_classes always being called before add_row_cells.
174 *
175 * @param object $capability the capability this row relates to.
176 */
177 protected abstract function add_row_cells($capability);
178}
179
180/**
181 * Subclass of capability_table_base for use on the Check permissions page.
182 *
01a2ce80 183 * We have one additional column, Allowed, which contains yes/no.
1e8e4687 184 */
01a2ce80 185class check_capability_table extends capability_table_base {
1e8e4687 186 protected $user;
187 protected $fullname;
1e8e4687 188 protected $contextname;
189 protected $stryes;
190 protected $strno;
1e8e4687 191 private $hascap;
192
193 /**
194 * Constructor
195 * @param object $context the context this table relates to.
196 * @param object $user the user we are generating the results for.
197 * @param string $contextname print_context_name($context) - to save recomputing.
198 */
199 public function __construct($context, $user, $contextname) {
200 global $CFG;
201 parent::__construct($context, 'explaincaps');
202 $this->user = $user;
203 $this->fullname = fullname($user);
204 $this->contextname = $contextname;
1e8e4687 205 $this->stryes = get_string('yes');
206 $this->strno = get_string('no');
1e8e4687 207 }
208
209 protected function add_header_cells() {
210 echo '<th>' . get_string('allowed', 'role') . '</th>';
1e8e4687 211 }
212
213 protected function num_extra_columns() {
01a2ce80 214 return 1;
1e8e4687 215 }
216
1e8e4687 217 protected function get_row_classes($capability) {
218 $this->hascap = has_capability($capability->name, $this->context, $this->user->id);
219 if ($this->hascap) {
220 return array('yes');
221 } else {
222 return array('no');
223 }
224 }
225
226 protected function add_row_cells($capability) {
04eb4d1e 227 global $OUTPUT;
1e8e4687 228 if ($this->hascap) {
229 $result = $this->stryes;
1e8e4687 230 } else {
231 $result = $this->strno;
1e8e4687 232 }
233 $a = new stdClass;
234 $a->fullname = $this->fullname;
235 $a->capability = $capability->name;
236 $a->context = $this->contextname;
237 echo '<td>' . $result . '</td>';
01a2ce80
PS
238 }
239}
04eb4d1e 240
04eb4d1e 241
01a2ce80
PS
242/**
243 * Subclass of capability_table_base for use on the Permissions page.
244 */
245class permissions_table extends capability_table_base {
246 protected $contextname;
247 protected $allowoverrides;
248 protected $allowsafeoverrides;
249 protected $overridableroles;
250 protected $roles;
251 protected $icons = array();
252
253 /**
254 * Constructor
255 * @param object $context the context this table relates to.
256 * @param string $contextname print_context_name($context) - to save recomputing.
257 */
258 public function __construct($context, $contextname, $allowoverrides, $allowsafeoverrides, $overridableroles) {
01a2ce80
PS
259 parent::__construct($context, 'permissions');
260 $this->contextname = $contextname;
261 $this->allowoverrides = $allowoverrides;
262 $this->allowsafeoverrides = $allowsafeoverrides;
263 $this->overridableroles = $overridableroles;
264
c52551dc
PS
265 $roles = get_all_roles($context);
266 $this->roles = role_fix_names(array_reverse($roles, true), $context, ROLENAME_ALIAS, true);
01a2ce80
PS
267
268 }
269
270 protected function add_header_cells() {
271 echo '<th>' . get_string('risks', 'role') . '</th>';
272 echo '<th>' . get_string('neededroles', 'role') . '</th>';
273 echo '<th>' . get_string('prohibitedroles', 'role') . '</th>';
274 }
275
276 protected function num_extra_columns() {
277 return 3;
278 }
279
01a2ce80
PS
280 protected function add_row_cells($capability) {
281 global $OUTPUT, $PAGE;
282
283 $context = $this->context;
284 $contextid = $this->context->id;
285 $allowoverrides = $this->allowoverrides;
286 $allowsafeoverrides = $this->allowsafeoverrides;
287 $overridableroles = $this->overridableroles;
288 $roles = $this->roles;
289
290
291 list($needed, $forbidden) = get_roles_with_cap_in_context($context, $capability->name);
292 $neededroles = array();
293 $forbiddenroles = array();
294 $allowable = $overridableroles;
295 $forbitable = $overridableroles;
296 foreach ($neededroles as $id=>$unused) {
297 unset($allowable[$id]);
298 }
299 foreach ($forbidden as $id=>$unused) {
300 unset($allowable[$id]);
301 unset($forbitable[$id]);
302 }
303
304 foreach ($roles as $id=>$name) {
305 if (isset($needed[$id])) {
306 $neededroles[$id] = $roles[$id];
307 if (isset($overridableroles[$id]) and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
308 $preventurl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'roleid'=>$id, 'capability'=>$capability->name, 'prevent'=>1));
309 $neededroles[$id] .= $OUTPUT->action_icon($preventurl, new pix_icon('t/delete', get_string('prevent', 'role')));
310 }
311 }
312 }
313 $neededroles = implode(', ', $neededroles);
314 foreach ($roles as $id=>$name) {
315 if (isset($forbidden[$id]) and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
316 $forbiddenroles[$id] = $roles[$id];
317 if (isset($overridableroles[$id]) and prohibit_is_removable($id, $context, $capability->name)) {
318 $unprohibiturl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'roleid'=>$id, 'capability'=>$capability->name, 'unprohibit'=>1));
319 $forbiddenroles[$id] .= $OUTPUT->action_icon($unprohibiturl, new pix_icon('t/delete', get_string('delete')));
320 }
321 }
322 }
323 $forbiddenroles = implode(', ', $forbiddenroles);
324
325 if ($allowable and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
326 $allowurl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'capability'=>$capability->name, 'allow'=>1));
327 $neededroles .= '<div class="allowmore">'.$OUTPUT->action_icon($allowurl, new pix_icon('t/add', get_string('allow', 'role'))).'</div>';
328 }
329
330 if ($forbitable and ($allowoverrides or ($allowsafeoverrides and is_safe_capability($capability)))) {
331 $prohibiturl = new moodle_url($PAGE->url, array('contextid'=>$contextid, 'capability'=>$capability->name, 'prohibit'=>1));
332 $forbiddenroles .= '<div class="prohibitmore">'.$OUTPUT->action_icon($prohibiturl, new pix_icon('t/add', get_string('prohibit', 'role'))).'</div>';
333 }
334
335 $risks = $this->get_risks($capability);
336
337 echo '<td>' . $risks . '</td>';
338 echo '<td>' . $neededroles . '</td>';
339 echo '<td>' . $forbiddenroles . '</td>';
340 }
341
342 protected function get_risks($capability) {
343 global $OUTPUT;
344
345 $allrisks = get_all_risks();
346 $risksurl = new moodle_url(get_docs_url(s(get_string('risks', 'role'))));
347
348 $return = '';
349
350 foreach ($allrisks as $type=>$risk) {
351 if ($risk & (int)$capability->riskbitmask) {
352 if (!isset($this->icons[$type])) {
353 $pixicon = new pix_icon('/i/' . str_replace('risk', 'risk_', $type), get_string($type . 'short', 'admin'));
354 $this->icons[$type] = $OUTPUT->action_icon($risksurl, $pixicon, new popup_action('click', $risksurl));
355 }
356 $return .= $this->icons[$type];
357 }
358 }
359
360 return $return;
1e8e4687 361 }
362}
363
01a2ce80 364
a4df1cbb 365/**
366 * This subclass is the bases for both the define roles and override roles
367 * pages. As well as adding the risks columns, this also provides generic
368 * facilities for showing a certain number of permissions columns, and
369 * recording the current and submitted permissions for each capability.
370 */
c6505f1e 371abstract class capability_table_with_risks extends capability_table_base {
372 protected $allrisks;
eab8ed9f 373 protected $allpermissions; // We don't need perms ourselves, but all our subclasses do.
c6505f1e 374 protected $strperms; // Language string cache.
375 protected $risksurl; // URL in moodledocs about risks.
376 protected $riskicons = array(); // Cache to avoid regenerating the HTML for each risk icon.
eab8ed9f 377 /** The capabilities to highlight as default/inherited. */
a4df1cbb 378 protected $parentpermissions;
379 protected $displaypermissions;
380 protected $permissions;
381 protected $changed;
bbdb7070 382 protected $roleid;
c6505f1e 383
bbdb7070 384 public function __construct($context, $id, $roleid) {
c6505f1e 385 parent::__construct($context, $id);
386
387 $this->allrisks = get_all_risks();
388 $this->risksurl = get_docs_url(s(get_string('risks', 'role')));
389
390 $this->allpermissions = array(
391 CAP_INHERIT => 'inherit',
392 CAP_ALLOW => 'allow',
393 CAP_PREVENT => 'prevent' ,
394 CAP_PROHIBIT => 'prohibit',
395 );
396
397 $this->strperms = array();
398 foreach ($this->allpermissions as $permname) {
399 $this->strperms[$permname] = get_string($permname, 'role');
400 }
a4df1cbb 401
bbdb7070 402 $this->roleid = $roleid;
a4df1cbb 403 $this->load_current_permissions();
404
405 /// Fill in any blank permissions with an explicit CAP_INHERIT, and init a locked field.
406 foreach ($this->capabilities as $capid => $cap) {
407 if (!isset($this->permissions[$cap->name])) {
408 $this->permissions[$cap->name] = CAP_INHERIT;
409 }
410 $this->capabilities[$capid]->locked = false;
411 }
412 }
413
414 protected function load_current_permissions() {
415 global $DB;
416
417 /// Load the overrides/definition in this context.
bbdb7070 418 if ($this->roleid) {
419 $this->permissions = $DB->get_records_menu('role_capabilities', array('roleid' => $this->roleid,
420 'contextid' => $this->context->id), '', 'capability,permission');
421 } else {
422 $this->permissions = array();
423 }
a4df1cbb 424 }
425
426 protected abstract function load_parent_permissions();
427
bbdb7070 428 /**
429 * Update $this->permissions based on submitted data, while making a list of
430 * changed capabilities in $this->changed.
431 */
a4df1cbb 432 public function read_submitted_permissions() {
bbdb7070 433 $this->changed = array();
434
a4df1cbb 435 foreach ($this->capabilities as $cap) {
436 if ($cap->locked || $this->skip_row($cap)) {
eab8ed9f 437 /// The user is not allowed to change the permission for this capability
a4df1cbb 438 continue;
439 }
440
441 $permission = optional_param($cap->name, null, PARAM_PERMISSION);
442 if (is_null($permission)) {
443 /// A permission was not specified in submitted data.
444 continue;
445 }
446
447 /// If the permission has changed, update $this->permissions and
448 /// Record the fact there is data to save.
449 if ($this->permissions[$cap->name] != $permission) {
450 $this->permissions[$cap->name] = $permission;
451 $this->changed[] = $cap->name;
452 }
453 }
454 }
455
bbdb7070 456 /**
457 * Save the new values of any permissions that have been changed.
458 */
459 public function save_changes() {
460 /// Set the permissions.
461 foreach ($this->changed as $changedcap) {
462 assign_capability($changedcap, $this->permissions[$changedcap],
463 $this->roleid, $this->context->id, true);
464 }
465
466 /// Force accessinfo refresh for users visiting this context.
467 mark_context_dirty($this->context->path);
468 }
469
a4df1cbb 470 public function display() {
471 $this->load_parent_permissions();
472 foreach ($this->capabilities as $cap) {
473 if (!isset($this->parentpermissions[$cap->name])) {
474 $this->parentpermissions[$cap->name] = CAP_INHERIT;
475 }
476 }
477 parent::display();
c6505f1e 478 }
479
480 protected function add_header_cells() {
8fbce1c8 481 global $OUTPUT;
bed9cec8 482 echo '<th colspan="' . count($this->displaypermissions) . '" scope="col">' .
52236f05 483 get_string('permission', 'role') . ' ' . $OUTPUT->help_icon('permission', 'role') . '</th>';
8aee9bcc 484 echo '<th class="risk" colspan="' . count($this->allrisks) . '" scope="col">' . get_string('risks','role') . '</th>';
c6505f1e 485 }
486
487 protected function num_extra_columns() {
a4df1cbb 488 return count($this->displaypermissions) + count($this->allrisks);
c6505f1e 489 }
490
491 protected function get_row_classes($capability) {
492 $rowclasses = array();
493 foreach ($this->allrisks as $riskname => $risk) {
494 if ($risk & (int)$capability->riskbitmask) {
495 $rowclasses[] = $riskname;
496 }
497 }
498 return $rowclasses;
499 }
500
a4df1cbb 501 protected abstract function add_permission_cells($capability);
502
c6505f1e 503 protected function add_row_cells($capability) {
a4df1cbb 504 $this->add_permission_cells($capability);
c6505f1e 505 /// One cell for each possible risk.
506 foreach ($this->allrisks as $riskname => $risk) {
507 echo '<td class="risk ' . str_replace('risk', '', $riskname) . '">';
508 if ($risk & (int)$capability->riskbitmask) {
509 echo $this->get_risk_icon($riskname);
510 }
511 echo '</td>';
512 }
513 }
514
515 /**
516 * Print a risk icon, as a link to the Risks page on Moodle Docs.
517 *
0df0df23 518 * @param string $type the type of risk, will be one of the keys from the
c6505f1e 519 * get_all_risks array. Must start with 'risk'.
520 */
521 function get_risk_icon($type) {
5d3b9994 522 global $OUTPUT;
c6505f1e 523 if (!isset($this->riskicons[$type])) {
b5d0cafc 524 $iconurl = $OUTPUT->pix_url('i/' . str_replace('risk', 'risk_', $type));
75015e5f
PS
525 $text = '<img src="' . $iconurl . '" alt="' . get_string($type . 'short', 'admin') . '" />';
526 $action = new popup_action('click', $this->risksurl, 'docspopup');
527 $this->riskicons[$type] = $OUTPUT->action_link($this->risksurl, $text, $action, array('title'=>get_string($type, 'admin')));
c6505f1e 528 }
529 return $this->riskicons[$type];
530 }
531}
532
bbdb7070 533/**
534 * As well as tracking the permissions information about the role we are creating
eab8ed9f 535 * or editing, we also track the other information about the role. (This class is
bbdb7070 536 * starting to be more and more like a formslib form in some respects.)
537 */
f4acee5d 538class define_role_table_advanced extends capability_table_with_risks {
bbdb7070 539 /** Used to store other information (besides permissions) about the role we are creating/editing. */
540 protected $role;
541 /** Used to store errors found when validating the data. */
542 protected $errors;
543 protected $contextlevels;
544 protected $allcontextlevels;
bbdb7070 545 protected $disabled = '';
a4df1cbb 546
547 public function __construct($context, $roleid) {
548 $this->roleid = $roleid;
bbdb7070 549 parent::__construct($context, 'defineroletable', $roleid);
a4df1cbb 550 $this->displaypermissions = $this->allpermissions;
f4acee5d 551 $this->strperms[$this->allpermissions[CAP_INHERIT]] = get_string('notset', 'role');
bbdb7070 552
553 $this->allcontextlevels = array(
554 CONTEXT_SYSTEM => get_string('coresystem'),
555 CONTEXT_USER => get_string('user'),
556 CONTEXT_COURSECAT => get_string('category'),
557 CONTEXT_COURSE => get_string('course'),
558 CONTEXT_MODULE => get_string('activitymodule'),
559 CONTEXT_BLOCK => get_string('block')
560 );
a4df1cbb 561 }
562
563 protected function load_current_permissions() {
bbdb7070 564 global $DB;
565 if ($this->roleid) {
566 if (!$this->role = $DB->get_record('role', array('id' => $this->roleid))) {
567 throw new moodle_exception('invalidroleid');
568 }
bbdb7070 569 $contextlevels = get_role_contextlevels($this->roleid);
570 // Put the contextlevels in the array keys, as well as the values.
a90d7cf9 571 if (!empty($contextlevels)) {
572 $this->contextlevels = array_combine($contextlevels, $contextlevels);
573 } else {
574 $this->contextlevels = array();
575 }
a4df1cbb 576 } else {
bbdb7070 577 $this->role = new stdClass;
578 $this->role->name = '';
579 $this->role->shortname = '';
580 $this->role->description = '';
4f0c2d00 581 $this->role->archetype = '';
bbdb7070 582 $this->contextlevels = array();
a4df1cbb 583 }
bbdb7070 584 parent::load_current_permissions();
a4df1cbb 585 }
586
bbdb7070 587 public function read_submitted_permissions() {
588 global $DB;
589 $this->errors = array();
590
bbdb7070 591 // Role short name. We clean this in a special way. We want to end up
592 // with only lowercase safe ASCII characters.
593 $shortname = optional_param('shortname', null, PARAM_RAW);
594 if (!is_null($shortname)) {
595 $this->role->shortname = $shortname;
f8311def 596 $this->role->shortname = textlib::specialtoascii($this->role->shortname);
6f3451e5 597 $this->role->shortname = textlib::strtolower(clean_param($this->role->shortname, PARAM_ALPHANUMEXT));
bbdb7070 598 if (empty($this->role->shortname)) {
599 $this->errors['shortname'] = get_string('errorbadroleshortname', 'role');
600 }
601 }
602 if ($DB->record_exists_select('role', 'shortname = ? and id <> ?', array($this->role->shortname, $this->roleid))) {
603 $this->errors['shortname'] = get_string('errorexistsroleshortname', 'role');
604 }
605
c52551dc 606 // Role name.
071e68f9 607 $name = optional_param('name', null, PARAM_TEXT);
c52551dc
PS
608 if (!is_null($name)) {
609 $this->role->name = $name;
610 // Hack: short names of standard roles are equal to archetypes, empty name means localised via lang packs.
611 $archetypes = get_role_archetypes();
612 if (!isset($archetypes[$shortname]) and html_is_blank($this->role->name)) {
613 $this->errors['name'] = get_string('errorbadrolename', 'role');
614 }
615 }
616 if ($this->role->name !== '' and $DB->record_exists_select('role', 'name = ? and id <> ?', array($this->role->name, $this->roleid))) {
617 $this->errors['name'] = get_string('errorexistsrolename', 'role');
618 }
619
bbdb7070 620 // Description.
cc1eebbb 621 $description = optional_param('description', null, PARAM_RAW);
bbdb7070 622 if (!is_null($description)) {
623 $this->role->description = $description;
624 }
625
626 // Legacy type.
4f0c2d00 627 $archetype = optional_param('archetype', null, PARAM_RAW);
c102060d 628 if (isset($archetype)) {
4f0c2d00
PS
629 $archetypes = get_role_archetypes();
630 if (isset($archetypes[$archetype])){
631 $this->role->archetype = $archetype;
bbdb7070 632 } else {
4f0c2d00 633 $this->role->archetype = '';
bbdb7070 634 }
635 }
636
637 // Assignable context levels.
638 foreach ($this->allcontextlevels as $cl => $notused) {
639 $assignable = optional_param('contextlevel' . $cl, null, PARAM_BOOL);
640 if (!is_null($assignable)) {
641 if ($assignable) {
642 $this->contextlevels[$cl] = $cl;
643 } else {
644 unset($this->contextlevels[$cl]);
645 }
646 }
a4df1cbb 647 }
bbdb7070 648
649 // Now read the permissions for each capability.
650 parent::read_submitted_permissions();
651 }
652
653 public function is_submission_valid() {
654 return empty($this->errors);
a4df1cbb 655 }
656
657 /**
bbdb7070 658 * Call this after the table has been initialised, so to indicate that
659 * when save is called, we want to make a duplicate role.
a4df1cbb 660 */
bbdb7070 661 public function make_copy() {
662 $this->roleid = 0;
663 unset($this->role->id);
664 $this->role->name .= ' ' . get_string('copyasnoun');
665 $this->role->shortname .= 'copy';
666 }
667
668 public function get_role_name() {
669 return $this->role->name;
670 }
671
672 public function get_role_id() {
673 return $this->role->id;
674 }
675
4f0c2d00
PS
676 public function get_archetype() {
677 return $this->role->archetype;
bbdb7070 678 }
679
680 protected function load_parent_permissions() {
4f0c2d00 681 $this->parentpermissions = get_default_capabilities($this->role->archetype);
bbdb7070 682 }
683
a4df1cbb 684 public function save_changes() {
bbdb7070 685 global $DB;
686
687 if (!$this->roleid) {
688 // Creating role
4f0c2d00 689 $this->role->id = create_role($this->role->name, $this->role->shortname, $this->role->description, $this->role->archetype);
bbdb7070 690 $this->roleid = $this->role->id; // Needed to make the parent::save_changes(); call work.
691 } else {
692 // Updating role
19a4a32e 693 $DB->update_record('role', $this->role);
a4df1cbb 694 }
695
bbdb7070 696 // Assignable contexts.
697 set_role_contextlevels($this->role->id, $this->contextlevels);
698
699 // Permissions.
700 parent::save_changes();
a4df1cbb 701 }
702
bbdb7070 703 protected function get_name_field($id) {
704 return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->name) . '" />';
705 }
706
707 protected function get_shortname_field($id) {
708 return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->shortname) . '" />';
709 }
0df0df23 710
bbdb7070 711 protected function get_description_field($id) {
712 return print_textarea(true, 10, 50, 50, 10, 'description', $this->role->description, 0, true);
713 }
714
4f0c2d00 715 protected function get_archetype_field($id) {
bbdb7070 716 $options = array();
717 $options[''] = get_string('none');
4f0c2d00
PS
718 foreach(get_role_archetypes() as $type) {
719 $options[$type] = get_string('archetype'.$type, 'role');
bbdb7070 720 }
3211569a 721 return html_writer::select($options, 'archetype', $this->role->archetype, false);
bbdb7070 722 }
723
724 protected function get_assignable_levels_control() {
725 $output = '';
726 foreach ($this->allcontextlevels as $cl => $clname) {
727 $extraarguments = $this->disabled;
728 if (in_array($cl, $this->contextlevels)) {
729 $extraarguments .= 'checked="checked" ';
730 }
731 if (!$this->disabled) {
a90d7cf9 732 $output .= '<input type="hidden" name="contextlevel' . $cl . '" value="0" />';
bbdb7070 733 }
734 $output .= '<input type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
735 '" value="1" ' . $extraarguments . '/> ';
736 $output .= '<label for="cl' . $cl . '">' . $clname . "</label><br />\n";
737 }
738 return $output;
739 }
740
741 protected function print_field($name, $caption, $field) {
04eb4d1e 742 global $OUTPUT;
bbdb7070 743 // Attempt to generate HTML like formslib.
744 echo '<div class="fitem">';
745 echo '<div class="fitemtitle">';
746 if ($name) {
747 echo '<label for="' . $name . '">';
748 }
749 echo $caption;
750 if ($name) {
751 echo "</label>\n";
752 }
753 echo '</div>';
754 if (isset($this->errors[$name])) {
755 $extraclass = ' error';
756 } else {
757 $extraclass = '';
758 }
759 echo '<div class="felement' . $extraclass . '">';
760 if (isset($this->errors[$name])) {
04eb4d1e 761 echo $OUTPUT->error_text($this->errors[$name]);
bbdb7070 762 }
763 echo $field;
764 echo '</div>';
765 echo '</div>';
766 }
767
bed9cec8 768 protected function print_show_hide_advanced_button() {
769 echo '<p class="definenotice">' . get_string('highlightedcellsshowdefault', 'role') . ' </p>';
770 echo '<div class="advancedbutton">';
771 echo '<input type="submit" name="toggleadvanced" value="' . get_string('hideadvanced', 'form') . '" />';
772 echo '</div>';
773 }
774
bbdb7070 775 public function display() {
6397deae 776 global $OUTPUT;
bbdb7070 777 // Extra fields at the top of the page.
778 echo '<div class="topfields clearfix">';
c52551dc
PS
779 $this->print_field('shortname', get_string('roleshortname', 'role').'&nbsp;'.$OUTPUT->help_icon('roleshortname', 'role'), $this->get_shortname_field('shortname'));
780 $this->print_field('name', get_string('customrolename', 'role').'&nbsp;'.$OUTPUT->help_icon('customrolename', 'role'), $this->get_name_field('name'));
781 $this->print_field('edit-description', get_string('customroledescription', 'role').'&nbsp;'.$OUTPUT->help_icon('customroledescription', 'role'), $this->get_description_field('description'));
6397deae 782 $this->print_field('menuarchetype', get_string('archetype', 'role').'&nbsp;'.$OUTPUT->help_icon('archetype', 'role'), $this->get_archetype_field('archetype'));
bbdb7070 783 $this->print_field('', get_string('maybeassignedin', 'role'), $this->get_assignable_levels_control());
784 echo "</div>";
785
bed9cec8 786 $this->print_show_hide_advanced_button();
787
bbdb7070 788 // Now the permissions table.
789 parent::display();
790 }
791
a4df1cbb 792 protected function add_permission_cells($capability) {
f4acee5d 793 /// One cell for each possible permission.
794 foreach ($this->displaypermissions as $perm => $permname) {
795 $strperm = $this->strperms[$permname];
796 $extraclass = '';
797 if ($perm == $this->parentpermissions[$capability->name]) {
798 $extraclass = ' capdefault';
799 }
800 $checked = '';
801 if ($this->permissions[$capability->name] == $perm) {
9c3ea652 802 $checked = 'checked="checked" ';
f4acee5d 803 }
804 echo '<td class="' . $permname . $extraclass . '">';
805 echo '<label><input type="radio" name="' . $capability->name .
9c3ea652 806 '" value="' . $perm . '" ' . $checked . '/> ';
f4acee5d 807 echo '<span class="note">' . $strperm . '</span>';
808 echo '</label></td>';
809 }
810 }
811}
812
813class define_role_table_basic extends define_role_table_advanced {
814 protected $stradvmessage;
815 protected $strallow;
816
817 public function __construct($context, $roleid) {
818 parent::__construct($context, $roleid);
819 $this->displaypermissions = array(CAP_ALLOW => $this->allpermissions[CAP_ALLOW]);
820 $this->stradvmessage = get_string('useshowadvancedtochange', 'role');
821 $this->strallow = $this->strperms[$this->allpermissions[CAP_ALLOW]];
822 }
823
bed9cec8 824 protected function print_show_hide_advanced_button() {
825 echo '<div class="advancedbutton">';
826 echo '<input type="submit" name="toggleadvanced" value="' . get_string('showadvanced', 'form') . '" />';
827 echo '</div>';
828 }
829
f4acee5d 830 protected function add_permission_cells($capability) {
831 $perm = $this->permissions[$capability->name];
832 $permname = $this->allpermissions[$perm];
bbdb7070 833 $defaultperm = $this->allpermissions[$this->parentpermissions[$capability->name]];
f4acee5d 834 echo '<td class="' . $permname . '">';
835 if ($perm == CAP_ALLOW || $perm == CAP_INHERIT) {
836 $checked = '';
837 if ($perm == CAP_ALLOW) {
838 $checked = 'checked="checked" ';
839 }
840 echo '<input type="hidden" name="' . $capability->name . '" value="' . CAP_INHERIT . '" />';
841 echo '<label><input type="checkbox" name="' . $capability->name .
9c3ea652 842 '" value="' . CAP_ALLOW . '" ' . $checked . '/> ' . $this->strallow . '</label>';
f4acee5d 843 } else {
844 echo '<input type="hidden" name="' . $capability->name . '" value="' . $perm . '" />';
845 echo $this->strperms[$permname] . '<span class="note">' . $this->stradvmessage . '</span>';
846 }
847 echo '</td>';
848 }
849}
850class view_role_definition_table extends define_role_table_advanced {
851 public function __construct($context, $roleid) {
852 parent::__construct($context, $roleid);
853 $this->displaypermissions = array(CAP_ALLOW => $this->allpermissions[CAP_ALLOW]);
bbdb7070 854 $this->disabled = 'disabled="disabled" ';
855 }
856
857 public function save_changes() {
858 throw new moodle_exception('invalidaccess');
859 }
860
861 protected function get_name_field($id) {
c52551dc 862 return role_get_name($this->role);
bbdb7070 863 }
864
865 protected function get_shortname_field($id) {
866 return $this->role->shortname;
867 }
868
869 protected function get_description_field($id) {
c52551dc 870 return role_get_description($this->role);
bbdb7070 871 }
872
4f0c2d00
PS
873 protected function get_archetype_field($id) {
874 if (empty($this->role->archetype)) {
bbdb7070 875 return get_string('none');
876 } else {
4f0c2d00 877 return get_string('archetype'.$this->role->archetype, 'role');
bbdb7070 878 }
f4acee5d 879 }
880
bed9cec8 881 protected function print_show_hide_advanced_button() {
882 // Do nothing.
883 }
884
f4acee5d 885 protected function add_permission_cells($capability) {
886 $perm = $this->permissions[$capability->name];
887 $permname = $this->allpermissions[$perm];
bbdb7070 888 $defaultperm = $this->allpermissions[$this->parentpermissions[$capability->name]];
889 if ($permname != $defaultperm) {
890 $default = get_string('defaultx', 'role', $this->strperms[$defaultperm]);
891 } else {
892 $default = "&#xa0;";
893 }
894 echo '<td class="' . $permname . '">' . $this->strperms[$permname] . '<span class="note">' .
895 $default . '</span></td>';
0df0df23 896
a4df1cbb 897 }
898}
899
e9abdd1b 900class override_permissions_table_advanced extends capability_table_with_risks {
8aee9bcc 901 protected $strnotset;
eab8ed9f 902 protected $haslockedcapabilities = false;
c6505f1e 903
904 /**
905 * Constructor
906 *
907 * This method loads loads all the information about the current state of
908 * the overrides, then updates that based on any submitted data. It also
909 * works out which capabilities should be locked for this user.
910 *
911 * @param object $context the context this table relates to.
912 * @param integer $roleid the role being overridden.
913 * @param boolean $safeoverridesonly If true, the user is only allowed to override
914 * capabilities with no risks.
915 */
916 public function __construct($context, $roleid, $safeoverridesonly) {
bbdb7070 917 parent::__construct($context, 'overriderolestable', $roleid);
8aee9bcc 918 $this->displaypermissions = $this->allpermissions;
919 $this->strnotset = get_string('notset', 'role');
c6505f1e 920
a4df1cbb 921 /// Determine which capabilities should be locked.
922 if ($safeoverridesonly) {
923 foreach ($this->capabilities as $capid => $cap) {
4659454a 924 if (!is_safe_capability($cap)) {
a4df1cbb 925 $this->capabilities[$capid]->locked = true;
eab8ed9f 926 $this->haslockedcapabilities = true;
a4df1cbb 927 }
c6505f1e 928 }
929 }
a4df1cbb 930 }
c6505f1e 931
a4df1cbb 932 protected function load_parent_permissions() {
933 global $DB;
c6505f1e 934
eab8ed9f 935 /// Get the capabilities from the parent context, so that can be shown in the interface.
d197ea43 936 $parentcontext = context::instance_by_id(get_parent_contextid($this->context));
a4df1cbb 937 $this->parentpermissions = role_context_capabilities($this->roleid, $parentcontext);
c6505f1e 938 }
939
eab8ed9f
PS
940 public function has_locked_capabilities() {
941 return $this->haslockedcapabilities;
c6505f1e 942 }
943
8aee9bcc 944 protected function add_permission_cells($capability) {
c6505f1e 945 $disabled = '';
a4df1cbb 946 if ($capability->locked || $this->parentpermissions[$capability->name] == CAP_PROHIBIT) {
c6505f1e 947 $disabled = ' disabled="disabled"';
948 }
949
950 /// One cell for each possible permission.
8aee9bcc 951 foreach ($this->displaypermissions as $perm => $permname) {
952 $strperm = $this->strperms[$permname];
c6505f1e 953 $extraclass = '';
a4df1cbb 954 if ($perm != CAP_INHERIT && $perm == $this->parentpermissions[$capability->name]) {
c6505f1e 955 $extraclass = ' capcurrent';
956 }
957 $checked = '';
a4df1cbb 958 if ($this->permissions[$capability->name] == $perm) {
9c3ea652 959 $checked = 'checked="checked" ';
c6505f1e 960 }
961 echo '<td class="' . $permname . $extraclass . '">';
8aee9bcc 962 echo '<label><input type="radio" name="' . $capability->name .
9c3ea652 963 '" value="' . $perm . '" ' . $checked . $disabled . '/> ';
8aee9bcc 964 if ($perm == CAP_INHERIT) {
a4df1cbb 965 $inherited = $this->parentpermissions[$capability->name];
8aee9bcc 966 if ($inherited == CAP_INHERIT) {
967 $inherited = $this->strnotset;
968 } else {
969 $inherited = $this->strperms[$this->allpermissions[$inherited]];
970 }
971 $strperm .= ' (' . $inherited . ')';
972 }
973 echo '<span class="note">' . $strperm . '</span>';
974 echo '</label></td>';
975 }
976 }
977}
978
1e8e4687 979// User selectors for managing role assignments ================================
980
981/**
982 * Base class to avoid duplicating code.
983 */
984abstract class role_assign_user_selector_base extends user_selector_base {
985 const MAX_USERS_PER_PAGE = 100;
986
987 protected $roleid;
988 protected $context;
989
990 /**
991 * @param string $name control name
992 * @param array $options should have two elements with keys groupid and courseid.
993 */
994 public function __construct($name, $options) {
995 global $CFG;
1e8e4687 996 if (isset($options['context'])) {
997 $this->context = $options['context'];
998 } else {
d197ea43 999 $this->context = context::instance_by_id($options['contextid']);
1e8e4687 1000 }
5c60a847 1001 $options['accesscontext'] = $this->context;
1002 parent::__construct($name, $options);
1003 $this->roleid = $options['roleid'];
1e8e4687 1004 require_once($CFG->dirroot . '/group/lib.php');
1005 }
1006
1007 protected function get_options() {
8aee9bcc 1008 global $CFG;
1e8e4687 1009 $options = parent::get_options();
8aee9bcc 1010 $options['file'] = $CFG->admin . '/roles/lib.php';
1e8e4687 1011 $options['roleid'] = $this->roleid;
1012 $options['contextid'] = $this->context->id;
1013 return $options;
1014 }
1015}
1016
1017/**
1018 * User selector subclass for the list of potential users on the assign roles page,
1019 * when we are assigning in a context below the course level. (CONTEXT_MODULE and
e92c286c 1020 * some CONTEXT_BLOCK).
1e8e4687 1021 *
df997f84 1022 * This returns only enrolled users in this context.
1e8e4687 1023 */
1024class potential_assignees_below_course extends role_assign_user_selector_base {
1025 public function find_users($search) {
1026 global $DB;
1027
df997f84 1028 list($enrolsql, $eparams) = get_enrolled_sql($this->context);
1e8e4687 1029
1030 // Now we have to go to the database.
1031 list($wherecondition, $params) = $this->search_sql($search, 'u');
df997f84
PS
1032 $params = array_merge($params, $eparams);
1033
1e8e4687 1034 if ($wherecondition) {
1035 $wherecondition = ' AND ' . $wherecondition;
1036 }
1e8e4687 1037
df997f84
PS
1038 $fields = 'SELECT ' . $this->required_fields_sql('u');
1039 $countfields = 'SELECT COUNT(u.id)';
1e8e4687 1040
1041 $sql = " FROM {user} u
cb94adc2
PS
1042 LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.roleid = :roleid AND ra.contextid = :contextid)
1043 WHERE u.id IN ($enrolsql)
1044 $wherecondition
1045 AND ra.id IS NULL";
4f0c2d00
PS
1046 $params['contextid'] = $this->context->id;
1047 $params['roleid'] = $this->roleid;
1e8e4687 1048
9695ff81
TH
1049 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
1050 $order = ' ORDER BY ' . $sort;
1051
1e8e4687 1052 // Check to see if there are too many to show sensibly.
1053 if (!$this->is_validating()) {
1054 $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
1055 if ($potentialmemberscount > role_assign_user_selector_base::MAX_USERS_PER_PAGE) {
1056 return $this->too_many_results($search, $potentialmemberscount);
1057 }
1058 }
1059
1060 // If not, show them.
9695ff81 1061 $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
1e8e4687 1062
1063 if (empty($availableusers)) {
1064 return array();
1065 }
1066
1067 if ($search) {
1068 $groupname = get_string('potusersmatching', 'role', $search);
1069 } else {
1070 $groupname = get_string('potusers', 'role');
1071 }
1072
1073 return array($groupname => $availableusers);
1074 }
1075}
1076
9f7b195f
PS
1077/**
1078 * User selector subclass for the selection of users in the check permissions page.
1079 *
1080 * @copyright 2012 Petr Skoda {@link http://skodak.org}
1081 */
1082class role_check_users_selector extends user_selector_base {
1083 const MAX_ENROLLED_PER_PAGE = 100;
1084 const MAX_POTENTIAL_PER_PAGE = 100;
1085
1086 /** @var bool limit listing of users to enrolled only */
1087 var $onlyenrolled;
1088
1089 /**
1090 * Constructor.
1091 *
1092 * @param string $name the control name/id for use in the HTML.
1093 * @param array $options other options needed to construct this selector.
1094 * You must be able to clone a userselector by doing new get_class($us)($us->get_name(), $us->get_options());
1095 */
1096 public function __construct($name, $options) {
1097 if (!isset($options['multiselect'])) {
1098 $options['multiselect'] = false;
1099 }
1100 parent::__construct($name, $options);
1101
1102 $coursecontext = $this->accesscontext->get_course_context(false);
1103 if ($coursecontext and $coursecontext->id != SITEID and !has_capability('moodle/role:manage', $coursecontext)) {
1104 // Prevent normal teachers from looking up all users.
1105 $this->onlyenrolled = true;
1106 } else {
1107 $this->onlyenrolled = false;
1108 }
1109 }
1110
1111 public function find_users($search) {
1112 global $DB;
1113
1114 list($wherecondition, $params) = $this->search_sql($search, 'u');
1115
1116 $fields = 'SELECT ' . $this->required_fields_sql('u');
1117 $countfields = 'SELECT COUNT(1)';
1118
1119 $coursecontext = $this->accesscontext->get_course_context(false);
1120
1121 if ($coursecontext and $coursecontext != SITEID) {
1122 $sql1 = " FROM {user} u
1123 JOIN {user_enrolments} ue ON (ue.userid = u.id)
1124 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
1125 WHERE $wherecondition";
1126 $params['courseid1'] = $coursecontext->instanceid;
1127
1128 if ($this->onlyenrolled) {
1129 $sql2 = null;
1130 } else {
1131 $sql2 = " FROM {user} u
1132 LEFT JOIN ({user_enrolments} ue
1133 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid2)) ON (ue.userid = u.id)
1134 WHERE $wherecondition
1135 AND ue.id IS NULL";
1136 $params['courseid2'] = $coursecontext->instanceid;
1137 }
1138
1139 } else {
1140 if ($this->onlyenrolled) {
1141 // Bad luck, current user may not view only enrolled users.
1142 return array();
1143 }
1144 $sql1 = null;
1145 $sql2 = " FROM {user} u
1146 WHERE $wherecondition";
1147 }
1148
9f7b195f
PS
1149 $params['contextid'] = $this->accesscontext->id;
1150
e24d97f9
EL
1151 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
1152 $order = ' ORDER BY ' . $sort;
1153
9f7b195f
PS
1154 $result = array();
1155
1156 if ($search) {
1157 $groupname1 = get_string('enrolledusersmatching', 'enrol', $search);
1158 $groupname2 = get_string('potusersmatching', 'role', $search);
1159 } else {
1160 $groupname1 = get_string('enrolledusers', 'enrol');
1161 $groupname2 = get_string('potusers', 'role');
1162 }
1163
1164 if ($sql1) {
1165 $enrolleduserscount = $DB->count_records_sql($countfields . $sql1, $params);
1166 if (!$this->is_validating() and $enrolleduserscount > $this::MAX_ENROLLED_PER_PAGE) {
1167 $result[$groupname1] = array();
1168 $toomany = $this->too_many_results($search, $enrolleduserscount);
1169 $result[implode(' - ', array_keys($toomany))] = array();
1170
1171 } else {
e24d97f9 1172 $enrolledusers = $DB->get_records_sql($fields . $sql1 . $order, array_merge($params, $sortparams));
9f7b195f
PS
1173 if ($enrolledusers) {
1174 $result[$groupname1] = $enrolledusers;
1175 }
1176 }
1177 if ($sql2) {
1178 $result[''] = array();
1179 }
1180 }
1181 if ($sql2) {
1182 $otheruserscount = $DB->count_records_sql($countfields . $sql2, $params);
1183 if (!$this->is_validating() and $otheruserscount > $this::MAX_POTENTIAL_PER_PAGE) {
1184 $result[$groupname2] = array();
1185 $toomany = $this->too_many_results($search, $otheruserscount);
1186 $result[implode(' - ', array_keys($toomany))] = array();
1187 } else {
e24d97f9 1188 $otherusers = $DB->get_records_sql($fields . $sql2 . $order, array_merge($params, $sortparams));
9f7b195f
PS
1189 if ($otherusers) {
1190 $result[$groupname2] = $otherusers;
1191 }
1192 }
1193 }
1194
1195 return $result;
1196 }
1197
1198 protected function get_options() {
1199 global $CFG;
1200 $options = parent::get_options();
1201 $options['file'] = $CFG->admin . '/roles/lib.php';
1202 return $options;
1203 }
1204}
1205
1e8e4687 1206/**
1207 * User selector subclass for the list of potential users on the assign roles page,
1208 * when we are assigning in a context at or above the course level. In this case we
1209 * show all the users in the system who do not already have the role.
1210 */
1211class potential_assignees_course_and_above extends role_assign_user_selector_base {
1212 public function find_users($search) {
1213 global $DB;
1214
1215 list($wherecondition, $params) = $this->search_sql($search, '');
1216
1217 $fields = 'SELECT ' . $this->required_fields_sql('');
1218 $countfields = 'SELECT COUNT(1)';
1219
0df0df23 1220 $sql = " FROM {user}
1e8e4687 1221 WHERE $wherecondition
1222 AND id NOT IN (
3c2ed2d7
MG
1223 SELECT r.userid
1224 FROM {role_assignments} r
4f0c2d00 1225 WHERE r.contextid = :contextid
4f0c2d00 1226 AND r.roleid = :roleid)";
9695ff81
TH
1227
1228 list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
1229 $order = ' ORDER BY ' . $sort;
1e8e4687 1230
4f0c2d00
PS
1231 $params['contextid'] = $this->context->id;
1232 $params['roleid'] = $this->roleid;
1e8e4687 1233
1234 if (!$this->is_validating()) {
1235 $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
1236 if ($potentialmemberscount > role_assign_user_selector_base::MAX_USERS_PER_PAGE) {
1237 return $this->too_many_results($search, $potentialmemberscount);
1238 }
1239 }
1240
9695ff81 1241 $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
1e8e4687 1242
1243 if (empty($availableusers)) {
1244 return array();
1245 }
1246
1247 if ($search) {
1248 $groupname = get_string('potusersmatching', 'role', $search);
1249 } else {
1250 $groupname = get_string('potusers', 'role');
1251 }
1252
1253 return array($groupname => $availableusers);
1254 }
1255}
1256
1257/**
1258 * User selector subclass for the list of users who already have the role in
1259 * question on the assign roles page.
1260 */
1261class existing_role_holders extends role_assign_user_selector_base {
1e8e4687 1262
1263 public function __construct($name, $options) {
1264 parent::__construct($name, $options);
1e8e4687 1265 }
1266
1267 public function find_users($search) {
698ae7eb 1268 global $DB;
1269
1e8e4687 1270 list($wherecondition, $params) = $this->search_sql($search, 'u');
cf717dc2 1271 list($ctxcondition, $ctxparams) = $DB->get_in_or_equal(get_parent_contexts($this->context, true), SQL_PARAMS_NAMED, 'ctx');
698ae7eb 1272 $params = array_merge($params, $ctxparams);
4f0c2d00 1273 $params['roleid'] = $this->roleid;
1e8e4687 1274
9695ff81
TH
1275 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
1276 $params = array_merge($params, $sortparams);
1277
df997f84 1278 $sql = "SELECT ra.id as raid," . $this->required_fields_sql('u') . ",ra.contextid,ra.component
698ae7eb 1279 FROM {role_assignments} ra
1280 JOIN {user} u ON u.id = ra.userid
1281 JOIN {context} ctx ON ra.contextid = ctx.id
1282 WHERE
1283 $wherecondition AND
1284 ctx.id $ctxcondition AND
4f0c2d00 1285 ra.roleid = :roleid
9695ff81 1286 ORDER BY ctx.depth DESC, ra.component, $sort";
698ae7eb 1287 $contextusers = $DB->get_records_sql($sql, $params);
1288
1289 // No users at all.
1e8e4687 1290 if (empty($contextusers)) {
1291 return array();
1292 }
1293
698ae7eb 1294 // We have users. Out put them in groups by context depth.
1295 // To help the loop below, tack a dummy user on the end of the results
1296 // array, to trigger output of the last group.
1297 $dummyuser = new stdClass;
1298 $dummyuser->contextid = 0;
1299 $dummyuser->id = 0;
df997f84 1300 $dummyuser->component = '';
698ae7eb 1301 $contextusers[] = $dummyuser;
1302 $results = array(); // The results array we are building up.
1303 $doneusers = array(); // Ensures we only list each user at most once.
1304 $currentcontextid = $this->context->id;
1305 $currentgroup = array();
1306 foreach ($contextusers as $user) {
1307 if (isset($doneusers[$user->id])) {
1308 continue;
1309 }
1310 $doneusers[$user->id] = 1;
1311 if ($user->contextid != $currentcontextid) {
1312 // We have got to the end of the previous group. Add it to the results array.
1313 if ($currentcontextid == $this->context->id) {
1314 $groupname = $this->this_con_group_name($search, count($currentgroup));
1315 } else {
1316 $groupname = $this->parent_con_group_name($search, $currentcontextid);
1317 }
1318 $results[$groupname] = $currentgroup;
1319 // Get ready for the next group.
1320 $currentcontextid = $user->contextid;
1321 $currentgroup = array();
1322 }
1323 // Add this user to the group we are building up.
1324 unset($user->contextid);
1325 if ($currentcontextid != $this->context->id) {
1326 $user->disabled = true;
1327 }
df997f84
PS
1328 if ($user->component !== '') {
1329 // bad luck, you can tweak only manual role assignments
1330 $user->disabled = true;
1331 }
1332 unset($user->component);
698ae7eb 1333 $currentgroup[$user->id] = $user;
1334 }
1335
1336 return $results;
1337 }
1338
1339 protected function this_con_group_name($search, $numusers) {
e6a3587c 1340 if ($this->context->contextlevel == CONTEXT_SYSTEM) {
1341 // Special case in the System context.
1342 if ($search) {
1343 return get_string('extusersmatching', 'role', $search);
1344 } else {
1345 return get_string('extusers', 'role');
1346 }
1347 }
490740d6 1348 $contexttype = get_contextlevel_name($this->context->contextlevel);
1e8e4687 1349 if ($search) {
490740d6 1350 $a = new stdClass;
1351 $a->search = $search;
1352 $a->contexttype = $contexttype;
698ae7eb 1353 if ($numusers) {
490740d6 1354 return get_string('usersinthisxmatching', 'role', $a);
698ae7eb 1355 } else {
490740d6 1356 return get_string('noneinthisxmatching', 'role', $a);
698ae7eb 1357 }
1e8e4687 1358 } else {
698ae7eb 1359 if ($numusers) {
490740d6 1360 return get_string('usersinthisx', 'role', $contexttype);
698ae7eb 1361 } else {
490740d6 1362 return get_string('noneinthisx', 'role', $contexttype);
698ae7eb 1363 }
1e8e4687 1364 }
698ae7eb 1365 }
1e8e4687 1366
698ae7eb 1367 protected function parent_con_group_name($search, $contextid) {
d197ea43 1368 $context = context::instance_by_id($contextid);
698ae7eb 1369 $contextname = print_context_name($context, true, true);
1370 if ($search) {
1371 $a = new stdClass;
1372 $a->contextname = $contextname;
1373 $a->search = $search;
1374 return get_string('usersfrommatching', 'role', $a);
1375 } else {
1376 return get_string('usersfrom', 'role', $contextname);
1377 }
1e8e4687 1378 }
1e8e4687 1379}
1380
9654643e 1381/**
c468795c 1382 * Base class for managing the data in the grid of checkboxes on the role allow
1383 * allow/overrides/switch editing pages (allow.php).
9654643e 1384 */
1385abstract class role_allow_role_page {
1386 protected $tablename;
1387 protected $targetcolname;
9654643e 1388 protected $roles;
1389 protected $allowed = null;
1390
c468795c 1391 /**
1392 * @param string $tablename the table where our data is stored.
1393 * @param string $targetcolname the name of the target role id column.
1394 */
9654643e 1395 public function __construct($tablename, $targetcolname) {
1396 $this->tablename = $tablename;
1397 $this->targetcolname = $targetcolname;
9654643e 1398 $this->load_required_roles();
1399 }
1400
9654643e 1401 /**
c468795c 1402 * Load information about all the roles we will need information about.
9654643e 1403 */
1404 protected function load_required_roles() {
1405 /// Get all roles
bf006d2c 1406 $this->roles = role_fix_names(get_all_roles(), context_system::instance(), ROLENAME_ORIGINAL);
9654643e 1407 }
1408
1409 /**
1410 * Update the data with the new settings submitted by the user.
1411 */
c468795c 1412 public function process_submission() {
1413 global $DB;
1414 /// Delete all records, then add back the ones that should be allowed.
9654643e 1415 $DB->delete_records($this->tablename);
1416 foreach ($this->roles as $fromroleid => $notused) {
1417 foreach ($this->roles as $targetroleid => $alsonotused) {
1418 if (optional_param('s_' . $fromroleid . '_' . $targetroleid, false, PARAM_BOOL)) {
1419 $this->set_allow($fromroleid, $targetroleid);
1420 }
1421 }
1422 }
1423 }
1424
1425 /**
1426 * Set one allow in the database.
c468795c 1427 * @param integer $fromroleid
1428 * @param integer $targetroleid
9654643e 1429 */
1430 protected abstract function set_allow($fromroleid, $targetroleid);
1431
c468795c 1432 /**
1433 * Load the current allows from the database.
1434 */
9654643e 1435 public function load_current_settings() {
1436 global $DB;
1437 /// Load the current settings
1438 $this->allowed = array();
1439 foreach ($this->roles as $role) {
eab8ed9f 1440 // Make an array $role->id => false. This is probably too clever for its own good.
9654643e 1441 $this->allowed[$role->id] = array_combine(array_keys($this->roles), array_fill(0, count($this->roles), false));
1442 }
1443 $rs = $DB->get_recordset($this->tablename);
1444 foreach ($rs as $allow) {
1445 $this->allowed[$allow->roleid][$allow->{$this->targetcolname}] = true;
1446 }
017c440b 1447 $rs->close();
9654643e 1448 }
1449
c468795c 1450 /**
1451 * @param integer $targetroleid a role id.
1452 * @return boolean whether the user should be allowed to select this role as a
1453 * target role.
1454 */
1455 protected function is_allowed_target($targetroleid) {
1456 return true;
1457 }
1458
1459 /**
1460 * @return object a $table structure that can be passed to print_table, containing
0df0df23 1461 * one cell for each checkbox.
c468795c 1462 */
9654643e 1463 public function get_table() {
414a4a91 1464 $table = new html_table();
9654643e 1465 $table->tablealign = 'center';
1466 $table->cellpadding = 5;
1467 $table->cellspacing = 0;
1468 $table->width = '90%';
c468795c 1469 $table->align = array('left');
9654643e 1470 $table->rotateheaders = true;
1471 $table->head = array('&#xa0;');
c468795c 1472 $table->colclasses = array('');
1473
9654643e 1474 /// Add role name headers.
1475 foreach ($this->roles as $targetrole) {
1476 $table->head[] = $targetrole->localname;
1477 $table->align[] = 'left';
c468795c 1478 if ($this->is_allowed_target($targetrole->id)) {
1479 $table->colclasses[] = '';
1480 } else {
1481 $table->colclasses[] = 'dimmed_text';
1482 }
9654643e 1483 }
c468795c 1484
9654643e 1485 /// Now the rest of the table.
1486 foreach ($this->roles as $fromrole) {
1487 $row = array($fromrole->localname);
1488 foreach ($this->roles as $targetrole) {
c468795c 1489 $checked = '';
1490 $disabled = '';
9654643e 1491 if ($this->allowed[$fromrole->id][$targetrole->id]) {
9c3ea652 1492 $checked = 'checked="checked" ';
c468795c 1493 }
1494 if (!$this->is_allowed_target($targetrole->id)) {
9c3ea652 1495 $disabled = 'disabled="disabled" ';
9654643e 1496 }
1497 $name = 's_' . $fromrole->id . '_' . $targetrole->id;
1498 $tooltip = $this->get_cell_tooltip($fromrole, $targetrole);
c468795c 1499 $row[] = '<input type="checkbox" name="' . $name . '" id="' . $name .
9c3ea652 1500 '" title="' . $tooltip . '" value="1" ' . $checked . $disabled . '/>' .
9654643e 1501 '<label for="' . $name . '" class="accesshide">' . $tooltip . '</label>';
1502 }
1503 $table->data[] = $row;
1504 }
1505
1506 return $table;
1507 }
91eb445c 1508
c468795c 1509 /**
1510 * Snippet of text displayed above the table, telling the admin what to do.
1511 * @return unknown_type
1512 */
91eb445c 1513 public abstract function get_intro_text();
9654643e 1514}
1515
c468795c 1516/**
1517 * Subclass of role_allow_role_page for the Allow assigns tab.
1518 */
9654643e 1519class role_allow_assign_page extends role_allow_role_page {
1520 public function __construct() {
1521 parent::__construct('role_allow_assign', 'allowassign');
1522 }
1523
1524 protected function set_allow($fromroleid, $targetroleid) {
1525 allow_assign($fromroleid, $targetroleid);
1526 }
1527
1528 protected function get_cell_tooltip($fromrole, $targetrole) {
1529 $a = new stdClass;
1530 $a->fromrole = $fromrole->localname;
1531 $a->targetrole = $targetrole->localname;
1532 return get_string('allowroletoassign', 'role', $a);
1533 }
91eb445c 1534
1535 public function get_intro_text() {
1536 return get_string('configallowassign', 'admin');
1537 }
1538}
1539
c468795c 1540/**
1541 * Subclass of role_allow_role_page for the Allow overrides tab.
1542 */
91eb445c 1543class role_allow_override_page extends role_allow_role_page {
1544 public function __construct() {
1545 parent::__construct('role_allow_override', 'allowoverride');
1546 }
1547
1548 protected function set_allow($fromroleid, $targetroleid) {
1549 allow_override($fromroleid, $targetroleid);
1550 }
1551
1552 protected function get_cell_tooltip($fromrole, $targetrole) {
1553 $a = new stdClass;
1554 $a->fromrole = $fromrole->localname;
1555 $a->targetrole = $targetrole->localname;
1556 return get_string('allowroletooverride', 'role', $a);
1557 }
1558
1559 public function get_intro_text() {
1560 return get_string('configallowoverride2', 'admin');
1561 }
9654643e 1562}
1563
c468795c 1564/**
1565 * Subclass of role_allow_role_page for the Allow switches tab.
1566 */
1567class role_allow_switch_page extends role_allow_role_page {
1568 protected $allowedtargetroles;
1569
1570 public function __construct() {
1571 parent::__construct('role_allow_switch', 'allowswitch');
1572 }
1573
1574 protected function load_required_roles() {
df997f84 1575 global $DB;
c468795c 1576 parent::load_required_roles();
da904f85 1577 $this->allowedtargetroles = $DB->get_records_menu('role', NULL, 'id');
c468795c 1578 }
1579
1580 protected function set_allow($fromroleid, $targetroleid) {
1581 allow_switch($fromroleid, $targetroleid);
1582 }
1583
1584 protected function is_allowed_target($targetroleid) {
1585 return isset($this->allowedtargetroles[$targetroleid]);
1586 }
1587
1588 protected function get_cell_tooltip($fromrole, $targetrole) {
1589 $a = new stdClass;
1590 $a->fromrole = $fromrole->localname;
1591 $a->targetrole = $targetrole->localname;
1592 return get_string('allowroletoswitch', 'role', $a);
1593 }
1594
1595 public function get_intro_text() {
1596 return get_string('configallowswitch', 'admin');
1597 }
1598}
1599
e92c286c 1600/**
1601 * Get the potential assignees selector for a given context.
1602 *
1603 * If this context is a course context, or inside a course context (module or
1604 * some blocks) then return a potential_assignees_below_course object. Otherwise
1605 * return a potential_assignees_course_and_above.
1606 *
eab8ed9f
PS
1607 * @param stdClass $context a context.
1608 * @param string $name passed to user selector constructor.
1609 * @param array $options to user selector constructor.
e92c286c 1610 * @return user_selector_base an appropriate user selector.
1611 */
1612function roles_get_potential_user_selector($context, $name, $options) {
1613 $blockinsidecourse = false;
1614 if ($context->contextlevel == CONTEXT_BLOCK) {
d197ea43 1615 $parentcontext = context::instance_by_id(get_parent_contextid($context));
e92c286c 1616 $blockinsidecourse = in_array($parentcontext->contextlevel, array(CONTEXT_MODULE, CONTEXT_COURSE));
1617 }
1618
1619 if (($context->contextlevel == CONTEXT_MODULE || $blockinsidecourse) &&
1620 !is_inside_frontpage($context)) {
1621 $potentialuserselector = new potential_assignees_below_course('addselect', $options);
1622 } else {
1623 $potentialuserselector = new potential_assignees_course_and_above('addselect', $options);
1624 }
1625 return $potentialuserselector;
1626}
1627
4f0c2d00
PS
1628class admins_potential_selector extends user_selector_base {
1629 /**
1630 * @param string $name control name
1631 * @param array $options should have two elements with keys groupid and courseid.
1632 */
6dde374a
TH
1633 public function __construct($name = null, $options = array()) {
1634 global $CFG;
1635 if (is_null($name)) {
1636 $name = 'addselect';
1637 }
1638 $options['multiselect'] = false;
1639 $options['exclude'] = explode(',', $CFG->siteadmins);
1640 parent::__construct($name, $options);
4f0c2d00
PS
1641 }
1642
1643 public function find_users($search) {
d44df69b 1644 global $CFG, $DB;
4f0c2d00
PS
1645 list($wherecondition, $params) = $this->search_sql($search, '');
1646
1647 $fields = 'SELECT ' . $this->required_fields_sql('');
1648 $countfields = 'SELECT COUNT(1)';
1649
1650 $sql = " FROM {user}
d44df69b 1651 WHERE $wherecondition AND mnethostid = :localmnet";
9695ff81 1652
d44df69b 1653 $params['localmnet'] = $CFG->mnet_localhost_id; // it could be dangerous to make remote users admins and also this could lead to other problems
4f0c2d00 1654
9695ff81
TH
1655 list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
1656 $order = ' ORDER BY ' . $sort;
1657
01beeae9
PS
1658 // Check to see if there are too many to show sensibly.
1659 if (!$this->is_validating()) {
1660 $potentialcount = $DB->count_records_sql($countfields . $sql, $params);
1661 if ($potentialcount > 100) {
1662 return $this->too_many_results($search, $potentialcount);
1663 }
1664 }
1665
9695ff81 1666 $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
4f0c2d00
PS
1667
1668 if (empty($availableusers)) {
1669 return array();
1670 }
1671
1672 if ($search) {
1673 $groupname = get_string('potusersmatching', 'role', $search);
1674 } else {
1675 $groupname = get_string('potusers', 'role');
1676 }
1677
1678 return array($groupname => $availableusers);
1679 }
be42eca7
PS
1680
1681 protected function get_options() {
1682 global $CFG;
1683 $options = parent::get_options();
1684 $options['file'] = $CFG->admin . '/roles/lib.php';
1685 return $options;
1686 }
4f0c2d00
PS
1687}
1688
1689class admins_existing_selector extends user_selector_base {
1690 /**
1691 * @param string $name control name
1692 * @param array $options should have two elements with keys groupid and courseid.
1693 */
6dde374a
TH
1694 public function __construct($name = null, $options = array()) {
1695 if (is_null($name)) {
1696 $name = 'removeselect';
1697 }
1698 $options['multiselect'] = false;
1699 parent::__construct($name, $options);
4f0c2d00
PS
1700 }
1701
1702 public function find_users($search) {
1703 global $DB, $CFG;
1704 list($wherecondition, $params) = $this->search_sql($search, '');
1705
1706 $fields = 'SELECT ' . $this->required_fields_sql('');
1707 $countfields = 'SELECT COUNT(1)';
1708
1709 if ($wherecondition) {
1710 $wherecondition = "$wherecondition AND id IN ($CFG->siteadmins)";
1711 } else {
1712 $wherecondition = "id IN ($CFG->siteadmins)";
1713 }
1714 $sql = " FROM {user}
1715 WHERE $wherecondition";
9695ff81
TH
1716
1717 list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
1718 $params = array_merge($params, $sortparams);
1719 $order = ' ORDER BY ' . $sort;
4f0c2d00
PS
1720
1721 $availableusers = $DB->get_records_sql($fields . $sql . $order, $params);
1722
1723 if (empty($availableusers)) {
1724 return array();
1725 }
1726
bb6ccfa5 1727 $mainadmin = array();
d0577078
TH
1728 $mainadminuser = get_admin();
1729 if ($mainadminuser && isset($availableusers[$mainadminuser->id])) {
1730 $mainadmin = array($mainadminuser->id => $availableusers[$mainadminuser->id]);
1731 unset($availableusers[$mainadminuser->id]);
4f0c2d00
PS
1732 }
1733
bb6ccfa5
PS
1734 $result = array();
1735 if ($mainadmin) {
1736 $result[get_string('mainadmin', 'role')] = $mainadmin;
1737 }
1738
1739 if ($availableusers) {
1740 if ($search) {
1741 $groupname = get_string('extusersmatching', 'role', $search);
1742 } else {
1743 $groupname = get_string('extusers', 'role');
1744 }
1745 $result[$groupname] = $availableusers;
1746 }
1747
1748 return $result;
4f0c2d00 1749 }
be42eca7
PS
1750
1751 protected function get_options() {
1752 global $CFG;
1753 $options = parent::get_options();
1754 $options['file'] = $CFG->admin . '/roles/lib.php';
1755 return $options;
1756 }
4f0c2d00 1757}