Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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   * This page handles listing of quiz overrides
  19   *
  20   * @package    mod_quiz
  21   * @copyright  2010 Matt Petro
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  require_once(__DIR__ . '/../../config.php');
  26  require_once($CFG->dirroot.'/mod/quiz/lib.php');
  27  require_once($CFG->dirroot.'/mod/quiz/locallib.php');
  28  require_once($CFG->dirroot.'/mod/quiz/override_form.php');
  29  
  30  
  31  $cmid = required_param('cmid', PARAM_INT);
  32  $mode = optional_param('mode', '', PARAM_ALPHA); // One of 'user' or 'group', default is 'group'.
  33  
  34  list($course, $cm) = get_course_and_cm_from_cmid($cmid, 'quiz');
  35  $quiz = $DB->get_record('quiz', ['id' => $cm->instance], '*', MUST_EXIST);
  36  
  37  require_login($course, false, $cm);
  38  
  39  $context = context_module::instance($cm->id);
  40  
  41  // Check the user has the required capabilities to list overrides.
  42  $canedit = has_capability('mod/quiz:manageoverrides', $context);
  43  if (!$canedit) {
  44      require_capability('mod/quiz:viewoverrides', $context);
  45  }
  46  
  47  $quizgroupmode = groups_get_activity_groupmode($cm);
  48  $showallgroups = ($quizgroupmode == NOGROUPS) || has_capability('moodle/site:accessallgroups', $context);
  49  
  50  // Get the course groups that the current user can access.
  51  $groups = $showallgroups ? groups_get_all_groups($cm->course) : groups_get_activity_allowed_groups($cm);
  52  
  53  // Default mode is "group", unless there are no groups.
  54  if ($mode != "user" and $mode != "group") {
  55      if (!empty($groups)) {
  56          $mode = "group";
  57      } else {
  58          $mode = "user";
  59      }
  60  }
  61  $groupmode = ($mode == "group");
  62  
  63  $url = new moodle_url('/mod/quiz/overrides.php', ['cmid' => $cm->id, 'mode' => $mode]);
  64  
  65  $title = get_string('overridesforquiz', 'quiz',
  66          format_string($quiz->name, true, ['context' => $context]));
  67  $PAGE->set_url($url);
  68  $PAGE->set_pagelayout('admin');
  69  $PAGE->add_body_class('limitedwidth');
  70  $PAGE->set_title($title);
  71  $PAGE->set_heading($course->fullname);
  72  $PAGE->activityheader->disable();
  73  
  74  // Activate the secondary nav tab.
  75  $PAGE->set_secondary_active_tab("mod_quiz_useroverrides");
  76  
  77  // Delete orphaned group overrides.
  78  $sql = 'SELECT o.id
  79            FROM {quiz_overrides} o
  80       LEFT JOIN {groups} g ON o.groupid = g.id
  81           WHERE o.groupid IS NOT NULL
  82                 AND g.id IS NULL
  83                 AND o.quiz = ?';
  84  $params = [$quiz->id];
  85  $orphaned = $DB->get_records_sql($sql, $params);
  86  if (!empty($orphaned)) {
  87      $DB->delete_records_list('quiz_overrides', 'id', array_keys($orphaned));
  88  }
  89  
  90  $overrides = [];
  91  $colclasses = [];
  92  $headers = [];
  93  
  94  // Fetch all overrides.
  95  if ($groupmode) {
  96      $headers[] = get_string('group');
  97      // To filter the result by the list of groups that the current user has access to.
  98      if ($groups) {
  99          $params = ['quizid' => $quiz->id];
 100          list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED);
 101          $params += $inparams;
 102  
 103          $sql = "SELECT o.*, g.name
 104                    FROM {quiz_overrides} o
 105                    JOIN {groups} g ON o.groupid = g.id
 106                   WHERE o.quiz = :quizid AND g.id $insql
 107                ORDER BY g.name";
 108  
 109          $overrides = $DB->get_records_sql($sql, $params);
 110      }
 111  
 112  } else {
 113      // User overrides.
 114      $colclasses[] = 'colname';
 115      $headers[] = get_string('user');
 116      $userfieldsapi = \core_user\fields::for_identity($context)->with_name()->with_userpic();
 117      $extrauserfields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
 118      $userfieldssql = $userfieldsapi->get_sql('u', true, '', 'userid', false);
 119      foreach ($extrauserfields as $field) {
 120          $colclasses[] = 'col' . $field;
 121          $headers[] = \core_user\fields::get_display_name($field);
 122      }
 123  
 124      list($sort, $params) = users_order_by_sql('u', null, $context, $extrauserfields);
 125      $params['quizid'] = $quiz->id;
 126  
 127      if ($showallgroups) {
 128          $groupsjoin = '';
 129          $groupswhere = '';
 130  
 131      } else if ($groups) {
 132          list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED);
 133          $groupsjoin = 'JOIN {groups_members} gm ON u.id = gm.userid';
 134          $groupswhere = ' AND gm.groupid ' . $insql;
 135          $params += $inparams;
 136  
 137      } else {
 138          // User cannot see any data.
 139          $groupsjoin = '';
 140          $groupswhere = ' AND 1 = 2';
 141      }
 142  
 143      $overrides = $DB->get_records_sql("
 144              SELECT o.*, {$userfieldssql->selects}
 145                FROM {quiz_overrides} o
 146                JOIN {user} u ON o.userid = u.id
 147                    {$userfieldssql->joins}
 148                $groupsjoin
 149               WHERE o.quiz = :quizid
 150                 $groupswhere
 151               ORDER BY $sort
 152              ", array_merge($params, $userfieldssql->params));
 153  }
 154  
 155  // Initialise table.
 156  $table = new html_table();
 157  $table->head = $headers;
 158  $table->colclasses = $colclasses;
 159  $table->headspan = array_fill(0, count($headers), 1);
 160  
 161  $table->head[] = get_string('overrides', 'quiz');
 162  $table->colclasses[] = 'colsetting';
 163  $table->colclasses[] = 'colvalue';
 164  $table->headspan[] = 2;
 165  
 166  if ($canedit) {
 167      $table->head[] = get_string('action');
 168      $table->colclasses[] = 'colaction';
 169      $table->headspan[] = 1;
 170  }
 171  $userurl = new moodle_url('/user/view.php', []);
 172  $groupurl = new moodle_url('/group/overview.php', ['id' => $cm->course]);
 173  
 174  $overridedeleteurl = new moodle_url('/mod/quiz/overridedelete.php');
 175  $overrideediturl = new moodle_url('/mod/quiz/overrideedit.php');
 176  
 177  $hasinactive = false; // Whether there are any inactive overrides.
 178  
 179  foreach ($overrides as $override) {
 180  
 181      // Check if this override is active.
 182      $active = true;
 183      if (!$groupmode) {
 184          if (!has_capability('mod/quiz:attempt', $context, $override->userid)) {
 185              // User not allowed to take the quiz.
 186              $active = false;
 187          } else if (!\core_availability\info_module::is_user_visible($cm, $override->userid)) {
 188              // User cannot access the module.
 189              $active = false;
 190          }
 191      }
 192      if (!$active) {
 193          $hasinactive = true;
 194      }
 195  
 196      // Prepare the information about which settings are overridden.
 197      $fields = [];
 198      $values = [];
 199  
 200      // Format timeopen.
 201      if (isset($override->timeopen)) {
 202          $fields[] = get_string('quizopens', 'quiz');
 203          $values[] = $override->timeopen > 0 ?
 204                  userdate($override->timeopen) : get_string('noopen', 'quiz');
 205      }
 206      // Format timeclose.
 207      if (isset($override->timeclose)) {
 208          $fields[] = get_string('quizcloses', 'quiz');
 209          $values[] = $override->timeclose > 0 ?
 210                  userdate($override->timeclose) : get_string('noclose', 'quiz');
 211      }
 212      // Format timelimit.
 213      if (isset($override->timelimit)) {
 214          $fields[] = get_string('timelimit', 'quiz');
 215          $values[] = $override->timelimit > 0 ?
 216                  format_time($override->timelimit) : get_string('none', 'quiz');
 217      }
 218      // Format number of attempts.
 219      if (isset($override->attempts)) {
 220          $fields[] = get_string('attempts', 'quiz');
 221          $values[] = $override->attempts > 0 ?
 222                  $override->attempts : get_string('unlimited');
 223      }
 224      // Format password.
 225      if (isset($override->password)) {
 226          $fields[] = get_string('requirepassword', 'quiz');
 227          $values[] = $override->password !== '' ?
 228                  get_string('enabled', 'quiz') : get_string('none', 'quiz');
 229      }
 230  
 231      // Prepare the information about who this override applies to.
 232      $extranamebit = $active ? '' : '*';
 233      $usercells = [];
 234      if ($groupmode) {
 235          $groupcell = new html_table_cell();
 236          $groupcell->rowspan = count($fields);
 237          $groupcell->text = html_writer::link(new moodle_url($groupurl, ['group' => $override->groupid]),
 238              format_string($override->name, true, ['context' => $context]) . $extranamebit);
 239          $usercells[] = $groupcell;
 240      } else {
 241          $usercell = new html_table_cell();
 242          $usercell->rowspan = count($fields);
 243          $usercell->text = html_writer::link(new moodle_url($userurl, ['id' => $override->userid]),
 244                  fullname($override) . $extranamebit);
 245          $usercells[] = $usercell;
 246  
 247          foreach ($extrauserfields as $field) {
 248              $usercell = new html_table_cell();
 249              $usercell->rowspan = count($fields);
 250              $usercell->text = s($override->$field);
 251              $usercells[] = $usercell;
 252          }
 253      }
 254  
 255      // Prepare the actions.
 256      if ($canedit) {
 257          // Icons.
 258          $iconstr = '';
 259  
 260          // Edit.
 261          $editurlstr = $overrideediturl->out(true, ['id' => $override->id]);
 262          $iconstr = '<a title="' . get_string('edit') . '" href="' . $editurlstr . '">' .
 263                  $OUTPUT->pix_icon('t/edit', get_string('edit')) . '</a> ';
 264          // Duplicate.
 265          $copyurlstr = $overrideediturl->out(true,
 266                  ['id' => $override->id, 'action' => 'duplicate']);
 267          $iconstr .= '<a title="' . get_string('copy') . '" href="' . $copyurlstr . '">' .
 268                  $OUTPUT->pix_icon('t/copy', get_string('copy')) . '</a> ';
 269          // Delete.
 270          $deleteurlstr = $overridedeleteurl->out(true,
 271                  ['id' => $override->id, 'sesskey' => sesskey()]);
 272          $iconstr .= '<a title="' . get_string('delete') . '" href="' . $deleteurlstr . '">' .
 273                  $OUTPUT->pix_icon('t/delete', get_string('delete')) . '</a> ';
 274  
 275          $actioncell = new html_table_cell();
 276          $actioncell->rowspan = count($fields);
 277          $actioncell->text = $iconstr;
 278      }
 279  
 280      // Add the data to the table.
 281      for ($i = 0; $i < count($fields); ++$i) {
 282          $row = new html_table_row();
 283          if (!$active) {
 284              $row->attributes['class'] = 'dimmed_text';
 285          }
 286  
 287          if ($i == 0) {
 288              $row->cells = $usercells;
 289          }
 290  
 291          $labelcell = new html_table_cell();
 292          $labelcell->text = $fields[$i];
 293          $row->cells[] = $labelcell;
 294          $valuecell = new html_table_cell();
 295          $valuecell->text = $values[$i];
 296          $row->cells[] = $valuecell;
 297  
 298          if ($canedit && $i == 0) {
 299              $row->cells[] = $actioncell;
 300          }
 301  
 302          $table->data[] = $row;
 303      }
 304  }
 305  
 306  // Work out what else needs to be displayed.
 307  $addenabled = true;
 308  $warningmessage = '';
 309  if ($canedit) {
 310      if ($groupmode) {
 311          if (empty($groups)) {
 312              // There are no groups.
 313              $warningmessage = get_string('groupsnone', 'quiz');
 314              $addenabled = false;
 315          }
 316      } else {
 317          // See if there are any students in the quiz.
 318          if ($showallgroups) {
 319              $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id');
 320              $nousermessage = get_string('usersnone', 'quiz');
 321          } else if ($groups) {
 322              $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id', '', '', '', array_keys($groups));
 323              $nousermessage = get_string('usersnone', 'quiz');
 324          } else {
 325              $users = [];
 326              $nousermessage = get_string('groupsnone', 'quiz');
 327          }
 328          $info = new \core_availability\info_module($cm);
 329          $users = $info->filter_user_list($users);
 330  
 331          if (empty($users)) {
 332              // There are no students.
 333              $warningmessage = $nousermessage;
 334              $addenabled = false;
 335          }
 336      }
 337  }
 338  
 339  // Tertiary navigation.
 340  echo $OUTPUT->header();
 341  $renderer = $PAGE->get_renderer('mod_quiz');
 342  $tertiarynav = new \mod_quiz\output\overrides_actions($cmid, $mode, $canedit, $addenabled);
 343  echo $renderer->render($tertiarynav);
 344  
 345  if ($mode === 'user') {
 346      echo $OUTPUT->heading(get_string('useroverrides', 'quiz'));
 347  } else {
 348      echo $OUTPUT->heading(get_string('groupoverrides', 'quiz'));
 349  }
 350  
 351  // Output the table and button.
 352  echo html_writer::start_tag('div', ['id' => 'quizoverrides']);
 353  if (count($table->data)) {
 354      echo html_writer::table($table);
 355  } else {
 356      if ($groupmode) {
 357          echo $OUTPUT->notification(get_string('overridesnoneforgroups', 'quiz'), 'info', false);
 358      } else {
 359          echo $OUTPUT->notification(get_string('overridesnoneforusers', 'quiz'), 'info', false);
 360      }
 361  }
 362  if ($hasinactive) {
 363      echo $OUTPUT->notification(get_string('inactiveoverridehelp', 'quiz'), 'info', false);
 364  }
 365  
 366  if ($warningmessage) {
 367      echo $OUTPUT->notification($warningmessage, 'error');
 368  }
 369  
 370  echo html_writer::end_tag('div');
 371  
 372  // Finish the page.
 373  echo $OUTPUT->footer();