From 5a96b5427fff7281ba750d1495f95b985f8c7b79 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Fri, 27 Nov 2020 16:20:45 +0000 Subject: [PATCH 1/2] MDL-69735 core renderer notifications: make closebutton option usable --- lib/classes/output/notification.php | 7 +++++-- lib/outputrenderers.php | 14 +++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/classes/output/notification.php b/lib/classes/output/notification.php index 1db678b0404..023193d94a6 100644 --- a/lib/classes/output/notification.php +++ b/lib/classes/output/notification.php @@ -84,9 +84,10 @@ class notification implements \renderable, \templatable { * Notification constructor. * * @param string $message the message to print out - * @param string $messagetype one of the NOTIFY_* constants.. + * @param ?string $messagetype one of the NOTIFY_* constants.. + * @param bool $closebutton Whether to show a close icon to remove the notification (default true). */ - public function __construct($message, $messagetype = null) { + public function __construct($message, $messagetype = null, $closebutton = true) { $this->message = $message; if (empty($messagetype)) { @@ -94,6 +95,8 @@ class notification implements \renderable, \templatable { } $this->messagetype = $messagetype; + + $this->closebutton = $closebutton; } /** diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 6a64c28cd38..6b61cab00f6 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -2875,10 +2875,11 @@ EOD; * Note: \core\notification::add() may be more suitable for your usage. * * @param string $message The message to print out. - * @param string $type The type of notification. See constants on \core\output\notification. + * @param ?string $type The type of notification. See constants on \core\output\notification. + * @param bool $closebutton Whether to show a close icon to remove the notification (default true). * @return string the HTML to output. */ - public function notification($message, $type = null) { + public function notification($message, $type = null, $closebutton = true) { $typemappings = [ // Valid types. 'success' => \core\output\notification::NOTIFY_SUCCESS, @@ -2922,7 +2923,7 @@ EOD; } } - $notification = new \core\output\notification($message, $type); + $notification = new \core\output\notification($message, $type, $closebutton); if (count($extraclasses)) { $notification->set_extra_classes($extraclasses); } @@ -4886,9 +4887,10 @@ class core_renderer_cli extends core_renderer { * * @param string $message The message to print out. * @param string $type The type of notification. See constants on \core\output\notification. + * @param bool $closebutton Whether to show a close icon to remove the notification (default true). * @return string A template fragment for a notification */ - public function notification($message, $type = null) { + public function notification($message, $type = null, $closebutton = true) { $message = clean_text($message); if ($type === 'notifysuccess' || $type === 'success') { return "++ $message ++\n"; @@ -4972,8 +4974,10 @@ class core_renderer_ajax extends core_renderer { * * @param string $message The message to print out. * @param string $type The type of notification. See constants on \core\output\notification. + * @param bool $closebutton Whether to show a close icon to remove the notification (default true). */ - public function notification($message, $type = null) {} + public function notification($message, $type = null, $closebutton = true) { + } /** * Used to display a redirection message. From 7350f41bf8b111242695ba79fde09f2a6b9ba414 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Fri, 27 Nov 2020 17:12:50 +0000 Subject: [PATCH 2/2] MDL-69735 quiz: new capability for read-only view of setting overrides --- mod/quiz/db/access.php | 131 ++++++----- mod/quiz/lang/en/deprecated.txt | 3 +- mod/quiz/lang/en/quiz.php | 8 +- mod/quiz/lib.php | 2 +- mod/quiz/overrides.php | 216 ++++++++++-------- .../tests/behat/quiz_group_override.feature | 63 +++-- .../tests/behat/quiz_user_override.feature | 73 +++--- mod/quiz/version.php | 2 +- 8 files changed, 281 insertions(+), 217 deletions(-) diff --git a/mod/quiz/db/access.php b/mod/quiz/db/access.php index ae31ddb7347..246641f6320 100644 --- a/mod/quiz/db/access.php +++ b/mod/quiz/db/access.php @@ -24,162 +24,173 @@ defined('MOODLE_INTERNAL') || die(); -$capabilities = array( +$capabilities = [ // Ability to see that the quiz exists, and the basic information // about it, for example the start date and time limit. - 'mod/quiz:view' => array( + 'mod/quiz:view' => [ 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'guest' => CAP_ALLOW, 'student' => CAP_ALLOW, 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ) - ), + ] + ], // Ability to add a new quiz to the course. - 'mod/quiz:addinstance' => array( + 'mod/quiz:addinstance' => [ 'riskbitmask' => RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, - 'archetypes' => array( + 'archetypes' => [ 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ), + ], 'clonepermissionsfrom' => 'moodle/course:manageactivities' - ), + ], // Ability to do the quiz as a 'student'. - 'mod/quiz:attempt' => array( + 'mod/quiz:attempt' => [ 'riskbitmask' => RISK_SPAM, 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'student' => CAP_ALLOW - ) - ), + ] + ], // Ability for a 'Student' to review their previous attempts. Review by // 'Teachers' is controlled by mod/quiz:viewreports. - 'mod/quiz:reviewmyattempts' => array( + 'mod/quiz:reviewmyattempts' => [ 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'student' => CAP_ALLOW - ), + ], 'clonepermissionsfrom' => 'moodle/quiz:attempt' - ), + ], // Edit the quiz settings, add and remove questions. - 'mod/quiz:manage' => array( + 'mod/quiz:manage' => [ 'riskbitmask' => RISK_SPAM, 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ) - ), + ] + ], // Edit the quiz overrides. - 'mod/quiz:manageoverrides' => array( + 'mod/quiz:manageoverrides' => [ 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ) - ), + ] + ], - // Preview the quiz. - 'mod/quiz:preview' => array( - 'captype' => 'write', // Only just a write. + // View the quiz overrides (only checked for users who don't have mod/quiz:manageoverrides. + 'mod/quiz:viewoverrides' => [ + 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ) - ), + ] + ], + + // Preview the quiz. + 'mod/quiz:preview' => [ + 'captype' => 'write', // Only just a write. + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ] + ], // Manually grade and comment on student attempts at a question. - 'mod/quiz:grade' => array( + 'mod/quiz:grade' => [ 'riskbitmask' => RISK_SPAM | RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ) - ), + ] + ], // Regrade quizzes. - 'mod/quiz:regrade' => array( + 'mod/quiz:regrade' => [ 'riskbitmask' => RISK_SPAM, 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ), + ], 'clonepermissionsfrom' => 'mod/quiz:grade' - ), + ], // View the quiz reports. - 'mod/quiz:viewreports' => array( + 'mod/quiz:viewreports' => [ 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ) - ), + ] + ], // Delete attempts using the overview report. - 'mod/quiz:deleteattempts' => array( + 'mod/quiz:deleteattempts' => [ 'riskbitmask' => RISK_DATALOSS, 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( + 'archetypes' => [ 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW - ) - ), + ] + ], // Do not have the time limit imposed. Used for accessibility legislation compliance. - 'mod/quiz:ignoretimelimits' => array( + 'mod/quiz:ignoretimelimits' => [ 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array() - ), + 'archetypes' => [] + ], // Receive a confirmation message of own quiz submission. - 'mod/quiz:emailconfirmsubmission' => array( + 'mod/quiz:emailconfirmsubmission' => [ 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array() - ), + 'archetypes' => [] + ], // Receive a notification message of other peoples' quiz submissions. - 'mod/quiz:emailnotifysubmission' => array( + 'mod/quiz:emailnotifysubmission' => [ 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array() - ), + 'archetypes' => [] + ], // Receive a notification message when a quiz attempt becomes overdue. - 'mod/quiz:emailwarnoverdue' => array( + 'mod/quiz:emailwarnoverdue' => [ 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array() - ), -); + 'archetypes' => [] + ], +]; diff --git a/mod/quiz/lang/en/deprecated.txt b/mod/quiz/lang/en/deprecated.txt index 29a19bc3732..ecadc8221b1 100644 --- a/mod/quiz/lang/en/deprecated.txt +++ b/mod/quiz/lang/en/deprecated.txt @@ -1,3 +1,4 @@ numattemptsmade,mod_quiz reviewofattempt,mod_quiz -reviewofpreview,mod_quiz \ No newline at end of file +reviewofpreview,mod_quiz +settingsoverrides,mod_quiz diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index b2684443eab..b59e4048403 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -613,6 +613,9 @@ $string['overridedeleteusersure'] = 'Are you sure you want to delete the overrid $string['overridegroup'] = 'Override group'; $string['overridegroupeventname'] = '{$a->quiz} - {$a->group}'; $string['overrides'] = 'Overrides'; +$string['overridesforquiz'] = 'Settings overrides: {$a}'; +$string['overridesnoneforgroups'] = 'No group settings overrides have been created for this quiz.'; +$string['overridesnoneforusers'] = 'No user settings overrides have been created for this quiz.'; $string['overrideuser'] = 'Override user'; $string['overrideusereventname'] = '{$a->quiz} - Override'; $string['pageshort'] = 'P'; @@ -724,7 +727,8 @@ $string['quizisopen'] = 'This quiz is open'; $string['quizisclosedwillopen'] = 'Quiz closed (opens {$a})'; $string['quizisopenwillclose'] = 'Quiz open (closes {$a})'; $string['quiz:manage'] = 'Manage quizzes'; -$string['quiz:manageoverrides'] = 'Manage quiz overrides'; +$string['quiz:manageoverrides'] = 'Manage quiz settings overrides'; +$string['quiz:viewoverrides'] = 'View quiz settings overrides'; $string['quiznavigation'] = 'Quiz navigation'; $string['quizopen'] = 'Open the quiz'; $string['quizeventopens'] = '{$a} opens'; @@ -886,7 +890,6 @@ $string['serveridentifier'] = 'Identifier'; $string['serverinfo'] = 'Server information'; $string['servers'] = 'Servers'; $string['serverurl'] = 'Server URL'; -$string['settingsoverrides'] = 'Settings overrides'; $string['shortanswer'] = 'Short answer'; $string['show'] = 'Show'; $string['showall'] = 'Show all questions on one page'; @@ -1008,3 +1011,4 @@ $string['yourfinalgradeis'] = 'Your final grade for this quiz is {$a}.'; $string['numattemptsmade'] = '{$a} attempts made on this quiz'; $string['reviewofattempt'] = 'Review of attempt {$a}'; $string['reviewofpreview'] = 'Review of preview'; +$string['settingsoverrides'] = 'Settings overrides'; diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index eb9b090de44..8f377d16479 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -1716,7 +1716,7 @@ function quiz_extend_settings_navigation($settings, $quiznode) { $beforekey = $keys[$i + 1]; } - if (has_capability('mod/quiz:manageoverrides', $PAGE->cm->context)) { + if (has_any_capability(['mod/quiz:manageoverrides', 'mod/quiz:viewoverrides'], $PAGE->cm->context)) { $url = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$PAGE->cm->id)); $node = navigation_node::create(get_string('groupoverrides', 'quiz'), new moodle_url($url, array('mode'=>'group')), diff --git a/mod/quiz/overrides.php b/mod/quiz/overrides.php index a42c3d6f96f..6e0e0dbb579 100644 --- a/mod/quiz/overrides.php +++ b/mod/quiz/overrides.php @@ -22,7 +22,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - require_once(__DIR__ . '/../../config.php'); require_once($CFG->dirroot.'/mod/quiz/lib.php'); require_once($CFG->dirroot.'/mod/quiz/locallib.php'); @@ -33,14 +32,17 @@ $cmid = required_param('cmid', PARAM_INT); $mode = optional_param('mode', '', PARAM_ALPHA); // One of 'user' or 'group', default is 'group'. list($course, $cm) = get_course_and_cm_from_cmid($cmid, 'quiz'); -$quiz = $DB->get_record('quiz', array('id' => $cm->instance), '*', MUST_EXIST); +$quiz = $DB->get_record('quiz', ['id' => $cm->instance], '*', MUST_EXIST); require_login($course, false, $cm); $context = context_module::instance($cm->id); // Check the user has the required capabilities to list overrides. -require_capability('mod/quiz:manageoverrides', $context); +$canedit = has_capability('mod/quiz:manageoverrides', $context); +if (!$canedit) { + require_capability('mod/quiz:viewoverrides', $context); +} $quizgroupmode = groups_get_activity_groupmode($cm); $accessallgroups = ($quizgroupmode == NOGROUPS) || has_capability('moodle/site:accessallgroups', $context); @@ -58,16 +60,14 @@ if ($mode != "user" and $mode != "group") { } $groupmode = ($mode == "group"); -$url = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id, 'mode'=>$mode)); +$url = new moodle_url('/mod/quiz/overrides.php', ['cmid' => $cm->id, 'mode' => $mode]); +$title = get_string('overridesforquiz', 'quiz', + format_string($quiz->name, true, ['context' => $context])); $PAGE->set_url($url); - -// Display a list of overrides. $PAGE->set_pagelayout('admin'); -$PAGE->set_title(get_string('overrides', 'quiz')); +$PAGE->set_title($title); $PAGE->set_heading($course->fullname); -echo $OUTPUT->header(); -echo $OUTPUT->heading(format_string($quiz->name, true, array('context' => $context))); // Delete orphaned group overrides. $sql = 'SELECT o.id @@ -76,7 +76,7 @@ $sql = 'SELECT o.id WHERE o.groupid IS NOT NULL AND g.id IS NULL AND o.quiz = ?'; -$params = array($quiz->id); +$params = [$quiz->id]; $orphaned = $DB->get_records_sql($sql, $params); if (!empty($orphaned)) { $DB->delete_records_list('quiz_overrides', 'id', array_keys($orphaned)); @@ -131,16 +131,18 @@ if ($groupmode) { // Initialise table. $table = new html_table(); -$table->headspan = array(1, 2, 1); -$table->colclasses = array('colname', 'colsetting', 'colvalue', 'colaction'); -$table->head = array( - $colname, - get_string('overrides', 'quiz'), - get_string('action'), -); +$table->headspan = [1, 2, 1]; +$table->colclasses = ['colname', 'colsetting', 'colvalue', 'colaction']; +$table->head = [ + $colname, + get_string('overrides', 'quiz'), +]; +if ($canedit) { + $table->head[] = get_string('action'); +} -$userurl = new moodle_url('/user/view.php', array()); -$groupurl = new moodle_url('/group/overview.php', array('id' => $cm->course)); +$userurl = new moodle_url('/user/view.php', []); +$groupurl = new moodle_url('/group/overview.php', ['id' => $cm->course]); $overridedeleteurl = new moodle_url('/mod/quiz/overridedelete.php'); $overrideediturl = new moodle_url('/mod/quiz/overrideedit.php'); @@ -149,11 +151,8 @@ $hasinactive = false; // Whether there are any inactive overrides. foreach ($overrides as $override) { - $fields = array(); - $values = array(); + // Check if this override is active. $active = true; - - // Check for inactive overrides. if (!$groupmode) { if (!has_capability('mod/quiz:attempt', $context, $override->userid)) { // User not allowed to take the quiz. @@ -163,6 +162,13 @@ foreach ($overrides as $override) { $active = false; } } + if (!$active) { + $hasinactive = true; + } + + // Prepare the information about which settings are overridden. + $fields = []; + $values = []; // Format timeopen. if (isset($override->timeopen)) { @@ -170,28 +176,24 @@ foreach ($overrides as $override) { $values[] = $override->timeopen > 0 ? userdate($override->timeopen) : get_string('noopen', 'quiz'); } - // Format timeclose. if (isset($override->timeclose)) { $fields[] = get_string('quizcloses', 'quiz'); $values[] = $override->timeclose > 0 ? userdate($override->timeclose) : get_string('noclose', 'quiz'); } - // Format timelimit. if (isset($override->timelimit)) { $fields[] = get_string('timelimit', 'quiz'); $values[] = $override->timelimit > 0 ? format_time($override->timelimit) : get_string('none', 'quiz'); } - // Format number of attempts. if (isset($override->attempts)) { $fields[] = get_string('attempts', 'quiz'); $values[] = $override->attempts > 0 ? $override->attempts : get_string('unlimited'); } - // Format password. if (isset($override->password)) { $fields[] = get_string('requirepassword', 'quiz'); @@ -199,110 +201,130 @@ foreach ($overrides as $override) { get_string('enabled', 'quiz') : get_string('none', 'quiz'); } - // Icons. - $iconstr = ''; - - // Edit. - $editurlstr = $overrideediturl->out(true, array('id' => $override->id)); - $iconstr = '' . - $OUTPUT->pix_icon('t/edit', get_string('edit')) . ' '; - // Duplicate. - $copyurlstr = $overrideediturl->out(true, - array('id' => $override->id, 'action' => 'duplicate')); - $iconstr .= '' . - $OUTPUT->pix_icon('t/copy', get_string('copy')) . ' '; - // Delete. - $deleteurlstr = $overridedeleteurl->out(true, - array('id' => $override->id, 'sesskey' => sesskey())); - $iconstr .= '' . - $OUTPUT->pix_icon('t/delete', get_string('delete')) . ' '; - + // Prepare the information about who this override applies to. if ($groupmode) { $usergroupstr = '' . $override->name . ''; + ['group' => $override->groupid]) . '" >' . $override->name . ''; } else { $usergroupstr = '' . fullname($override) . ''; + ['id' => $override->userid]) . '" >' . fullname($override) . ''; } - - $class = ''; if (!$active) { - $class = "dimmed_text"; $usergroupstr .= '*'; - $hasinactive = true; } - $usergroupcell = new html_table_cell(); $usergroupcell->rowspan = count($fields); $usergroupcell->text = $usergroupstr; - $actioncell = new html_table_cell(); - $actioncell->rowspan = count($fields); - $actioncell->text = $iconstr; + // Prepare the actions. + if ($canedit) { + // Icons. + $iconstr = ''; + + // Edit. + $editurlstr = $overrideediturl->out(true, ['id' => $override->id]); + $iconstr = '' . + $OUTPUT->pix_icon('t/edit', get_string('edit')) . ' '; + // Duplicate. + $copyurlstr = $overrideediturl->out(true, + ['id' => $override->id, 'action' => 'duplicate']); + $iconstr .= '' . + $OUTPUT->pix_icon('t/copy', get_string('copy')) . ' '; + // Delete. + $deleteurlstr = $overridedeleteurl->out(true, + ['id' => $override->id, 'sesskey' => sesskey()]); + $iconstr .= '' . + $OUTPUT->pix_icon('t/delete', get_string('delete')) . ' '; + + $actioncell = new html_table_cell(); + $actioncell->rowspan = count($fields); + $actioncell->text = $iconstr; + } + + // Add the data to the table. for ($i = 0; $i < count($fields); ++$i) { $row = new html_table_row(); - $row->attributes['class'] = $class; + if (!$active) { + $row->attributes['class'] = 'dimmed_text'; + } + if ($i == 0) { $row->cells[] = $usergroupcell; } - $cell1 = new html_table_cell(); - $cell1->text = $fields[$i]; - $row->cells[] = $cell1; - $cell2 = new html_table_cell(); - $cell2->text = $values[$i]; - $row->cells[] = $cell2; - if ($i == 0) { + + $labelcell = new html_table_cell(); + $labelcell->text = $fields[$i]; + $row->cells[] = $labelcell; + $valuecell = new html_table_cell(); + $valuecell->text = $values[$i]; + $row->cells[] = $valuecell; + + if ($canedit && $i == 0) { $row->cells[] = $actioncell; } + $table->data[] = $row; } } +// Display a list of overrides. +echo $OUTPUT->header(); +echo $OUTPUT->heading($title); + // Output the table and button. -echo html_writer::start_tag('div', array('id' => 'quizoverrides')); +echo html_writer::start_tag('div', ['id' => 'quizoverrides']); if (count($table->data)) { echo html_writer::table($table); +} else { + if ($groupmode) { + echo $OUTPUT->notification(get_string('overridesnoneforgroups', 'quiz'), 'info', false); + } else { + echo $OUTPUT->notification(get_string('overridesnoneforusers', 'quiz'), 'info', false); + } } if ($hasinactive) { - echo $OUTPUT->notification(get_string('inactiveoverridehelp', 'quiz'), 'dimmed_text'); + echo $OUTPUT->notification(get_string('inactiveoverridehelp', 'quiz'), 'info', false); } -echo html_writer::start_tag('div', array('class' => 'buttons')); -$options = array(); -if ($groupmode) { - if (empty($groups)) { - // There are no groups. - echo $OUTPUT->notification(get_string('groupsnone', 'quiz'), 'error'); - $options['disabled'] = true; - } - echo $OUTPUT->single_button($overrideediturl->out(true, - array('action' => 'addgroup', 'cmid' => $cm->id)), - get_string('addnewgroupoverride', 'quiz'), 'post', $options); -} else { - $users = array(); - // See if there are any students in the quiz. - if ($accessallgroups) { - $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id'); - $nousermessage = get_string('usersnone', 'quiz'); - } else if ($groups) { - $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id', '', '', '', array_keys($groups)); - $nousermessage = get_string('usersnone', 'quiz'); +if ($canedit) { + echo html_writer::start_tag('div', ['class' => 'buttons']); + $options = []; + if ($groupmode) { + if (empty($groups)) { + // There are no groups. + echo $OUTPUT->notification(get_string('groupsnone', 'quiz'), 'error'); + $options['disabled'] = true; + } + echo $OUTPUT->single_button($overrideediturl->out(true, + ['action' => 'addgroup', 'cmid' => $cm->id]), + get_string('addnewgroupoverride', 'quiz'), 'post', $options); } else { - $nousermessage = get_string('groupsnone', 'quiz'); - } - $info = new \core_availability\info_module($cm); - $users = $info->filter_user_list($users); + $users = []; + // See if there are any students in the quiz. + if ($accessallgroups) { + $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id'); + $nousermessage = get_string('usersnone', 'quiz'); + } else if ($groups) { + $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id', '', '', '', array_keys($groups)); + $nousermessage = get_string('usersnone', 'quiz'); + } else { + $nousermessage = get_string('groupsnone', 'quiz'); + } + $info = new \core_availability\info_module($cm); + $users = $info->filter_user_list($users); - if (empty($users)) { - // There are no students. - echo $OUTPUT->notification($nousermessage, 'error'); - $options['disabled'] = true; + if (empty($users)) { + // There are no students. + echo $OUTPUT->notification($nousermessage, 'error'); + $options['disabled'] = true; + } + echo $OUTPUT->single_button($overrideediturl->out(true, + ['action' => 'adduser', 'cmid' => $cm->id]), + get_string('addnewuseroverride', 'quiz'), 'get', $options); } - echo $OUTPUT->single_button($overrideediturl->out(true, - array('action' => 'adduser', 'cmid' => $cm->id)), - get_string('addnewuseroverride', 'quiz'), 'get', $options); + echo html_writer::end_tag('div'); } -echo html_writer::end_tag('div'); + echo html_writer::end_tag('div'); // Finish the page. diff --git a/mod/quiz/tests/behat/quiz_group_override.feature b/mod/quiz/tests/behat/quiz_group_override.feature index c20ac9fbffd..04edfc4e83a 100644 --- a/mod/quiz/tests/behat/quiz_group_override.feature +++ b/mod/quiz/tests/behat/quiz_group_override.feature @@ -2,7 +2,7 @@ Feature: Quiz group override In order to grant a group special access to a quiz As a teacher - I need to create an override for thta group. + I need to create an override for that group. Background: Given the following "users" exist: @@ -13,6 +13,7 @@ Feature: Quiz group override | student2 | Sam 2 | Student 2 | student2@example.com | | teacher3 | Terry 3 | Teacher 3 | teacher3@example.com | | student3 | Sam 3 | Student 3 | student3@example.com | + | helper | Exam | Helper | helper@example.com | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | @@ -24,6 +25,7 @@ Feature: Quiz group override | student2 | C1 | student | | teacher3 | C1 | editingteacher | | student3 | C1 | student | + | helper | C1 | teacher | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | @@ -38,6 +40,9 @@ Feature: Quiz group override | teacher2 | G2 | | teacher2 | G3 | | student3 | G3 | + | helper | G1 | + | helper | G2 | + | helper | G3 | And the following "activities" exist: | activity | name | intro | course | idnumber | groupmode | | quiz | Test quiz | Test quiz description | C1 | quiz1 | 1 | @@ -59,37 +64,49 @@ Feature: Quiz group override Then I should see "No groups you can access." And the "Add group override" "button" should be disabled - Scenario: A teacher with accessallgroups permission should see all group overrides - When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "admin" + Scenario: A teacher can create an override + When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "teacher1" And I press "Add group override" And I set the following fields to these values: - | Override group | Group 1 | - | Attempts allowed | 2 | + | Override group | Group 1 | + | Attempts allowed | 2 | And I press "Save and enter another override" And I set the following fields to these values: - | Override group | Group 2 | + | Override group | Group 3 | | Attempts allowed | 2 | And I press "Save" - And I log out - And I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "teacher1" - Then I should see "Group 1" in the ".generaltable" "css_element" - And I should see "Group 2" in the ".generaltable" "css_element" + Then "Group 1" "table_row" should exist + + Scenario: A teacher with accessallgroups permission should see all group overrides + Given the following "mod_quiz > group overrides" exist: + | quiz | group | attempts | + | Test quiz | G1 | 2 | + | Test quiz | G2 | 2 | + When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "teacher1" + Then "Group 1" "table_row" should exist + And "Group 2" "table_row" should exist Scenario: A teacher without accessallgroups permission should only see the group overrides within his/her groups, when the activity's group mode is "separate groups" Given the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | - When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "admin" - And I press "Add group override" - And I set the following fields to these values: - | Override group | Group 1 | - | Attempts allowed | 2 | - And I press "Save and enter another override" - And I set the following fields to these values: - | Override group | Group 2 | - | Attempts allowed | 2 | - And I press "Save" - And I log out + And the following "mod_quiz > group overrides" exist: + | quiz | group | attempts | + | Test quiz | G1 | 2 | + | Test quiz | G2 | 2 | When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "teacher1" - Then I should see "Group 1" in the ".generaltable" "css_element" - And I should not see "Group 2" in the ".generaltable" "css_element" + Then "Group 1" "table_row" should exist + And "Group 2" "table_row" should not exist + + Scenario: A non-editing teacher can see the overrides, but not change them + Given the following "mod_quiz > group overrides" exist: + | quiz | group | attempts | + | Test quiz | G1 | 2 | + | Test quiz | G2 | 2 | + When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "helper" + Then "Group 1" "table_row" should exist + And "Group 2" "table_row" should exist + And "Add group override" "button" should not exist + And "Edit" "link" should not exist in the "Group 1" "table_row" + And "Copy" "link" should not exist in the "Group 1" "table_row" + And "Delete" "link" should not exist in the "Group 1" "table_row" diff --git a/mod/quiz/tests/behat/quiz_user_override.feature b/mod/quiz/tests/behat/quiz_user_override.feature index 408345d8b22..c9e7d905834 100644 --- a/mod/quiz/tests/behat/quiz_user_override.feature +++ b/mod/quiz/tests/behat/quiz_user_override.feature @@ -7,7 +7,8 @@ Feature: Quiz user override Background: Given the following "users" exist: | username | firstname | lastname | email | - | teacher1 | Teacher | One | teacher1@example.com | + | teacher | Teacher | One | teacher@example.com | + | helper | Exam | Helper | helper@example.com | | student1 | Student | One | student1@example.com | | student2 | Student | Two | student2@example.com | And the following "courses" exist: @@ -15,18 +16,17 @@ Feature: Quiz user override | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | - | teacher1 | C1 | editingteacher | + | teacher | C1 | editingteacher | + | helper | C1 | teacher | | student1 | C1 | student | | student2 | C1 | student | - And the following "activities" exist: - | activity | name | intro | course | idnumber | - | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | @javascript Scenario: Add, modify then delete a user override - Given I log in as "teacher1" - And I am on "Course 1" course homepage - When I follow "Quiz 1" + Given the following "activities" exist: + | activity | name | course | idnumber | + | quiz | Test quiz | C1 | quiz1 | + And I am on the "Test quiz" "mod_quiz > View" page logged in as "teacher" And I navigate to "User overrides" in current page administration And I press "Add user override" And I set the following fields to these values: @@ -38,26 +38,24 @@ Feature: Quiz user override | timeclose[hour] | 08 | | timeclose[minute] | 00 | And I press "Save" - And I should see "Wednesday, 1 January 2020, 8:00" - Then I click on "Edit" "link" in the "Student One" "table_row" + Then I should see "Wednesday, 1 January 2020, 8:00" + + And I click on "Edit" "link" in the "Student One" "table_row" And I set the following fields to these values: | timeclose[year] | 2030 | And I press "Save" And I should see "Tuesday, 1 January 2030, 8:00" + And I click on "Delete" "link" And I press "Continue" And I should not see "Student One" @javascript - Scenario: Being able to modify a user override when the quiz is not available to the student - Given I log in as "teacher1" - And I am on "Course 1" course homepage - And I follow "Quiz 1" - And I navigate to "Edit settings" in current page administration - And I expand all fieldsets - And I set the field "Availability" to "Hide from students" - And I click on "Save and display" "button" - When I navigate to "User overrides" in current page administration + Scenario: Can add a user override when the quiz is not available to the student + Given the following "activities" exist: + | activity | name | course | idnumber | visible | + | quiz | Test quiz | C1 | quiz1 | 0 | + When I am on the "Test quiz" "mod_quiz > User overrides" page logged in as "teacher" And I press "Add user override" And I set the following fields to these values: | Override user | Student1 | @@ -77,18 +75,15 @@ Feature: Quiz user override And the following "group members" exist: | user | group | | student1 | G1 | - | teacher1 | G1 | + | teacher | G1 | | student2 | G2 | And the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | And the following "activities" exist: - | activity | name | intro | course | idnumber | groupmode | - | quiz | Quiz 2 | Quiz 2 description | C1 | quiz2 | 1 | - When I log in as "teacher1" - And I am on "Course 1" course homepage - And I follow "Quiz 2" - And I navigate to "User overrides" in current page administration + | activity | name | course | idnumber | groupmode | + | quiz | Test quiz | C1 | quiz1 | 1 | + When I am on the "Test quiz" "mod_quiz > User overrides" page logged in as "teacher" And I press "Add user override" Then the "Override user" select box should contain "Student One, student1@example.com" And the "Override user" select box should not contain "Student Two, student2@example.com" @@ -104,11 +99,25 @@ Feature: Quiz user override | capability | permission | role | contextlevel | reference | | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | And the following "activities" exist: - | activity | name | intro | course | idnumber | groupmode | - | quiz | Quiz 2 | Quiz 2 description | C1 | quiz2 | 1 | - When I log in as "teacher1" - And I am on "Course 1" course homepage - And I follow "Quiz 2" - And I navigate to "User overrides" in current page administration + | activity | name | course | idnumber | groupmode | + | quiz | Test quiz | C1 | quiz1 | 1 | + When I am on the "Test quiz" "mod_quiz > User overrides" page logged in as "teacher" Then I should see "No groups you can access." And the "Add user override" "button" should be disabled + + Scenario: A non-editing teacher can see the overrides, but not change them + Given the following "activities" exist: + | activity | name | course | idnumber | + | quiz | Test quiz | C1 | quiz1 | + And the following "mod_quiz > user overrides" exist: + | quiz | user | attempts | + | Test quiz | student1 | 2 | + | Test quiz | student2 | 2 | + And I am on the "Test quiz" "mod_quiz > View" page logged in as "helper" + When I navigate to "User overrides" in current page administration + Then "Student One" "table_row" should exist + And "Student Two" "table_row" should exist + And "Add user override" "button" should not exist + And "Edit" "link" should not exist in the "Student One" "table_row" + And "Copy" "link" should not exist in the "Student One" "table_row" + And "Delete" "link" should not exist in the "Student One" "table_row" diff --git a/mod/quiz/version.php b/mod/quiz/version.php index ca5d4f5ec72..4ec5a89b202 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021052500; +$plugin->version = 2021052501; $plugin->requires = 2021052500; $plugin->component = 'mod_quiz';