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