forked from moodle/moodle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MDL-35745 quiz: let teachers to re-open a Never submitted attempt
In the quiz reports, for any 'Never submitted' attempt, there is now a 'Re-open' button next to where it says the attempt state. If that is clicked, there is an 'Are you sure?' pop-up. If the user continues, the attempt is reopened. If the student now has time left, the attempt is put into the In progress state. If there is not time left the attempt is immediately submitted and graded. The 'Are you sure? pop-up says which of those two things will happen.
- Loading branch information
Showing
18 changed files
with
832 additions
and
21 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
/** | ||
* This module has the code to make the Re-open attempt button work, if present. | ||
* | ||
* That is, it looks for buttons with HTML like | ||
* <button type="button" data-action="reopen-attempt" data-attempt-id="227000" data-after-action-url="/mod/quiz/report.php"> | ||
* and if that is clicked, it first shows an 'Are you sure' pop-up, and if they are sure, | ||
* the attempt is re-opened, and then the page reloads. | ||
* | ||
* @module mod_quiz/reopen_attempt_ui | ||
* @copyright 2023 The Open University | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
|
||
import {exception as displayException} from 'core/notification'; | ||
import {call as fetchMany} from 'core/ajax'; | ||
import {get_string as getString} from 'core/str'; | ||
import {saveCancelPromise} from 'core/notification'; | ||
|
||
/** | ||
* Handle a click if it is on one of our buttons. | ||
* | ||
* @param {MouseEvent} e the click event. | ||
*/ | ||
const reopenButtonClicked = async(e) => { | ||
if (!(e.target instanceof HTMLElement) || !e.target.matches('button[data-action="reopen-attempt"]')) { | ||
return; | ||
} | ||
|
||
e.preventDefault(); | ||
const attemptId = e.target.dataset.attemptId; | ||
|
||
try { | ||
// We fetch the confirmation message from the server now, so the message is based | ||
// on the latest state of the attempt, rather than when the containing page loaded. | ||
const messages = fetchMany([{ | ||
methodname: 'mod_quiz_get_reopen_attempt_confirmation', | ||
args: { | ||
"attemptid": attemptId, | ||
}, | ||
}]); | ||
|
||
await saveCancelPromise( | ||
getString('reopenattemptareyousuretitle', 'mod_quiz'), | ||
messages[0], | ||
getString('reopenattempt', 'mod_quiz'), | ||
{triggerElement: e.target}, | ||
); | ||
|
||
await (fetchMany([{ | ||
methodname: 'mod_quiz_reopen_attempt', | ||
args: { | ||
"attemptid": attemptId, | ||
}, | ||
}])[0]); | ||
window.location = M.cfg.wwwroot + e.target.dataset.afterActionUrl; | ||
|
||
} catch (error) { | ||
if (error.type === 'modal-save-cancel:cancel') { | ||
// User clicked Cancel, so do nothing. | ||
return; | ||
} | ||
await displayException(error); | ||
} | ||
}; | ||
|
||
export const init = () => { | ||
document.addEventListener('click', reopenButtonClicked); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace mod_quiz\event; | ||
|
||
use coding_exception; | ||
use core\event\base; | ||
use moodle_url; | ||
|
||
/** | ||
* Event fired when a quiz attempt is reopened. | ||
* | ||
* @property-read array $other { | ||
* Extra information about event. | ||
* | ||
* - int submitterid: id of submitter (null when triggered by CLI script). | ||
* - int quizid: (optional) id of the quiz. | ||
* } | ||
* | ||
* @package mod_quiz | ||
* @since Moodle 4.2 | ||
* @copyright 2023 The Open University | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class attempt_reopened extends base { | ||
|
||
protected function init() { | ||
$this->data['objecttable'] = 'quiz_attempts'; | ||
$this->data['crud'] = 'u'; | ||
$this->data['edulevel'] = self::LEVEL_TEACHING; | ||
} | ||
|
||
public function get_description(): string { | ||
return "The user with id '$this->relateduserid' has had their attempt with id '$this->objectid'" . | ||
"for the quiz with course module id '$this->contextinstanceid' re-opened by the user with id '$this->userid'."; | ||
} | ||
|
||
public static function get_name(): string { | ||
return get_string('eventquizattemptreopened', 'mod_quiz'); | ||
} | ||
|
||
public function get_url(): moodle_url { | ||
return new moodle_url('/mod/quiz/review.php', ['attempt' => $this->objectid]); | ||
} | ||
|
||
protected function validate_data(): void { | ||
parent::validate_data(); | ||
|
||
if (!isset($this->relateduserid)) { | ||
throw new coding_exception('The \'relateduserid\' must be set.'); | ||
} | ||
|
||
if (!array_key_exists('submitterid', $this->other)) { | ||
throw new coding_exception('The \'submitterid\' value must be set in other.'); | ||
} | ||
} | ||
|
||
public static function get_objectid_mapping(): array { | ||
return ['db' => 'quiz_attempts', 'restore' => 'quiz_attempt']; | ||
} | ||
|
||
public static function get_other_mapping(): array { | ||
return [ | ||
'submitterid' => ['db' => 'user', 'restore' => 'user'], | ||
'quizid' => ['db' => 'quiz', 'restore' => 'quiz'], | ||
]; | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
mod/quiz/classes/external/get_reopen_attempt_confirmation.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace mod_quiz\external; | ||
|
||
use core_external\external_api; | ||
use core_external\external_description; | ||
use core_external\external_function_parameters; | ||
use core_external\external_value; | ||
use Exception; | ||
use html_writer; | ||
use mod_quiz\quiz_attempt; | ||
use moodle_exception; | ||
|
||
/** | ||
* Web service to check a quiz attempt state, and return a confirmation message if it can be reopened now. | ||
* | ||
* The use must have the 'mod/quiz:reopenattempts' capability and the attempt | ||
* must (at least for now) be in the 'Never submitted' state (quiz_attempt::ABANDONED). | ||
* | ||
* @package mod_quiz | ||
* @copyright 2023 The Open University | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class get_reopen_attempt_confirmation extends external_api { | ||
|
||
/** | ||
* Declare the method parameters. | ||
* | ||
* @return external_function_parameters | ||
*/ | ||
public static function execute_parameters(): external_function_parameters { | ||
return new external_function_parameters([ | ||
'attemptid' => new external_value(PARAM_INT, 'The id of the attempt to reopen'), | ||
]); | ||
} | ||
|
||
/** | ||
* Check a quiz attempt state, and return a confirmation message method implementation. | ||
* | ||
* @param int $attemptid the id of the attempt to reopen. | ||
* @return string a suitable confirmation message (HTML), if the attempt is suitable to be reopened. | ||
* @throws Exception an appropriate exception if the attempt cannot be reopened now. | ||
*/ | ||
public static function execute(int $attemptid): string { | ||
global $DB; | ||
['attemptid' => $attemptid] = self::validate_parameters( | ||
self::execute_parameters(), ['attemptid' => $attemptid]); | ||
|
||
// Check the request is valid. | ||
$attemptobj = quiz_attempt::create($attemptid); | ||
require_capability('mod/quiz:reopenattempts', $attemptobj->get_context()); | ||
self::validate_context($attemptobj->get_context()); | ||
if ($attemptobj->get_state() != quiz_attempt::ABANDONED) { | ||
throw new moodle_exception('reopenattemptwrongstate', 'quiz', '', | ||
['attemptid' => $attemptid, 'state' => quiz_attempt_state_name($attemptobj->get_state())]); | ||
} | ||
|
||
// Work out what the affect or re-opening will be. | ||
$timestamp = time(); | ||
$timeclose = $attemptobj->get_access_manager(time())->get_end_time($attemptobj->get_attempt()); | ||
if ($timeclose && $timestamp > $timeclose) { | ||
$expectedoutcome = get_string('reopenedattemptwillbesubmitted', 'quiz'); | ||
} else if ($timeclose) { | ||
$expectedoutcome = get_string('reopenedattemptwillbeinprogressuntil', 'quiz', userdate($timeclose)); | ||
} else { | ||
$expectedoutcome = get_string('reopenedattemptwillbeinprogress', 'quiz'); | ||
} | ||
|
||
// Return the required message. | ||
$user = $DB->get_record('user', ['id' => $attemptobj->get_userid()], '*', MUST_EXIST); | ||
return html_writer::tag('p', get_string('reopenattemptareyousuremessage', 'quiz', | ||
['attemptnumber' => $attemptobj->get_attempt_number(), 'attemptuser' => s(fullname($user))])) . | ||
html_writer::tag('p', $expectedoutcome); | ||
} | ||
|
||
/** | ||
* Define the webservice response. | ||
* | ||
* @return external_description | ||
*/ | ||
public static function execute_returns(): external_description { | ||
return new external_value(PARAM_RAW, 'Confirmation to show the user before the attempt is reopened.'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
namespace mod_quiz\external; | ||
|
||
use core_external\external_api; | ||
use core_external\external_description; | ||
use core_external\external_function_parameters; | ||
use core_external\external_value; | ||
use mod_quiz\quiz_attempt; | ||
use moodle_exception; | ||
|
||
/** | ||
* Web service method for re-opening a quiz attempt. | ||
* | ||
* The use must have the 'mod/quiz:reopenattempts' capability and the attempt | ||
* must (at least for now) be in the 'Never submitted' state (quiz_attempt::ABANDONED). | ||
* | ||
* @package mod_quiz | ||
* @copyright 2023 The Open University | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class reopen_attempt extends external_api { | ||
|
||
/** | ||
* Declare the method parameters. | ||
* | ||
* @return external_function_parameters | ||
*/ | ||
public static function execute_parameters(): external_function_parameters { | ||
return new external_function_parameters([ | ||
'attemptid' => new external_value(PARAM_INT, 'The id of the attempt to reopen'), | ||
]); | ||
} | ||
|
||
/** | ||
* Re-opening a submitted attempt method implementation. | ||
* | ||
* @param int $attemptid the id of the attempt to reopen. | ||
*/ | ||
public static function execute(int $attemptid): void { | ||
['attemptid' => $attemptid] = self::validate_parameters( | ||
self::execute_parameters(), ['attemptid' => $attemptid]); | ||
|
||
// Check the request is valid. | ||
$attemptobj = quiz_attempt::create($attemptid); | ||
require_capability('mod/quiz:reopenattempts', $attemptobj->get_context()); | ||
self::validate_context($attemptobj->get_context()); | ||
if ($attemptobj->get_state() != quiz_attempt::ABANDONED) { | ||
throw new moodle_exception('reopenattemptwrongstate', 'quiz', '', | ||
['attemptid' => $attemptid, 'state' => quiz_attempt_state_name($attemptobj->get_state())]); | ||
} | ||
|
||
// Re-open the attempt. | ||
$attemptobj->process_reopen_abandoned(time()); | ||
} | ||
|
||
/** | ||
* Define the webservice response. | ||
* | ||
* @return external_description|null always null. | ||
*/ | ||
public static function execute_returns(): ?external_description { | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.