MDL-40337 use class loader for admin/roles/*
[moodle.git] / admin / roles / classes / define_role_table_advanced.php
CommitLineData
bc7b53fb
PS
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 * Library code used by the roles administration interfaces.
19 *
20 * @package core_role
21 * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27
28/**
29 * As well as tracking the permissions information about the role we are creating
30 * or editing, we also track the other information about the role. (This class is
31 * starting to be more and more like a formslib form in some respects.)
32 */
33class core_role_define_role_table_advanced extends core_role_capability_table_with_risks {
34 /** Used to store other information (besides permissions) about the role we are creating/editing. */
35 protected $role;
36 /** Used to store errors found when validating the data. */
37 protected $errors;
38 protected $contextlevels;
39 protected $allcontextlevels;
40 protected $disabled = '';
41
42 protected $allowassign;
43 protected $allowoverride;
44 protected $allowswitch;
45
46 public function __construct($context, $roleid) {
47 $this->roleid = $roleid;
48 parent::__construct($context, 'defineroletable', $roleid);
49 $this->displaypermissions = $this->allpermissions;
50 $this->strperms[$this->allpermissions[CAP_INHERIT]] = get_string('notset', 'role');
51
52 $this->allcontextlevels = array(
53 CONTEXT_SYSTEM => get_string('coresystem'),
54 CONTEXT_USER => get_string('user'),
55 CONTEXT_COURSECAT => get_string('category'),
56 CONTEXT_COURSE => get_string('course'),
57 CONTEXT_MODULE => get_string('activitymodule'),
58 CONTEXT_BLOCK => get_string('block')
59 );
60 }
61
62 protected function load_current_permissions() {
63 global $DB;
64 if ($this->roleid) {
65 if (!$this->role = $DB->get_record('role', array('id' => $this->roleid))) {
66 throw new moodle_exception('invalidroleid');
67 }
68 $contextlevels = get_role_contextlevels($this->roleid);
69 // Put the contextlevels in the array keys, as well as the values.
70 if (!empty($contextlevels)) {
71 $this->contextlevels = array_combine($contextlevels, $contextlevels);
72 } else {
73 $this->contextlevels = array();
74 }
75 $this->allowassign = array_keys($this->get_allow_roles_list('assign'));
76 $this->allowoverride = array_keys($this->get_allow_roles_list('override'));
77 $this->allowswitch = array_keys($this->get_allow_roles_list('switch'));
78
79 } else {
80 $this->role = new stdClass;
81 $this->role->name = '';
82 $this->role->shortname = '';
83 $this->role->description = '';
84 $this->role->archetype = '';
85 $this->contextlevels = array();
86 $this->allowassign = array();
87 $this->allowoverride = array();
88 $this->allowswitch = array();
89 }
90 parent::load_current_permissions();
91 }
92
93 public function read_submitted_permissions() {
94 global $DB;
95 $this->errors = array();
96
97 // Role short name. We clean this in a special way. We want to end up
98 // with only lowercase safe ASCII characters.
99 $shortname = optional_param('shortname', null, PARAM_RAW);
100 if (!is_null($shortname)) {
101 $this->role->shortname = $shortname;
102 $this->role->shortname = textlib::specialtoascii($this->role->shortname);
103 $this->role->shortname = textlib::strtolower(clean_param($this->role->shortname, PARAM_ALPHANUMEXT));
104 if (empty($this->role->shortname)) {
105 $this->errors['shortname'] = get_string('errorbadroleshortname', 'role');
106 }
107 }
108 if ($DB->record_exists_select('role', 'shortname = ? and id <> ?', array($this->role->shortname, $this->roleid))) {
109 $this->errors['shortname'] = get_string('errorexistsroleshortname', 'role');
110 }
111
112 // Role name.
113 $name = optional_param('name', null, PARAM_TEXT);
114 if (!is_null($name)) {
115 $this->role->name = $name;
116 // Hack: short names of standard roles are equal to archetypes, empty name means localised via lang packs.
117 $archetypes = get_role_archetypes();
118 if (!isset($archetypes[$shortname]) and html_is_blank($this->role->name)) {
119 $this->errors['name'] = get_string('errorbadrolename', 'role');
120 }
121 }
122 if ($this->role->name !== '' and $DB->record_exists_select('role', 'name = ? and id <> ?', array($this->role->name, $this->roleid))) {
123 $this->errors['name'] = get_string('errorexistsrolename', 'role');
124 }
125
126 // Description.
127 $description = optional_param('description', null, PARAM_RAW);
128 if (!is_null($description)) {
129 $this->role->description = $description;
130 }
131
132 // Legacy type.
133 $archetype = optional_param('archetype', null, PARAM_RAW);
134 if (isset($archetype)) {
135 $archetypes = get_role_archetypes();
136 if (isset($archetypes[$archetype])){
137 $this->role->archetype = $archetype;
138 } else {
139 $this->role->archetype = '';
140 }
141 }
142
143 // Assignable context levels.
144 foreach ($this->allcontextlevels as $cl => $notused) {
145 $assignable = optional_param('contextlevel' . $cl, null, PARAM_BOOL);
146 if (!is_null($assignable)) {
147 if ($assignable) {
148 $this->contextlevels[$cl] = $cl;
149 } else {
150 unset($this->contextlevels[$cl]);
151 }
152 }
153 }
154
155 // Allowed roles.
156 $allow = optional_param_array('allowassign', null, PARAM_INT);
157 if (!is_null($allow)) {
158 $this->allowassign = $allow;
159 }
160 $allow = optional_param_array('allowoverride', null, PARAM_INT);
161 if (!is_null($allow)) {
162 $this->allowoverride = $allow;
163 }
164 $allow = optional_param_array('allowswitch', null, PARAM_INT);
165 if (!is_null($allow)) {
166 $this->allowswitch = $allow;
167 }
168
169 // Now read the permissions for each capability.
170 parent::read_submitted_permissions();
171 }
172
173 public function is_submission_valid() {
174 return empty($this->errors);
175 }
176
177 /**
178 * Call this after the table has been initialised,
179 * this resets everything to that role.
180 *
181 * @param int $roleid role id or 0 for no role
182 * @param array $options array with following keys:
183 * 'name', 'shortname', 'description', 'permissions', 'archetype',
184 * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
185 */
186 public function force_duplicate($roleid, array $options) {
187 global $DB;
188
189 if ($roleid == 0) {
190 // This means reset to nothing == remove everything.
191
192 if ($options['shortname']) {
193 $this->role->shortname = '';
194 }
195
196 if ($options['name']) {
197 $this->role->name = '';
198 }
199
200 if ($options['description']) {
201 $this->role->description = '';
202 }
203
204 if ($options['archetype']) {
205 $this->role->archetype = '';
206 }
207
208 if ($options['contextlevels']) {
209 $this->contextlevels = array();
210 }
211
212 if ($options['allowassign']) {
213 $this->allowassign = array();
214 }
215 if ($options['allowoverride']) {
216 $this->allowoverride = array();
217 }
218 if ($options['allowswitch']) {
219 $this->allowswitch = array();
220 }
221
222 if ($options['permissions']) {
223 foreach ($this->capabilities as $capid => $cap) {
224 $this->permissions[$cap->name] = CAP_INHERIT;
225 }
226 }
227
228 return;
229 }
230
231 $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
232
233 if ($options['shortname']) {
234 $this->role->shortname = $role->shortname;
235 }
236
237 if ($options['name']) {
238 $this->role->name = $role->name;
239 }
240
241 if ($options['description']) {
242 $this->role->description = $role->description;
243 }
244
245 if ($options['archetype']) {
246 $this->role->archetype = $role->archetype;
247 }
248
249 if ($options['contextlevels']) {
250 $this->contextlevels = array();
251 $levels = get_role_contextlevels($roleid);
252 foreach ($levels as $cl) {
253 $this->contextlevels[$cl] = $cl;
254 }
255 }
256
257 if ($options['allowassign']) {
258 $this->allowassign = array_keys($this->get_allow_roles_list('assign', $roleid));
259 }
260 if ($options['allowoverride']) {
261 $this->allowoverride = array_keys($this->get_allow_roles_list('override', $roleid));
262 }
263 if ($options['allowswitch']) {
264 $this->allowswitch = array_keys($this->get_allow_roles_list('switch', $roleid));
265 }
266
267 if ($options['permissions']) {
268 $this->permissions = $DB->get_records_menu('role_capabilities',
269 array('roleid' => $roleid, 'contextid' => context_system::instance()->id),
270 '', 'capability,permission');
271
272 foreach ($this->capabilities as $capid => $cap) {
273 if (!isset($this->permissions[$cap->name])) {
274 $this->permissions[$cap->name] = CAP_INHERIT;
275 }
276 }
277 }
278 }
279
280 /**
281 * Change the role definition to match given archetype.
282 *
283 * @param string $archetype
284 * @param array $options array with following keys:
285 * 'name', 'shortname', 'description', 'permissions', 'archetype',
286 * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
287 */
288 public function force_archetype($archetype, array $options) {
289 $archetypes = get_role_archetypes();
290 if (!isset($archetypes[$archetype])) {
291 throw new coding_exception('Unknown archetype: '.$archetype);
292 }
293
294 if ($options['shortname']) {
295 $this->role->shortname = '';
296 }
297
298 if ($options['name']) {
299 $this->role->name = '';
300 }
301
302 if ($options['description']) {
303 $this->role->description = '';
304 }
305
306 if ($options['archetype']) {
307 $this->role->archetype = $archetype;
308 }
309
310 if ($options['contextlevels']) {
311 $this->contextlevels = array();
312 $defaults = get_default_contextlevels($archetype);
313 foreach ($defaults as $cl) {
314 $this->contextlevels[$cl] = $cl;
315 }
316 }
317
318 if ($options['allowassign']) {
319 $this->allowassign = get_default_role_archetype_allows('assign', $archetype);
320 }
321 if ($options['allowoverride']) {
322 $this->allowoverride = get_default_role_archetype_allows('override', $archetype);
323 }
324 if ($options['allowswitch']) {
325 $this->allowswitch = get_default_role_archetype_allows('switch', $archetype);
326 }
327
328 if ($options['permissions']) {
329 $defaultpermissions = get_default_capabilities($archetype);
330 foreach ($this->permissions as $k => $v) {
331 if (isset($defaultpermissions[$k])) {
332 $this->permissions[$k] = $defaultpermissions[$k];
333 continue;
334 }
335 $this->permissions[$k] = CAP_INHERIT;
336 }
337 }
338 }
339
340 /**
341 * Change the role definition to match given preset.
342 *
343 * @param string $xml
344 * @param array $options array with following keys:
345 * 'name', 'shortname', 'description', 'permissions', 'archetype',
346 * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
347 */
348 public function force_preset($xml, array $options) {
349 if (!$info = core_role_preset::parse_preset($xml)) {
350 throw new coding_exception('Invalid role preset');
351 }
352
353 if ($options['shortname']) {
354 if (isset($info['shortname'])) {
355 $this->role->shortname = $info['shortname'];
356 }
357 }
358
359 if ($options['name']) {
360 if (isset($info['name'])) {
361 $this->role->name = $info['name'];
362 }
363 }
364
365 if ($options['description']) {
366 if (isset($info['description'])) {
367 $this->role->description = $info['description'];
368 }
369 }
370
371 if ($options['archetype']) {
372 if (isset($info['archetype'])) {
373 $this->role->archetype = $info['archetype'];
374 }
375 }
376
377 if ($options['contextlevels']) {
378 if (isset($info['contextlevels'])) {
379 $this->contextlevels = $info['contextlevels'];
380 }
381 }
382
383 foreach (array('assign', 'override', 'switch') as $type) {
384 if ($options['allow'.$type]) {
385 if (isset($info['allow'.$type])) {
386 $this->{'allow'.$type} = $info['allow'.$type];
387 }
388 }
389 }
390
391 if ($options['permissions']) {
392 foreach ($this->permissions as $k => $v) {
393 // Note: do not set everything else to CAP_INHERIT here
394 // because the xml file might not contain all capabilities.
395 if (isset($info['permissions'][$k])) {
396 $this->permissions[$k] = $info['permissions'][$k];
397 }
398 }
399 }
400 }
401
402 public function get_role_name() {
403 return $this->role->name;
404 }
405
406 public function get_role_id() {
407 return $this->role->id;
408 }
409
410 public function get_archetype() {
411 return $this->role->archetype;
412 }
413
414 protected function load_parent_permissions() {
415 $this->parentpermissions = get_default_capabilities($this->role->archetype);
416 }
417
418 public function save_changes() {
419 global $DB;
420
421 if (!$this->roleid) {
422 // Creating role
423 $this->role->id = create_role($this->role->name, $this->role->shortname, $this->role->description, $this->role->archetype);
424 $this->roleid = $this->role->id; // Needed to make the parent::save_changes(); call work.
425 } else {
426 // Updating role
427 $DB->update_record('role', $this->role);
428 }
429
430 // Assignable contexts.
431 set_role_contextlevels($this->role->id, $this->contextlevels);
432
433 // Set allowed roles.
434 $this->save_allow('assign');
435 $this->save_allow('override');
436 $this->save_allow('switch');
437
438 // Permissions.
439 parent::save_changes();
440 }
441
442 protected function save_allow($type) {
443 global $DB;
444
445 $current = array_keys($this->get_allow_roles_list($type));
446 $wanted = $this->{'allow'.$type};
447
448 $addfunction = 'allow_'.$type;
449 $deltable = 'role_allow_'.$type;
450 $field = 'allow'.$type;
451
452 foreach ($current as $roleid) {
453 if (!in_array($roleid, $wanted)) {
454 $DB->delete_records($deltable, array('roleid'=>$this->roleid, $field=>$roleid));
455 continue;
456 }
457 $key = array_search($roleid, $wanted);
458 unset($wanted[$key]);
459 }
460
461 foreach ($wanted as $roleid) {
462 if ($roleid == -1) {
463 $roleid = $this->roleid;
464 }
465 $addfunction($this->roleid, $roleid);
466 }
467 }
468
469 protected function get_name_field($id) {
470 return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->name) . '" />';
471 }
472
473 protected function get_shortname_field($id) {
474 return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->shortname) . '" />';
475 }
476
477 protected function get_description_field($id) {
478 return print_textarea(true, 10, 50, 50, 10, 'description', $this->role->description, 0, true);
479 }
480
481 protected function get_archetype_field($id) {
482 $options = array();
483 $options[''] = get_string('none');
484 foreach(get_role_archetypes() as $type) {
485 $options[$type] = get_string('archetype'.$type, 'role');
486 }
487 return html_writer::select($options, 'archetype', $this->role->archetype, false);
488 }
489
490 protected function get_assignable_levels_control() {
491 $output = '';
492 foreach ($this->allcontextlevels as $cl => $clname) {
493 $extraarguments = $this->disabled;
494 if (in_array($cl, $this->contextlevels)) {
495 $extraarguments .= 'checked="checked" ';
496 }
497 if (!$this->disabled) {
498 $output .= '<input type="hidden" name="contextlevel' . $cl . '" value="0" />';
499 }
500 $output .= '<input type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
501 '" value="1" ' . $extraarguments . '/> ';
502 $output .= '<label for="cl' . $cl . '">' . $clname . "</label><br />\n";
503 }
504 return $output;
505 }
506
507 /**
508 * Returns an array of roles of the allowed type.
509 *
510 * @param string $type Must be one of: assign, switch, or override.
511 * @param int $roleid (null means current role)
512 * @return array
513 */
514 protected function get_allow_roles_list($type, $roleid = null) {
515 global $DB;
516
517 if ($type !== 'assign' and $type !== 'switch' and $type !== 'override') {
518 debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
519 return array();
520 }
521
522 if ($roleid === null) {
523 $roleid = $this->roleid;
524 }
525
526 if (empty($roleid)) {
527 return array();
528 }
529
530 $sql = "SELECT r.*
531 FROM {role} r
532 JOIN {role_allow_{$type}} a ON a.allow{$type} = r.id
533 WHERE a.roleid = :roleid
534 ORDER BY r.sortorder ASC";
535 return $DB->get_records_sql($sql, array('roleid'=>$roleid));
536 }
537
538 /**
539 * Returns an array of roles with the allowed type.
540 *
541 * @param string $type Must be one of: assign, switch, or override.
542 * @return array Am array of role names with the allowed type
543 */
544 protected function get_allow_role_control($type) {
545 if ($type !== 'assign' and $type !== 'switch' and $type !== 'override') {
546 debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
547 return '';
548 }
549
550 $property = 'allow'.$type;
551 $selected = $this->$property;
552
553 $options = array();
554 foreach (role_get_names(null, ROLENAME_ALIAS) as $role) {
555 $options[$role->id] = $role->localname;
556 }
557 if ($this->roleid == 0) {
558 $options[-1] = get_string('thisnewrole', 'core_role');
559 }
560 return html_writer::select($options, 'allow'.$type.'[]', $selected, false, array('multiple'=>'multiple', 'size'=>10));
561 }
562
563 /**
564 * Returns information about the risks associated with a role.
565 *
566 * @return string
567 */
568 protected function get_role_risks_info() {
569 return '';
570 }
571
572 protected function print_field($name, $caption, $field) {
573 global $OUTPUT;
574 // Attempt to generate HTML like formslib.
575 echo '<div class="fitem">';
576 echo '<div class="fitemtitle">';
577 if ($name) {
578 echo '<label for="' . $name . '">';
579 }
580 echo $caption;
581 if ($name) {
582 echo "</label>\n";
583 }
584 echo '</div>';
585 if (isset($this->errors[$name])) {
586 $extraclass = ' error';
587 } else {
588 $extraclass = '';
589 }
590 echo '<div class="felement' . $extraclass . '">';
591 echo $field;
592 if (isset($this->errors[$name])) {
593 echo $OUTPUT->error_text($this->errors[$name]);
594 }
595 echo '</div>';
596 echo '</div>';
597 }
598
599 protected function print_show_hide_advanced_button() {
600 echo '<p class="definenotice">' . get_string('highlightedcellsshowdefault', 'role') . ' </p>';
601 echo '<div class="advancedbutton">';
602 echo '<input type="submit" name="toggleadvanced" value="' . get_string('hideadvanced', 'form') . '" />';
603 echo '</div>';
604 }
605
606 public function display() {
607 global $OUTPUT;
608 // Extra fields at the top of the page.
609 echo '<div class="topfields clearfix">';
610 $this->print_field('shortname', get_string('roleshortname', 'role').'&nbsp;'.$OUTPUT->help_icon('roleshortname', 'role'), $this->get_shortname_field('shortname'));
611 $this->print_field('name', get_string('customrolename', 'role').'&nbsp;'.$OUTPUT->help_icon('customrolename', 'role'), $this->get_name_field('name'));
612 $this->print_field('edit-description', get_string('customroledescription', 'role').'&nbsp;'.$OUTPUT->help_icon('customroledescription', 'role'), $this->get_description_field('description'));
613 $this->print_field('menuarchetype', get_string('archetype', 'role').'&nbsp;'.$OUTPUT->help_icon('archetype', 'role'), $this->get_archetype_field('archetype'));
614 $this->print_field('', get_string('maybeassignedin', 'role'), $this->get_assignable_levels_control());
615 $this->print_field('menuallowassign', get_string('allowassign', 'role'), $this->get_allow_role_control('assign'));
616 $this->print_field('menuallowoverride', get_string('allowoverride', 'role'), $this->get_allow_role_control('override'));
617 $this->print_field('menuallowswitch', get_string('allowswitch', 'role'), $this->get_allow_role_control('switch'));
618 if ($risks = $this->get_role_risks_info()) {
619 $this->print_field('', get_string('rolerisks', 'role'), $risks);
620 }
621 echo "</div>";
622
623 $this->print_show_hide_advanced_button();
624
625 // Now the permissions table.
626 parent::display();
627 }
628
629 protected function add_permission_cells($capability) {
630 /// One cell for each possible permission.
631 foreach ($this->displaypermissions as $perm => $permname) {
632 $strperm = $this->strperms[$permname];
633 $extraclass = '';
634 if ($perm == $this->parentpermissions[$capability->name]) {
635 $extraclass = ' capdefault';
636 }
637 $checked = '';
638 if ($this->permissions[$capability->name] == $perm) {
639 $checked = 'checked="checked" ';
640 }
641 echo '<td class="' . $permname . $extraclass . '">';
642 echo '<label><input type="radio" name="' . $capability->name .
643 '" value="' . $perm . '" ' . $checked . '/> ';
644 echo '<span class="note">' . $strperm . '</span>';
645 echo '</label></td>';
646 }
647 }
648}