Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * 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   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  
  28  /**
  29   * As well as tracking the permissions information about the role we are creating
  30   * or editing, we also track the other information about the role. (This class is
  31   * starting to be more and more like a formslib form in some respects.)
  32   */
  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 = '';
  41  
  42      protected $allowassign;
  43      protected $allowoverride;
  44      protected $allowswitch;
  45      protected $allowview;
  46  
  47      public function __construct($context, $roleid) {
  48          $this->roleid = $roleid;
  49          parent::__construct($context, 'defineroletable', $roleid);
  50          $this->displaypermissions = $this->allpermissions;
  51          $this->strperms[$this->allpermissions[CAP_INHERIT]] = get_string('notset', 'core_role');
  52  
  53          $this->allcontextlevels = array();
  54          $levels = context_helper::get_all_levels();
  55          foreach ($levels as $level => $classname) {
  56              $this->allcontextlevels[$level] = context_helper::get_level_name($level);
  57          }
  58          $this->add_classes(['table-striped']);
  59      }
  60  
  61      protected function load_current_permissions() {
  62          global $DB;
  63          if ($this->roleid) {
  64              if (!$this->role = $DB->get_record('role', array('id' => $this->roleid))) {
  65                  throw new moodle_exception('invalidroleid');
  66              }
  67              $contextlevels = get_role_contextlevels($this->roleid);
  68              // Put the contextlevels in the array keys, as well as the values.
  69              if (!empty($contextlevels)) {
  70                  $this->contextlevels = array_combine($contextlevels, $contextlevels);
  71              } else {
  72                  $this->contextlevels = array();
  73              }
  74              $this->allowassign = array_keys($this->get_allow_roles_list('assign'));
  75              $this->allowoverride = array_keys($this->get_allow_roles_list('override'));
  76              $this->allowswitch = array_keys($this->get_allow_roles_list('switch'));
  77              $this->allowview = array_keys($this->get_allow_roles_list('view'));
  78  
  79          } else {
  80              $this->role = new stdClass;
  81              $this->role->name = '';
  82              $this->role->shortname = '';
  83              $this->role->description = '';
  84              $this->role->archetype = '';
  85              $this->contextlevels = array();
  86              $this->allowassign = array();
  87              $this->allowoverride = array();
  88              $this->allowswitch = array();
  89              $this->allowview = array();
  90          }
  91          parent::load_current_permissions();
  92      }
  93  
  94      public function read_submitted_permissions() {
  95          global $DB;
  96          $this->errors = array();
  97  
  98          // Role short name. We clean this in a special way. We want to end up
  99          // with only lowercase safe ASCII characters.
 100          $shortname = optional_param('shortname', null, PARAM_RAW);
 101          if (!is_null($shortname)) {
 102              $this->role->shortname = $shortname;
 103              $this->role->shortname = core_text::specialtoascii($this->role->shortname);
 104              $this->role->shortname = core_text::strtolower(clean_param($this->role->shortname, PARAM_ALPHANUMEXT));
 105              if (empty($this->role->shortname)) {
 106                  $this->errors['shortname'] = get_string('errorbadroleshortname', 'core_role');
 107              } else if (core_text::strlen($this->role->shortname) > 100) { // Check if it exceeds the max of 100 characters.
 108                  $this->errors['shortname'] = get_string('errorroleshortnametoolong', 'core_role');
 109              }
 110          }
 111          if ($DB->record_exists_select('role', 'shortname = ? and id <> ?', array($this->role->shortname, $this->roleid))) {
 112              $this->errors['shortname'] = get_string('errorexistsroleshortname', 'core_role');
 113          }
 114  
 115          // Role name.
 116          $name = optional_param('name', null, PARAM_TEXT);
 117          if (!is_null($name)) {
 118              $this->role->name = $name;
 119              // Hack: short names of standard roles are equal to archetypes, empty name means localised via lang packs.
 120              $archetypes = get_role_archetypes();
 121              if (!isset($archetypes[$shortname]) and html_is_blank($this->role->name)) {
 122                  $this->errors['name'] = get_string('errorbadrolename', 'core_role');
 123              }
 124          }
 125          if ($this->role->name !== '' and $DB->record_exists_select('role', 'name = ? and id <> ?', array($this->role->name, $this->roleid))) {
 126              $this->errors['name'] = get_string('errorexistsrolename', 'core_role');
 127          }
 128  
 129          // Description.
 130          $description = optional_param('description', null, PARAM_RAW);
 131          if (!is_null($description)) {
 132              $this->role->description = $description;
 133          }
 134  
 135          // Legacy type.
 136          $archetype = optional_param('archetype', null, PARAM_RAW);
 137          if (isset($archetype)) {
 138              $archetypes = get_role_archetypes();
 139              if (isset($archetypes[$archetype])) {
 140                  $this->role->archetype = $archetype;
 141              } else {
 142                  $this->role->archetype = '';
 143              }
 144          }
 145  
 146          // Assignable context levels.
 147          foreach ($this->allcontextlevels as $cl => $notused) {
 148              $assignable = optional_param('contextlevel' . $cl, null, PARAM_BOOL);
 149              if (!is_null($assignable)) {
 150                  if ($assignable) {
 151                      $this->contextlevels[$cl] = $cl;
 152                  } else {
 153                      unset($this->contextlevels[$cl]);
 154                  }
 155              }
 156          }
 157  
 158          // Allowed roles.
 159          $allow = optional_param_array('allowassign', null, PARAM_INT);
 160          if (!is_null($allow)) {
 161              $this->allowassign = array_filter($allow);
 162          }
 163          $allow = optional_param_array('allowoverride', null, PARAM_INT);
 164          if (!is_null($allow)) {
 165              $this->allowoverride = array_filter($allow);
 166          }
 167          $allow = optional_param_array('allowswitch', null, PARAM_INT);
 168          if (!is_null($allow)) {
 169              $this->allowswitch = array_filter($allow);
 170          }
 171          $allow = optional_param_array('allowview', null, PARAM_INT);
 172          if (!is_null($allow)) {
 173              $this->allowview = array_filter($allow);
 174          }
 175  
 176          // Now read the permissions for each capability.
 177          parent::read_submitted_permissions();
 178      }
 179  
 180      public function is_submission_valid() {
 181          return empty($this->errors);
 182      }
 183  
 184      /**
 185       * Call this after the table has been initialised,
 186       * this resets everything to that role.
 187       *
 188       * @param int $roleid role id or 0 for no role
 189       * @param array $options array with following keys:
 190       *      'name', 'shortname', 'description', 'permissions', 'archetype',
 191       *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
 192       *      'allowview'
 193       */
 194      public function force_duplicate($roleid, array $options) {
 195          global $DB;
 196  
 197          if ($roleid == 0) {
 198              // This means reset to nothing == remove everything.
 199  
 200              if ($options['shortname']) {
 201                  $this->role->shortname = '';
 202              }
 203  
 204              if ($options['name']) {
 205                  $this->role->name = '';
 206              }
 207  
 208              if ($options['description']) {
 209                  $this->role->description = '';
 210              }
 211  
 212              if ($options['archetype']) {
 213                  $this->role->archetype = '';
 214              }
 215  
 216              if ($options['contextlevels']) {
 217                  $this->contextlevels = array();
 218              }
 219  
 220              if ($options['allowassign']) {
 221                  $this->allowassign = array();
 222              }
 223              if ($options['allowoverride']) {
 224                  $this->allowoverride = array();
 225              }
 226              if ($options['allowswitch']) {
 227                  $this->allowswitch = array();
 228              }
 229              if ($options['allowview']) {
 230                  $this->allowview = array();
 231              }
 232  
 233              if ($options['permissions']) {
 234                  foreach ($this->capabilities as $capid => $cap) {
 235                      $this->permissions[$cap->name] = CAP_INHERIT;
 236                  }
 237              }
 238  
 239              return;
 240          }
 241  
 242          $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
 243  
 244          if ($options['shortname']) {
 245              $this->role->shortname = $role->shortname;
 246          }
 247  
 248          if ($options['name']) {
 249              $this->role->name = $role->name;
 250          }
 251  
 252          if ($options['description']) {
 253              $this->role->description = $role->description;
 254          }
 255  
 256          if ($options['archetype']) {
 257              $this->role->archetype = $role->archetype;
 258          }
 259  
 260          if ($options['contextlevels']) {
 261              $this->contextlevels = array();
 262              $levels = get_role_contextlevels($roleid);
 263              foreach ($levels as $cl) {
 264                  $this->contextlevels[$cl] = $cl;
 265              }
 266          }
 267  
 268          if ($options['allowassign']) {
 269              $this->allowassign = array_keys($this->get_allow_roles_list('assign', $roleid));
 270          }
 271          if ($options['allowoverride']) {
 272              $this->allowoverride = array_keys($this->get_allow_roles_list('override', $roleid));
 273          }
 274          if ($options['allowswitch']) {
 275              $this->allowswitch = array_keys($this->get_allow_roles_list('switch', $roleid));
 276          }
 277          if ($options['allowview']) {
 278              $this->allowview = array_keys($this->get_allow_roles_list('view', $roleid));
 279          }
 280  
 281          if ($options['permissions']) {
 282              $this->permissions = $DB->get_records_menu('role_capabilities',
 283                  array('roleid' => $roleid, 'contextid' => context_system::instance()->id),
 284                  '', 'capability,permission');
 285  
 286              foreach ($this->capabilities as $capid => $cap) {
 287                  if (!isset($this->permissions[$cap->name])) {
 288                      $this->permissions[$cap->name] = CAP_INHERIT;
 289                  }
 290              }
 291          }
 292      }
 293  
 294      /**
 295       * Change the role definition to match given archetype.
 296       *
 297       * @param string $archetype
 298       * @param array $options array with following keys:
 299       *      'name', 'shortname', 'description', 'permissions', 'archetype',
 300       *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
 301       *      'allowview'
 302       */
 303      public function force_archetype($archetype, array $options) {
 304          $archetypes = get_role_archetypes();
 305          if (!isset($archetypes[$archetype])) {
 306              throw new coding_exception('Unknown archetype: '.$archetype);
 307          }
 308  
 309          if ($options['shortname']) {
 310              $this->role->shortname = '';
 311          }
 312  
 313          if ($options['name']) {
 314              $this->role->name = '';
 315          }
 316  
 317          if ($options['description']) {
 318              $this->role->description = '';
 319          }
 320  
 321          if ($options['archetype']) {
 322              $this->role->archetype = $archetype;
 323          }
 324  
 325          if ($options['contextlevels']) {
 326              $this->contextlevels = array();
 327              $defaults = get_default_contextlevels($archetype);
 328              foreach ($defaults as $cl) {
 329                  $this->contextlevels[$cl] = $cl;
 330              }
 331          }
 332  
 333          if ($options['allowassign']) {
 334              $this->allowassign = get_default_role_archetype_allows('assign', $archetype);
 335          }
 336          if ($options['allowoverride']) {
 337              $this->allowoverride = get_default_role_archetype_allows('override', $archetype);
 338          }
 339          if ($options['allowswitch']) {
 340              $this->allowswitch = get_default_role_archetype_allows('switch', $archetype);
 341          }
 342          if ($options['allowview']) {
 343              $this->allowview = get_default_role_archetype_allows('view', $archetype);
 344          }
 345  
 346          if ($options['permissions']) {
 347              $defaultpermissions = get_default_capabilities($archetype);
 348              foreach ($this->permissions as $k => $v) {
 349                  if (isset($defaultpermissions[$k])) {
 350                      $this->permissions[$k] = $defaultpermissions[$k];
 351                      continue;
 352                  }
 353                  $this->permissions[$k] = CAP_INHERIT;
 354              }
 355          }
 356      }
 357  
 358      /**
 359       * Change the role definition to match given preset.
 360       *
 361       * @param string $xml
 362       * @param array $options array with following keys:
 363       *      'name', 'shortname', 'description', 'permissions', 'archetype',
 364       *      'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
 365       *      'allowview'
 366       */
 367      public function force_preset($xml, array $options) {
 368          if (!$info = core_role_preset::parse_preset($xml)) {
 369              throw new coding_exception('Invalid role preset');
 370          }
 371  
 372          if ($options['shortname']) {
 373              if (isset($info['shortname'])) {
 374                  $this->role->shortname = $info['shortname'];
 375              }
 376          }
 377  
 378          if ($options['name']) {
 379              if (isset($info['name'])) {
 380                  $this->role->name = $info['name'];
 381              }
 382          }
 383  
 384          if ($options['description']) {
 385              if (isset($info['description'])) {
 386                  $this->role->description = $info['description'];
 387              }
 388          }
 389  
 390          if ($options['archetype']) {
 391              if (isset($info['archetype'])) {
 392                  $this->role->archetype = $info['archetype'];
 393              }
 394          }
 395  
 396          if ($options['contextlevels']) {
 397              if (isset($info['contextlevels'])) {
 398                  $this->contextlevels = $info['contextlevels'];
 399              }
 400          }
 401  
 402          foreach (array('assign', 'override', 'switch', 'view') as $type) {
 403              if ($options['allow'.$type]) {
 404                  if (isset($info['allow'.$type])) {
 405                      $this->{'allow'.$type} = $info['allow'.$type];
 406                  }
 407              }
 408          }
 409  
 410          if ($options['permissions']) {
 411              foreach ($this->permissions as $k => $v) {
 412                  // Note: do not set everything else to CAP_INHERIT here
 413                  //       because the xml file might not contain all capabilities.
 414                  if (isset($info['permissions'][$k])) {
 415                      $this->permissions[$k] = $info['permissions'][$k];
 416                  }
 417              }
 418          }
 419      }
 420  
 421      public function get_role_name() {
 422          return $this->role->name;
 423      }
 424  
 425      public function get_role_id() {
 426          return $this->role->id;
 427      }
 428  
 429      public function get_archetype() {
 430          return $this->role->archetype;
 431      }
 432  
 433      protected function load_parent_permissions() {
 434          $this->parentpermissions = get_default_capabilities($this->role->archetype);
 435      }
 436  
 437      public function save_changes() {
 438          global $DB, $USER;
 439  
 440          if (!$this->roleid) {
 441              // Creating role.
 442              $this->role->id = create_role($this->role->name, $this->role->shortname, $this->role->description, $this->role->archetype);
 443              $this->roleid = $this->role->id; // Needed to make the parent::save_changes(); call work.
 444          } else {
 445              // Updating role.
 446              $DB->update_record('role', $this->role);
 447  
 448              // Trigger role updated event.
 449              \core\event\role_updated::create([
 450                  'userid' => $USER->id,
 451                  'objectid' => $this->role->id,
 452                  'context' => $this->context,
 453                  'other' => [
 454                      'name' => $this->role->name,
 455                      'shortname' => $this->role->shortname,
 456                      'description' => $this->role->description,
 457                      'archetype' => $this->role->archetype,
 458                      'contextlevels' => $this->contextlevels
 459                  ]
 460              ])->trigger();
 461  
 462              // This will ensure the course contacts cache is purged so name changes get updated in
 463              // the UI. It would be better to do this only when we know that fields affected are
 464              // updated. But thats getting into the weeds of the coursecat cache and role edits
 465              // should not be that frequent, so here is the ugly brutal approach.
 466              core_course_category::role_assignment_changed($this->role->id, context_system::instance());
 467          }
 468  
 469          // Assignable contexts.
 470          set_role_contextlevels($this->role->id, $this->contextlevels);
 471  
 472          // Set allowed roles.
 473          $this->save_allow('assign');
 474          $this->save_allow('override');
 475          $this->save_allow('switch');
 476          $this->save_allow('view');
 477  
 478          // Permissions.
 479          parent::save_changes();
 480      }
 481  
 482      protected function save_allow($type) {
 483          global $DB;
 484  
 485          $current = array_keys($this->get_allow_roles_list($type));
 486          $wanted = $this->{'allow'.$type};
 487  
 488          $addfunction = "core_role_set_{$type}_allowed";
 489          $deltable = 'role_allow_'.$type;
 490          $field = 'allow'.$type;
 491          $eventclass = "\\core\\event\\role_allow_" . $type . "_updated";
 492          $context = context_system::instance();
 493  
 494          foreach ($current as $roleid) {
 495              if (!in_array($roleid, $wanted)) {
 496                  $DB->delete_records($deltable, array('roleid'=>$this->roleid, $field=>$roleid));
 497                  $eventclass::create([
 498                      'context' => $context,
 499                      'objectid' => $this->roleid,
 500                      'other' => ['targetroleid' => $roleid, 'allow' => false]
 501                  ])->trigger();
 502                  continue;
 503              }
 504              $key = array_search($roleid, $wanted);
 505              unset($wanted[$key]);
 506          }
 507  
 508          foreach ($wanted as $roleid) {
 509              if ($roleid == -1) {
 510                  $roleid = $this->roleid;
 511              }
 512              $addfunction($this->roleid, $roleid);
 513  
 514              if (in_array($roleid, $wanted)) {
 515                  $eventclass::create([
 516                      'context' => $context,
 517                      'objectid' => $this->roleid,
 518                      'other' => ['targetroleid' => $roleid, 'allow' => true]
 519                  ])->trigger();
 520              }
 521          }
 522      }
 523  
 524      protected function get_name_field($id) {
 525          return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->name) . '"' .
 526                  ' class="form-control"/>';
 527      }
 528  
 529      protected function get_shortname_field($id) {
 530          return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="100" value="' . s($this->role->shortname) . '"' .
 531                  ' class="form-control"/>';
 532      }
 533  
 534      protected function get_description_field($id) {
 535          return '<textarea class="form-textarea form-control" id="'. s($id) .'" name="description" rows="10" cols="50">' .
 536              htmlspecialchars($this->role->description, ENT_COMPAT) .
 537              '</textarea>';
 538      }
 539  
 540      protected function get_archetype_field($id) {
 541          $options = array();
 542          $options[''] = get_string('none');
 543          foreach (get_role_archetypes() as $type) {
 544              $options[$type] = get_string('archetype'.$type, 'role');
 545          }
 546          return html_writer::select($options, 'archetype', $this->role->archetype, false,
 547              array('class' => 'custom-select'));
 548      }
 549  
 550      protected function get_assignable_levels_control() {
 551          $output = '';
 552          foreach ($this->allcontextlevels as $cl => $clname) {
 553              $extraarguments = $this->disabled;
 554              if (in_array($cl, $this->contextlevels)) {
 555                  $extraarguments .= 'checked="checked" ';
 556              }
 557              if (!$this->disabled) {
 558                  $output .= '<input type="hidden" name="contextlevel' . $cl . '" value="0" />';
 559              }
 560              $output .= '<div class="form-check justify-content-start w-100">';
 561              $output .= '<input class="form-check-input" type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
 562                  '" value="1" ' . $extraarguments . '/> ';
 563              $output .= '<label class="form-check-label" for="cl' . $cl . '">' . $clname . "</label>\n";
 564              $output .= '</div>';
 565          }
 566          return $output;
 567      }
 568  
 569      /**
 570       * Returns an array of roles of the allowed type.
 571       *
 572       * @param string $type Must be one of: assign, switch, or override.
 573       * @param int $roleid (null means current role)
 574       * @return array
 575       */
 576      protected function get_allow_roles_list($type, $roleid = null) {
 577          global $DB;
 578  
 579          if ($type !== 'assign' and $type !== 'switch' and $type !== 'override' and $type !== 'view') {
 580              debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
 581              return array();
 582          }
 583  
 584          if ($roleid === null) {
 585              $roleid = $this->roleid;
 586          }
 587  
 588          if (empty($roleid)) {
 589              return array();
 590          }
 591  
 592          $sql = "SELECT r.*
 593                    FROM {role} r
 594                    JOIN {role_allow_{$type}} a ON a.allow{$type} = r.id
 595                   WHERE a.roleid = :roleid
 596                ORDER BY r.sortorder ASC";
 597          return $DB->get_records_sql($sql, array('roleid'=>$roleid));
 598      }
 599  
 600      /**
 601       * Returns an array of roles with the allowed type.
 602       *
 603       * @param string $type Must be one of: assign, switch, override or view.
 604       * @return array Am array of role names with the allowed type
 605       */
 606      protected function get_allow_role_control($type) {
 607          if ($type !== 'assign' and $type !== 'switch' and $type !== 'override' and $type !== 'view') {
 608              debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
 609              return '';
 610          }
 611  
 612          $property = 'allow'.$type;
 613          $selected = $this->$property;
 614  
 615          $options = array();
 616          foreach (role_get_names(null, ROLENAME_ALIAS) as $role) {
 617              $options[$role->id] = $role->localname;
 618          }
 619          if ($this->roleid == 0) {
 620              $options[-1] = get_string('thisnewrole', 'core_role');
 621          }
 622          return
 623              html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'allow'.$type.'[]', 'value' => "")) .
 624              html_writer::select($options, 'allow'.$type.'[]', $selected, false, array('multiple' => 'multiple',
 625              'size' => 10, 'class' => 'form-control'));
 626      }
 627  
 628      /**
 629       * Returns information about the risks associated with a role.
 630       *
 631       * @return string
 632       */
 633      protected function get_role_risks_info() {
 634          return '';
 635      }
 636  
 637      /**
 638       * Print labels, fields and help icon on role administration page.
 639       *
 640       * @param string $name The field name.
 641       * @param string $caption The field caption.
 642       * @param string $field The field type.
 643       * @param null|string $helpicon The help icon content.
 644       */
 645      protected function print_field($name, $caption, $field, $helpicon = null) {
 646          global $OUTPUT;
 647          // Attempt to generate HTML like formslib.
 648          echo '<div class="fitem row form-group">';
 649          echo '<div class="fitemtitle col-md-3">';
 650          if ($name) {
 651              echo '<label for="' . $name . '">';
 652          }
 653          echo $caption;
 654          if ($name) {
 655              echo "</label>\n";
 656          }
 657          if ($helpicon) {
 658              echo '<span class="float-sm-right text-nowrap">'.$helpicon.'</span>';
 659          }
 660          echo '</div>';
 661          if (isset($this->errors[$name])) {
 662              $extraclass = ' error';
 663          } else {
 664              $extraclass = '';
 665          }
 666          echo '<div class="felement col-md-9 form-inline' . $extraclass . '">';
 667          echo $field;
 668          if (isset($this->errors[$name])) {
 669              echo $OUTPUT->error_text($this->errors[$name]);
 670          }
 671          echo '</div>';
 672          echo '</div>';
 673      }
 674  
 675      protected function print_show_hide_advanced_button() {
 676          echo '<p class="definenotice">' . get_string('highlightedcellsshowdefault', 'core_role') . ' </p>';
 677          echo '<div class="advancedbutton">';
 678          echo '<input type="submit" class="btn btn-secondary" name="toggleadvanced" value="' .
 679              get_string('hideadvanced', 'form') . '" />';
 680          echo '</div>';
 681      }
 682  
 683      public function display() {
 684          global $OUTPUT;
 685          // Extra fields at the top of the page.
 686          echo '<div class="topfields clearfix">';
 687          $this->print_field('shortname', get_string('roleshortname', 'core_role'),
 688              $this->get_shortname_field('shortname'), $OUTPUT->help_icon('roleshortname', 'core_role'));
 689          $this->print_field('name', get_string('customrolename', 'core_role'), $this->get_name_field('name'),
 690              $OUTPUT->help_icon('customrolename', 'core_role'));
 691          $this->print_field('edit-description', get_string('customroledescription', 'core_role'),
 692              $this->get_description_field('description'), $OUTPUT->help_icon('customroledescription', 'core_role'));
 693          $this->print_field('menuarchetype', get_string('archetype', 'core_role'), $this->get_archetype_field('archetype'),
 694              $OUTPUT->help_icon('archetype', 'core_role'));
 695          $this->print_field('', get_string('maybeassignedin', 'core_role'), $this->get_assignable_levels_control());
 696          $this->print_field('menuallowassign', get_string('allowassign', 'core_role'), $this->get_allow_role_control('assign'));
 697          $this->print_field('menuallowoverride', get_string('allowoverride', 'core_role'), $this->get_allow_role_control('override'));
 698          $this->print_field('menuallowswitch', get_string('allowswitch', 'core_role'), $this->get_allow_role_control('switch'));
 699          $this->print_field('menuallowview', get_string('allowview', 'core_role'), $this->get_allow_role_control('view'));
 700          if ($risks = $this->get_role_risks_info()) {
 701              $this->print_field('', get_string('rolerisks', 'core_role'), $risks);
 702          }
 703          echo "</div>";
 704  
 705          $this->print_show_hide_advanced_button();
 706  
 707          // Now the permissions table.
 708          parent::display();
 709      }
 710  
 711      protected function add_permission_cells($capability) {
 712          // One cell for each possible permission.
 713          $content = '';
 714          foreach ($this->displaypermissions as $perm => $permname) {
 715              $strperm = $this->strperms[$permname];
 716              $extraclass = '';
 717              if ($perm == $this->parentpermissions[$capability->name]) {
 718                  $extraclass = ' capdefault';
 719              }
 720              $checked = '';
 721              if ($this->permissions[$capability->name] == $perm) {
 722                  $checked = 'checked="checked" ';
 723              }
 724              $content .= '<td class="' . $permname . $extraclass . '">';
 725              $content .= '<label><input type="radio" name="' . $capability->name .
 726                  '" value="' . $perm . '" ' . $checked . '/> ';
 727              $content .= '<span class="note">' . $strperm . '</span>';
 728              $content .= '</label></td>';
 729          }
 730          return $content;
 731      }
 732  }