60acdf996bfa7abceb032b4642be9d929454d00b
[moodle.git] / admin / roles / lib.php
1 <?php
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/>.
18 /**
19  * Library code used by the roles administration interfaces.
20  *
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  *
27  * @package    core
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  */
33 require_once($CFG->libdir.'/adminlib.php');
34 require_once($CFG->dirroot.'/user/selector/lib.php');
36 // Classes for producing tables with one row per capability ====================
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
42  * admin/roles/manage.php, override.php and check.php.
43  *
44  * An ajaxy search UI shown at the top, if JavaScript is on.
45  */
46 abstract class capability_table_base {
47     /** The context this table relates to. */
48     protected $context;
50     /** The capabilities to display. Initialised as fetch_context_capabilities($context). */
51     protected $capabilities = array();
53     /** Added as an id="" attribute to the table on output. */
54     protected $id;
56     /** Added to the class="" attribute on output. */
57     protected $classes = array('rolecap');
59     /** Default number of capabilities in the table for the search UI to be shown. */
60     const NUM_CAPS_FOR_SEARCH = 12;
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     }
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     }
82     /**
83      * Display the table.
84      */
85     public function display() {
86         if (count($this->capabilities) > capability_table_base::NUM_CAPS_FOR_SEARCH) {
87             global $PAGE;
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));
90         }
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";
96     /// Loop over capabilities.
97         $contextlevel = 0;
98         $component = '';
99         foreach ($this->capabilities as $capability) {
100             if ($this->skip_row($capability)) {
101                 continue;
102             }
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;
111         /// Start the row.
112             echo '<tr class="' . implode(' ', array_unique(array_merge(array('rolecap'),
113                     $this->get_row_classes($capability)))) . '">';
115         /// Table cell for the capability name.
116             echo '<th scope="row" class="name"><span class="cap-desc">' . get_capability_docs_link($capability) .
117                     '<span class="cap-name">' . $capability->name . '</span></span></th>';
119         /// Add the cells specific to this table.
120             $this->add_row_cells($capability);
122         /// End the row.
123             echo "</tr>\n";
124         }
126     /// End of the table.
127         echo "</tbody>\n</table>\n";
128     }
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>';
139     }
141     /** For subclasses to override, output header cells, after the initial capability one. */
142     protected abstract function add_header_cells();
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();
147     /**
148      * For subclasses to override. Allows certain capabilties
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     }
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     }
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);
180 /**
181  * Subclass of capability_table_base for use on the Check permissions page.
182  *
183  * We have one additional column, Allowed, which contains yes/no.
184  */
185 class check_capability_table extends capability_table_base {
186     protected $user;
187     protected $fullname;
188     protected $contextname;
189     protected $stryes;
190     protected $strno;
191     private $hascap;
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;
205         $this->stryes = get_string('yes');
206         $this->strno = get_string('no');
207     }
209     protected function add_header_cells() {
210         echo '<th>' . get_string('allowed', 'role') . '</th>';
211     }
213     protected function num_extra_columns() {
214         return 1;
215     }
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     }
226     protected function add_row_cells($capability) {
227         global $OUTPUT;
228         if ($this->hascap) {
229             $result = $this->stryes;
230         } else {
231             $result = $this->strno;
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>';
238     }
242 /**
243  * Subclass of capability_table_base for use on the Permissions page.
244  */
245 class 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();
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) {
259         parent::__construct($context, 'permissions');
260         $this->contextname = $contextname;
261         $this->allowoverrides = $allowoverrides;
262         $this->allowsafeoverrides = $allowsafeoverrides;
263         $this->overridableroles = $overridableroles;
265         $roles = get_all_roles($context);
266         $this->roles = role_fix_names(array_reverse($roles, true), $context, ROLENAME_ALIAS, true);
268     }
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     }
276     protected function num_extra_columns() {
277         return 3;
278     }
280     protected function add_row_cells($capability) {
281         global $OUTPUT, $PAGE;
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;
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         }
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);
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         }
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         }
335         $risks = $this->get_risks($capability);
337         echo '<td>' . $risks . '</td>';
338         echo '<td>' . $neededroles . '</td>';
339         echo '<td>' . $forbiddenroles . '</td>';
340     }
342     protected function get_risks($capability) {
343         global $OUTPUT;
345         $allrisks = get_all_risks();
346         $risksurl = new moodle_url(get_docs_url(s(get_string('risks', 'role'))));
348         $return = '';
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         }
360         return $return;
361     }
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  */
371 abstract class capability_table_with_risks extends capability_table_base {
372     protected $allrisks;
373     protected $allpermissions; // We don't need perms ourselves, but all our subclasses do.
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.
377     /** The capabilities to highlight as default/inherited. */
378     protected $parentpermissions;
379     protected $displaypermissions;
380     protected $permissions;
381     protected $changed;
382     protected $roleid;
384     public function __construct($context, $id, $roleid) {
385         parent::__construct($context, $id);
387         $this->allrisks = get_all_risks();
388         $this->risksurl = get_docs_url(s(get_string('risks', 'role')));
390         $this->allpermissions = array(
391             CAP_INHERIT => 'inherit',
392             CAP_ALLOW => 'allow',
393             CAP_PREVENT => 'prevent' ,
394             CAP_PROHIBIT => 'prohibit',
395         );
397         $this->strperms = array();
398         foreach ($this->allpermissions as $permname) {
399             $this->strperms[$permname] =  get_string($permname, 'role');
400         }
402         $this->roleid = $roleid;
403         $this->load_current_permissions();
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     }
414     protected function load_current_permissions() {
415         global $DB;
417     /// Load the overrides/definition in this context.
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         }
424     }
426     protected abstract function load_parent_permissions();
428     /**
429      * Update $this->permissions based on submitted data, while making a list of
430      * changed capabilities in $this->changed.
431      */
432     public function read_submitted_permissions() {
433         $this->changed = array();
435         foreach ($this->capabilities as $cap) {
436             if ($cap->locked || $this->skip_row($cap)) {
437             /// The user is not allowed to change the permission for this capability
438                 continue;
439             }
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             }
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     }
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         }
466     /// Force accessinfo refresh for users visiting this context.
467         mark_context_dirty($this->context->path);
468     }
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();
478     }
480     protected function add_header_cells() {
481         global $OUTPUT;
482         echo '<th colspan="' . count($this->displaypermissions) . '" scope="col">' .
483                 get_string('permission', 'role') . ' ' . $OUTPUT->help_icon('permission', 'role') . '</th>';
484         echo '<th class="risk" colspan="' . count($this->allrisks) . '" scope="col">' . get_string('risks','role') . '</th>';
485     }
487     protected function num_extra_columns() {
488         return count($this->displaypermissions) + count($this->allrisks);
489     }
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     }
501     protected abstract function add_permission_cells($capability);
503     protected function add_row_cells($capability) {
504         $this->add_permission_cells($capability);
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     }
515     /**
516      * Print a risk icon, as a link to the Risks page on Moodle Docs.
517      *
518      * @param string $type the type of risk, will be one of the keys from the
519      *      get_all_risks array. Must start with 'risk'.
520      */
521     function get_risk_icon($type) {
522         global $OUTPUT;
523         if (!isset($this->riskicons[$type])) {
524             $iconurl = $OUTPUT->pix_url('i/' . str_replace('risk', 'risk_', $type));
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')));
528         }
529         return $this->riskicons[$type];
530     }
533 /**
534  * As well as tracking the permissions information about the role we are creating
535  * or editing, we also track the other information about the role. (This class is
536  * starting to be more and more like a formslib form in some respects.)
537  */
538 class define_role_table_advanced extends capability_table_with_risks {
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;
545     protected $disabled = '';
547     public function __construct($context, $roleid) {
548         $this->roleid = $roleid;
549         parent::__construct($context, 'defineroletable', $roleid);
550         $this->displaypermissions = $this->allpermissions;
551         $this->strperms[$this->allpermissions[CAP_INHERIT]] = get_string('notset', 'role');
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         );
561     }
563     protected function load_current_permissions() {
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             }
569             $contextlevels = get_role_contextlevels($this->roleid);
570             // Put the contextlevels in the array keys, as well as the values.
571             if (!empty($contextlevels)) {
572                 $this->contextlevels = array_combine($contextlevels, $contextlevels);
573             } else {
574                 $this->contextlevels = array();
575             }
576         } else {
577             $this->role = new stdClass;
578             $this->role->name = '';
579             $this->role->shortname = '';
580             $this->role->description = '';
581             $this->role->archetype = '';
582             $this->contextlevels = array();
583         }
584         parent::load_current_permissions();
585     }
587     public function read_submitted_permissions() {
588         global $DB;
589         $this->errors = array();
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;
596             $this->role->shortname = textlib::specialtoascii($this->role->shortname);
597             $this->role->shortname = textlib::strtolower(clean_param($this->role->shortname, PARAM_ALPHANUMEXT));
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         }
606         // Role name.
607         $name = optional_param('name', null, PARAM_TEXT);
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         }
620         // Description.
621         $description = optional_param('description', null, PARAM_RAW);
622         if (!is_null($description)) {
623             $this->role->description = $description;
624         }
626         // Legacy type.
627         $archetype = optional_param('archetype', null, PARAM_RAW);
628         if (isset($archetype)) {
629             $archetypes = get_role_archetypes();
630             if (isset($archetypes[$archetype])){
631                 $this->role->archetype = $archetype;
632             } else {
633                 $this->role->archetype = '';
634             }
635         }
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             }
647         }
649         // Now read the permissions for each capability.
650         parent::read_submitted_permissions();
651     }
653     public function is_submission_valid() {
654         return empty($this->errors);
655     }
657     /**
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.
660      */
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     }
668     public function get_role_name() {
669         return $this->role->name;
670     }
672     public function get_role_id() {
673         return $this->role->id;
674     }
676     public function get_archetype() {
677         return $this->role->archetype;
678     }
680     protected function load_parent_permissions() {
681         $this->parentpermissions = get_default_capabilities($this->role->archetype);
682     }
684     public function save_changes() {
685         global $DB;
687         if (!$this->roleid) {
688             // Creating role
689             $this->role->id = create_role($this->role->name, $this->role->shortname, $this->role->description, $this->role->archetype);
690             $this->roleid = $this->role->id; // Needed to make the parent::save_changes(); call work.
691         } else {
692             // Updating role
693             $DB->update_record('role', $this->role);
694         }
696         // Assignable contexts.
697         set_role_contextlevels($this->role->id, $this->contextlevels);
699         // Permissions.
700         parent::save_changes();
701     }
703     protected function get_name_field($id) {
704         return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->name) . '" />';
705     }
707     protected function get_shortname_field($id) {
708         return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->shortname) . '" />';
709     }
711     protected function get_description_field($id) {
712         return print_textarea(true, 10, 50, 50, 10, 'description', $this->role->description, 0, true);
713     }
715     protected function get_archetype_field($id) {
716         $options = array();
717         $options[''] = get_string('none');
718         foreach(get_role_archetypes() as $type) {
719             $options[$type] = get_string('archetype'.$type, 'role');
720         }
721         return html_writer::select($options, 'archetype', $this->role->archetype, false);
722     }
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) {
732                 $output .= '<input type="hidden" name="contextlevel' . $cl . '" value="0" />';
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     }
741     protected function print_field($name, $caption, $field) {
742         global $OUTPUT;
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])) {
761             echo $OUTPUT->error_text($this->errors[$name]);
762         }
763         echo $field;
764         echo '</div>';
765         echo '</div>';
766     }
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     }
775     public function display() {
776         global $OUTPUT;
777         // Extra fields at the top of the page.
778         echo '<div class="topfields clearfix">';
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'));
782         $this->print_field('menuarchetype', get_string('archetype', 'role').'&nbsp;'.$OUTPUT->help_icon('archetype', 'role'), $this->get_archetype_field('archetype'));
783         $this->print_field('', get_string('maybeassignedin', 'role'), $this->get_assignable_levels_control());
784         echo "</div>";
786         $this->print_show_hide_advanced_button();
788         // Now the permissions table.
789         parent::display();
790     }
792     protected function add_permission_cells($capability) {
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) {
802                 $checked = 'checked="checked" ';
803             }
804             echo '<td class="' . $permname . $extraclass . '">';
805             echo '<label><input type="radio" name="' . $capability->name .
806                     '" value="' . $perm . '" ' . $checked . '/> ';
807             echo '<span class="note">' . $strperm . '</span>';
808             echo '</label></td>';
809         }
810     }
813 class define_role_table_basic extends define_role_table_advanced {
814     protected $stradvmessage;
815     protected $strallow;
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     }
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     }
830     protected function add_permission_cells($capability) {
831         $perm = $this->permissions[$capability->name];
832         $permname = $this->allpermissions[$perm];
833         $defaultperm = $this->allpermissions[$this->parentpermissions[$capability->name]];
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 .
842                     '" value="' . CAP_ALLOW . '" ' . $checked . '/> ' . $this->strallow . '</label>';
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     }
850 class 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]);
854         $this->disabled = 'disabled="disabled" ';
855     }
857     public function save_changes() {
858         throw new moodle_exception('invalidaccess');
859     }
861     protected function get_name_field($id) {
862         return role_get_name($this->role);
863     }
865     protected function get_shortname_field($id) {
866         return $this->role->shortname;
867     }
869     protected function get_description_field($id) {
870         return role_get_description($this->role);
871     }
873     protected function get_archetype_field($id) {
874         if (empty($this->role->archetype)) {
875             return get_string('none');
876         } else {
877             return get_string('archetype'.$this->role->archetype, 'role');
878         }
879     }
881     protected function print_show_hide_advanced_button() {
882         // Do nothing.
883     }
885     protected function add_permission_cells($capability) {
886         $perm = $this->permissions[$capability->name];
887         $permname = $this->allpermissions[$perm];
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>';
897     }
900 class override_permissions_table_advanced extends capability_table_with_risks {
901     protected $strnotset;
902     protected $haslockedcapabilities = false;
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) {
917         parent::__construct($context, 'overriderolestable', $roleid);
918         $this->displaypermissions = $this->allpermissions;
919         $this->strnotset = get_string('notset', 'role');
921     /// Determine which capabilities should be locked.
922         if ($safeoverridesonly) {
923             foreach ($this->capabilities as $capid => $cap) {
924                 if (!is_safe_capability($cap)) {
925                     $this->capabilities[$capid]->locked = true;
926                     $this->haslockedcapabilities = true;
927                 }
928             }
929         }
930     }
932     protected function load_parent_permissions() {
933         global $DB;
935     /// Get the capabilities from the parent context, so that can be shown in the interface.
936         $parentcontext = context::instance_by_id(get_parent_contextid($this->context));
937         $this->parentpermissions = role_context_capabilities($this->roleid, $parentcontext);
938     }
940     public function has_locked_capabilities() {
941         return $this->haslockedcapabilities;
942     }
944     protected function add_permission_cells($capability) {
945         $disabled = '';
946         if ($capability->locked || $this->parentpermissions[$capability->name] == CAP_PROHIBIT) {
947             $disabled = ' disabled="disabled"';
948         }
950     /// One cell for each possible permission.
951         foreach ($this->displaypermissions as $perm => $permname) {
952             $strperm = $this->strperms[$permname];
953             $extraclass = '';
954             if ($perm != CAP_INHERIT && $perm == $this->parentpermissions[$capability->name]) {
955                 $extraclass = ' capcurrent';
956             }
957             $checked = '';
958             if ($this->permissions[$capability->name] == $perm) {
959                 $checked = 'checked="checked" ';
960             }
961             echo '<td class="' . $permname . $extraclass . '">';
962             echo '<label><input type="radio" name="' . $capability->name .
963                     '" value="' . $perm . '" ' . $checked . $disabled . '/> ';
964             if ($perm == CAP_INHERIT) {
965                 $inherited = $this->parentpermissions[$capability->name];
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     }
979 // User selectors for managing role assignments ================================
981 /**
982  * Base class to avoid duplicating code.
983  */
984 abstract class role_assign_user_selector_base extends user_selector_base {
985     const MAX_USERS_PER_PAGE = 100;
987     protected $roleid;
988     protected $context;
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;
996         if (isset($options['context'])) {
997             $this->context = $options['context'];
998         } else {
999             $this->context = context::instance_by_id($options['contextid']);
1000         }
1001         $options['accesscontext'] = $this->context;
1002         parent::__construct($name, $options);
1003         $this->roleid = $options['roleid'];
1004         require_once($CFG->dirroot . '/group/lib.php');
1005     }
1007     protected function get_options() {
1008         global $CFG;
1009         $options = parent::get_options();
1010         $options['file'] = $CFG->admin . '/roles/lib.php';
1011         $options['roleid'] = $this->roleid;
1012         $options['contextid'] = $this->context->id;
1013         return $options;
1014     }
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
1020  * some CONTEXT_BLOCK).
1021  *
1022  * This returns only enrolled users in this context.
1023  */
1024 class potential_assignees_below_course extends role_assign_user_selector_base {
1025     public function find_users($search) {
1026         global $DB;
1028         list($enrolsql, $eparams) = get_enrolled_sql($this->context);
1030         // Now we have to go to the database.
1031         list($wherecondition, $params) = $this->search_sql($search, 'u');
1032         $params = array_merge($params, $eparams);
1034         if ($wherecondition) {
1035             $wherecondition = ' AND ' . $wherecondition;
1036         }
1038         $fields      = 'SELECT ' . $this->required_fields_sql('u');
1039         $countfields = 'SELECT COUNT(u.id)';
1041         $sql   = " FROM {user} u
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";
1046         $params['contextid'] = $this->context->id;
1047         $params['roleid'] = $this->roleid;
1049         list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
1050         $order = ' ORDER BY ' . $sort;
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         }
1060         // If not, show them.
1061         $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
1063         if (empty($availableusers)) {
1064             return array();
1065         }
1067         if ($search) {
1068             $groupname = get_string('potusersmatching', 'role', $search);
1069         } else {
1070             $groupname = get_string('potusers', 'role');
1071         }
1073         return array($groupname => $availableusers);
1074     }
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  */
1082 class role_check_users_selector extends user_selector_base {
1083     const MAX_ENROLLED_PER_PAGE = 100;
1084     const MAX_POTENTIAL_PER_PAGE = 100;
1086     /** @var bool limit listing of users to enrolled only */
1087     var $onlyenrolled;
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);
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     }
1111     public function find_users($search) {
1112         global $DB;
1114         list($wherecondition, $params) = $this->search_sql($search, 'u');
1116         $fields      = 'SELECT ' . $this->required_fields_sql('u');
1117         $countfields = 'SELECT COUNT(1)';
1119         $coursecontext = $this->accesscontext->get_course_context(false);
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;
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             }
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         }
1149         $params['contextid'] = $this->accesscontext->id;
1151         list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
1152         $order = ' ORDER BY ' . $sort;
1154         $result = array();
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         }
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();
1171             } else {
1172                 $enrolledusers = $DB->get_records_sql($fields . $sql1 . $order, array_merge($params, $sortparams));
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 {
1188                 $otherusers = $DB->get_records_sql($fields . $sql2 . $order, array_merge($params, $sortparams));
1189                 if ($otherusers) {
1190                     $result[$groupname2] = $otherusers;
1191                 }
1192             }
1193         }
1195         return $result;
1196     }
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     }
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  */
1211 class potential_assignees_course_and_above extends role_assign_user_selector_base {
1212     public function find_users($search) {
1213         global $DB;
1215         list($wherecondition, $params) = $this->search_sql($search, '');
1217         $fields      = 'SELECT ' . $this->required_fields_sql('');
1218         $countfields = 'SELECT COUNT(1)';
1220         $sql = " FROM {user}
1221                 WHERE $wherecondition
1222                       AND id NOT IN (
1223                          SELECT r.userid
1224                            FROM {role_assignments} r
1225                           WHERE r.contextid = :contextid
1226                                 AND r.roleid = :roleid)";
1228         list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
1229         $order = ' ORDER BY ' . $sort;
1231         $params['contextid'] = $this->context->id;
1232         $params['roleid'] = $this->roleid;
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         }
1241         $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
1243         if (empty($availableusers)) {
1244             return array();
1245         }
1247         if ($search) {
1248             $groupname = get_string('potusersmatching', 'role', $search);
1249         } else {
1250             $groupname = get_string('potusers', 'role');
1251         }
1253         return array($groupname => $availableusers);
1254     }
1257 /**
1258  * User selector subclass for the list of users who already have the role in
1259  * question on the assign roles page.
1260  */
1261 class existing_role_holders extends role_assign_user_selector_base {
1263     public function __construct($name, $options) {
1264         parent::__construct($name, $options);
1265     }
1267     public function find_users($search) {
1268         global $DB;
1270         list($wherecondition, $params) = $this->search_sql($search, 'u');
1271         list($ctxcondition, $ctxparams) = $DB->get_in_or_equal(get_parent_contexts($this->context, true), SQL_PARAMS_NAMED, 'ctx');
1272         $params = array_merge($params, $ctxparams);
1273         $params['roleid'] = $this->roleid;
1275         list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
1276         $params = array_merge($params, $sortparams);
1278         $sql = "SELECT ra.id as raid," . $this->required_fields_sql('u') . ",ra.contextid,ra.component
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
1285                     ra.roleid = :roleid
1286                 ORDER BY ctx.depth DESC, ra.component, $sort";
1287         $contextusers = $DB->get_records_sql($sql, $params);
1289         // No users at all.
1290         if (empty($contextusers)) {
1291             return array();
1292         }
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;
1300         $dummyuser->component = '';
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             }
1328             if ($user->component !== '') {
1329                 // bad luck, you can tweak only manual role assignments
1330                 $user->disabled = true;
1331             }
1332             unset($user->component);
1333             $currentgroup[$user->id] = $user;
1334         }
1336         return $results;
1337     }
1339     protected function this_con_group_name($search, $numusers) {
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         }
1348         $contexttype = get_contextlevel_name($this->context->contextlevel);
1349         if ($search) {
1350             $a = new stdClass;
1351             $a->search = $search;
1352             $a->contexttype = $contexttype;
1353             if ($numusers) {
1354                 return get_string('usersinthisxmatching', 'role', $a);
1355             } else {
1356                 return get_string('noneinthisxmatching', 'role', $a);
1357             }
1358         } else {
1359             if ($numusers) {
1360                 return get_string('usersinthisx', 'role', $contexttype);
1361             } else {
1362                 return get_string('noneinthisx', 'role', $contexttype);
1363             }
1364         }
1365     }
1367     protected function parent_con_group_name($search, $contextid) {
1368         $context = context::instance_by_id($contextid);
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         }
1378     }
1381 /**
1382  * Base class for managing the data in the grid of checkboxes on the role allow
1383  * allow/overrides/switch editing pages (allow.php).
1384  */
1385 abstract class role_allow_role_page {
1386     protected $tablename;
1387     protected $targetcolname;
1388     protected $roles;
1389     protected $allowed = null;
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      */
1395     public function __construct($tablename, $targetcolname) {
1396         $this->tablename = $tablename;
1397         $this->targetcolname = $targetcolname;
1398         $this->load_required_roles();
1399     }
1401     /**
1402      * Load information about all the roles we will need information about.
1403      */
1404     protected function load_required_roles() {
1405     /// Get all roles
1406         $this->roles = role_fix_names(get_all_roles(), context_system::instance(), ROLENAME_ORIGINAL);
1407     }
1409     /**
1410      * Update the data with the new settings submitted by the user.
1411      */
1412     public function process_submission() {
1413         global $DB;
1414     /// Delete all records, then add back the ones that should be allowed.
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     }
1425     /**
1426      * Set one allow in the database.
1427      * @param integer $fromroleid
1428      * @param integer $targetroleid
1429      */
1430     protected abstract function set_allow($fromroleid, $targetroleid);
1432     /**
1433      * Load the current allows from the database.
1434      */
1435     public function load_current_settings() {
1436         global $DB;
1437     /// Load the current settings
1438         $this->allowed = array();
1439         foreach ($this->roles as $role) {
1440             // Make an array $role->id => false. This is probably too clever for its own good.
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         }
1447         $rs->close();
1448     }
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     }
1459     /**
1460      * @return object a $table structure that can be passed to print_table, containing
1461      * one cell for each checkbox.
1462      */
1463     public function get_table() {
1464         $table = new html_table();
1465         $table->tablealign = 'center';
1466         $table->cellpadding = 5;
1467         $table->cellspacing = 0;
1468         $table->width = '90%';
1469         $table->align = array('left');
1470         $table->rotateheaders = true;
1471         $table->head = array('&#xa0;');
1472         $table->colclasses = array('');
1474     /// Add role name headers.
1475         foreach ($this->roles as $targetrole) {
1476             $table->head[] = $targetrole->localname;
1477             $table->align[] = 'left';
1478             if ($this->is_allowed_target($targetrole->id)) {
1479                 $table->colclasses[] = '';
1480             } else {
1481                 $table->colclasses[] = 'dimmed_text';
1482             }
1483         }
1485     /// Now the rest of the table.
1486         foreach ($this->roles as $fromrole) {
1487             $row = array($fromrole->localname);
1488             foreach ($this->roles as $targetrole) {
1489                 $checked = '';
1490                 $disabled = '';
1491                 if ($this->allowed[$fromrole->id][$targetrole->id]) {
1492                     $checked = 'checked="checked" ';
1493                 }
1494                 if (!$this->is_allowed_target($targetrole->id)) {
1495                     $disabled = 'disabled="disabled" ';
1496                 }
1497                 $name = 's_' . $fromrole->id . '_' . $targetrole->id;
1498                 $tooltip = $this->get_cell_tooltip($fromrole, $targetrole);
1499                 $row[] = '<input type="checkbox" name="' . $name . '" id="' . $name .
1500                         '" title="' . $tooltip . '" value="1" ' . $checked . $disabled . '/>' .
1501                         '<label for="' . $name . '" class="accesshide">' . $tooltip . '</label>';
1502             }
1503             $table->data[] = $row;
1504         }
1506         return $table;
1507     }
1509     /**
1510      * Snippet of text displayed above the table, telling the admin what to do.
1511      * @return unknown_type
1512      */
1513     public abstract function get_intro_text();
1516 /**
1517  * Subclass of role_allow_role_page for the Allow assigns tab.
1518  */
1519 class role_allow_assign_page extends role_allow_role_page {
1520     public function __construct() {
1521         parent::__construct('role_allow_assign', 'allowassign');
1522     }
1524     protected function set_allow($fromroleid, $targetroleid) {
1525         allow_assign($fromroleid, $targetroleid);
1526     }
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     }
1535     public function get_intro_text() {
1536         return get_string('configallowassign', 'admin');
1537     }
1540 /**
1541  * Subclass of role_allow_role_page for the Allow overrides tab.
1542  */
1543 class role_allow_override_page extends role_allow_role_page {
1544     public function __construct() {
1545         parent::__construct('role_allow_override', 'allowoverride');
1546     }
1548     protected function set_allow($fromroleid, $targetroleid) {
1549         allow_override($fromroleid, $targetroleid);
1550     }
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     }
1559     public function get_intro_text() {
1560         return get_string('configallowoverride2', 'admin');
1561     }
1564 /**
1565  * Subclass of role_allow_role_page for the Allow switches tab.
1566  */
1567 class role_allow_switch_page extends role_allow_role_page {
1568     protected $allowedtargetroles;
1570     public function __construct() {
1571         parent::__construct('role_allow_switch', 'allowswitch');
1572     }
1574     protected function load_required_roles() {
1575         global $DB;
1576         parent::load_required_roles();
1577         $this->allowedtargetroles = $DB->get_records_menu('role', NULL, 'id');
1578     }
1580     protected function set_allow($fromroleid, $targetroleid) {
1581         allow_switch($fromroleid, $targetroleid);
1582     }
1584     protected function is_allowed_target($targetroleid) {
1585         return isset($this->allowedtargetroles[$targetroleid]);
1586     }
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     }
1595     public function get_intro_text() {
1596         return get_string('configallowswitch', 'admin');
1597     }
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  *
1607  * @param stdClass $context a context.
1608  * @param string $name passed to user selector constructor.
1609  * @param array $options to user selector constructor.
1610  * @return user_selector_base an appropriate user selector.
1611  */
1612 function roles_get_potential_user_selector($context, $name, $options) {
1613         $blockinsidecourse = false;
1614         if ($context->contextlevel == CONTEXT_BLOCK) {
1615             $parentcontext = context::instance_by_id(get_parent_contextid($context));
1616             $blockinsidecourse = in_array($parentcontext->contextlevel, array(CONTEXT_MODULE, CONTEXT_COURSE));
1617         }
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;
1628 class 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      */
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);
1641     }
1643     public function find_users($search) {
1644         global $CFG, $DB;
1645         list($wherecondition, $params) = $this->search_sql($search, '');
1647         $fields      = 'SELECT ' . $this->required_fields_sql('');
1648         $countfields = 'SELECT COUNT(1)';
1650         $sql = " FROM {user}
1651                 WHERE $wherecondition AND mnethostid = :localmnet";
1653         $params['localmnet'] = $CFG->mnet_localhost_id; // it could be dangerous to make remote users admins and also this could lead to other problems
1655         list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
1656         $order = ' ORDER BY ' . $sort;
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         }
1666         $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
1668         if (empty($availableusers)) {
1669             return array();
1670         }
1672         if ($search) {
1673             $groupname = get_string('potusersmatching', 'role', $search);
1674         } else {
1675             $groupname = get_string('potusers', 'role');
1676         }
1678         return array($groupname => $availableusers);
1679     }
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     }
1689 class 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      */
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);
1700     }
1702     public function find_users($search) {
1703         global $DB, $CFG;
1704         list($wherecondition, $params) = $this->search_sql($search, '');
1706         $fields      = 'SELECT ' . $this->required_fields_sql('');
1707         $countfields = 'SELECT COUNT(1)';
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";
1717         list($sort, $sortparams) = users_order_by_sql('', $search, $this->accesscontext);
1718         $params = array_merge($params, $sortparams);
1719         $order = ' ORDER BY ' . $sort;
1721         $availableusers = $DB->get_records_sql($fields . $sql . $order, $params);
1723         if (empty($availableusers)) {
1724             return array();
1725         }
1727         $mainadmin = array();
1728         $mainadminuser = get_admin();
1729         if ($mainadminuser && isset($availableusers[$mainadminuser->id])) {
1730             $mainadmin = array($mainadminuser->id => $availableusers[$mainadminuser->id]);
1731             unset($availableusers[$mainadminuser->id]);
1732         }
1734         $result = array();
1735         if ($mainadmin) {
1736             $result[get_string('mainadmin', 'role')] = $mainadmin;
1737         }
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         }
1748         return $result;
1749     }
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     }