quiz: MDL-16478 Allow different open/close dates, etc. for individual students or groups.

This was implemented by Matt Petro of the University of Wisconsin - Madison Engineering
School and Math Department. Many thanks. Reviewed by and committed by Tim Hunt.

This adds a new Overrides tab to the UI, with sub-tabs Group overrides and User overrides.
Each of those lists all the overrides that currently exist, and lets you manage them and
create more.

When a quiz is being attempted, the override that applies to the current user is combined
with the current quiz settings loaded from the quiz table (normally called $quiz).
If there are both user and group overrides, then just the specific user override is used (more specific).
If the user is in several groups, then the overrides are combined to give the most permissive set of options.

There is one new database table quiz_overrides, to store the overrides.
This commit is contained in:
Tim Hunt 2010-03-08 16:01:38 +00:00
parent cdede6fbfe
commit 990650f94c
25 changed files with 1309 additions and 127 deletions

View file

@ -26,8 +26,10 @@ $string['addingrandomsamatch'] = 'Adding a Random Short-Answer Matching question
$string['addingshortanswer'] = 'Adding a Short-Answer question'; $string['addingshortanswer'] = 'Adding a Short-Answer question';
$string['addingtruefalse'] = 'Adding a True/False question'; $string['addingtruefalse'] = 'Adding a True/False question';
$string['addmoreoverallfeedbacks'] = 'Add {no} more feedback fields'; $string['addmoreoverallfeedbacks'] = 'Add {no} more feedback fields';
$string['addnewgroupoverride'] = 'Add group override';
$string['addnewpagesafterselected'] = 'Add new pages after selected questions'; $string['addnewpagesafterselected'] = 'Add new pages after selected questions';
$string['addnewquestionsqbank'] = 'Add questions to the category $a->catname: $a->link'; $string['addnewquestionsqbank'] = 'Add questions to the category $a->catname: $a->link';
$string['addnewuseroverride'] = 'Add user override';
$string['addpagehere'] = 'Add page here'; $string['addpagehere'] = 'Add page here';
$string['addquestion'] = 'Add question'; $string['addquestion'] = 'Add question';
$string['addquestions'] = 'Add questions'; $string['addquestions'] = 'Add questions';
@ -220,6 +222,7 @@ $string['deletequestionscheck'] = 'Are you absolutely sure you want to delete th
$string['deleteselected'] = 'Delete selected'; $string['deleteselected'] = 'Delete selected';
$string['deletingquestionattempts'] = 'Deleting question attempts'; $string['deletingquestionattempts'] = 'Deleting question attempts';
$string['description'] = 'Description'; $string['description'] = 'Description';
$string['disabled'] = 'Disabled';
$string['discrimination'] = 'Discrim. Index'; $string['discrimination'] = 'Discrim. Index';
$string['displayoptions'] = 'Display options'; $string['displayoptions'] = 'Display options';
$string['download'] = 'Click to download the exported category file'; $string['download'] = 'Click to download the exported category file';
@ -244,6 +247,7 @@ $string['editingrandomsamatch'] = 'Editing a Random Short-Answer Matching questi
$string['editingrqp'] = '$a: editing a question'; $string['editingrqp'] = '$a: editing a question';
$string['editingshortanswer'] = 'Editing a Short-Answer question'; $string['editingshortanswer'] = 'Editing a Short-Answer question';
$string['editingtruefalse'] = 'Editing a True/False question'; $string['editingtruefalse'] = 'Editing a True/False question';
$string['editoverride'] = 'Edit override';
$string['editqcats'] = 'Edit questions categories'; $string['editqcats'] = 'Edit questions categories';
$string['editquestions'] = 'Edit questions'; $string['editquestions'] = 'Edit questions';
$string['editquiz'] = 'Edit Quiz'; $string['editquiz'] = 'Edit Quiz';
@ -268,6 +272,7 @@ in course \'$a->coursename\'
You can review this attempt at $a->quizreviewurl.'; You can review this attempt at $a->quizreviewurl.';
$string['emailnotifysubject'] = '$a->studentname has completed quiz $a->quizname'; $string['emailnotifysubject'] = '$a->studentname has completed quiz $a->quizname';
$string['empty'] = 'Empty'; $string['empty'] = 'Empty';
$string['enabled'] = 'Enabled';
$string['endtest'] = 'End test ...'; $string['endtest'] = 'End test ...';
$string['erroraccessingreport'] = 'You cannot access this report'; $string['erroraccessingreport'] = 'You cannot access this report';
$string['errorinquestion'] = 'Error in question'; $string['errorinquestion'] = 'Error in question';
@ -350,6 +355,7 @@ $string['gradingdetailsadjustment'] = 'With previous penalties this gives <stron
$string['gradingdetailspenalty'] = 'This submission attracted a penalty of $a.'; $string['gradingdetailspenalty'] = 'This submission attracted a penalty of $a.';
$string['gradingdetailszeropenalty'] = 'You were not penalized for this submission.'; $string['gradingdetailszeropenalty'] = 'You were not penalized for this submission.';
$string['gradingmethod'] = 'Grading method: $a'; $string['gradingmethod'] = 'Grading method: $a';
$string['groupoverrides'] = 'Group overrides';
$string['guestsno'] = 'Sorry, guests cannot see or attempt quizzes'; $string['guestsno'] = 'Sorry, guests cannot see or attempt quizzes';
$string['hidebreaks'] = 'Hide page breaks'; $string['hidebreaks'] = 'Hide page breaks';
$string['hidereordertool'] = 'Hide the reordering tool'; $string['hidereordertool'] = 'Hide the reordering tool';
@ -380,6 +386,7 @@ $string['invalidattemptid'] = 'No such attempt ID exists';
$string['invalidcategory'] = 'Category ID is invalid'; $string['invalidcategory'] = 'Category ID is invalid';
$string['invalidnumericanswer'] = 'One of the answers you entered was not a valid number.'; $string['invalidnumericanswer'] = 'One of the answers you entered was not a valid number.';
$string['invalidnumerictolerance'] = 'One of the tolerances you entered was not a valid number.'; $string['invalidnumerictolerance'] = 'One of the tolerances you entered was not a valid number.';
$string['invalidoverrideid'] = 'Invalid override id';
$string['invalidquestionid'] = 'Invalid question id'; $string['invalidquestionid'] = 'Invalid question id';
$string['invalidquizid'] = 'Invalid Quiz ID'; $string['invalidquizid'] = 'Invalid Quiz ID';
$string['invalidsource'] = 'The source is not accepted as valid.'; $string['invalidsource'] = 'The source is not accepted as valid.';
@ -444,6 +451,7 @@ $string['noanswers'] = 'No answers were selected!';
$string['noattempts'] = 'No attempts have been made on this quiz'; $string['noattempts'] = 'No attempts have been made on this quiz';
$string['noattemptstoshow'] = 'There are no attempts to show'; $string['noattemptstoshow'] = 'There are no attempts to show';
$string['nocategory'] = 'Incorrect or no category specified'; $string['nocategory'] = 'Incorrect or no category specified';
$string['noclose'] = 'No close date';
$string['nocommentsyet'] = 'No comments yet.'; $string['nocommentsyet'] = 'No comments yet.';
$string['noconnection'] = 'There is currently no connection to a web service that can process this question. Please contact your administrator'; $string['noconnection'] = 'There is currently no connection to a web service that can process this question. Please contact your administrator';
$string['nodataset'] = 'nothing - it is not a wild card'; $string['nodataset'] = 'nothing - it is not a wild card';
@ -454,6 +462,8 @@ $string['nomatchinganswer'] = 'You must specify an answer matching the question
$string['nominal'] = 'Nominal'; $string['nominal'] = 'Nominal';
$string['nomoreattempts'] = 'No more attempts are allowed'; $string['nomoreattempts'] = 'No more attempts are allowed';
$string['none'] = 'None'; $string['none'] = 'None';
$string['noopen'] = 'No open date';
$string['nooverridedata'] = 'You must override at least one of the quiz settings.';
$string['nopossibledatasets'] = 'No possible datasets'; $string['nopossibledatasets'] = 'No possible datasets';
$string['noquestionintext'] = 'The question text does not contain any embedded questions'; $string['noquestionintext'] = 'The question text does not contain any embedded questions';
$string['noquestions'] = 'No questions have been added yet'; $string['noquestions'] = 'No questions have been added yet';
@ -492,6 +502,14 @@ $string['outofpercent'] = '$a->grade out of a maximum of $a->maxgrade ($a->perce
$string['outofshort'] = '$a->grade/$a->maxgrade'; $string['outofshort'] = '$a->grade/$a->maxgrade';
$string['overallfeedback'] = 'Overall feedback'; $string['overallfeedback'] = 'Overall feedback';
$string['overdue'] = 'Overdue'; $string['overdue'] = 'Overdue';
$string['overridedeletegroupsure'] = 'Are you sure you want to delete the override for group $a?';
$string['overridedeleteusersure'] = 'Are you sure you want to delete the override for user $a?';
$string['override'] = 'Override';
$string['overrides'] = 'Overrides';
$string['overridegroup'] = 'Override group';
$string['overrideuser'] = 'Override user';
$string['overrideusereventname'] = '$a->quiz - Override';
$string['overridegroupeventname'] = '$a->quiz - $a->group';
$string['pagesize'] = 'Attempts shown per page:'; $string['pagesize'] = 'Attempts shown per page:';
$string['paragraphquestion'] = 'Paragraph Question not supported at line $a. The question will be ignored'; $string['paragraphquestion'] = 'Paragraph Question not supported at line $a. The question will be ignored';
$string['parent'] = 'Parent'; $string['parent'] = 'Parent';
@ -552,6 +570,7 @@ $string['quiz:emailnotifysubmission'] = 'Get email notification of submissions';
$string['quiz:grade'] = 'Grade quizzes manually'; $string['quiz:grade'] = 'Grade quizzes manually';
$string['quiz:ignoretimelimits'] = 'Ignores time limit on quizzes'; $string['quiz:ignoretimelimits'] = 'Ignores time limit on quizzes';
$string['quiz:manage'] = 'Manage quizzes'; $string['quiz:manage'] = 'Manage quizzes';
$string['quiz:manageoverrides'] = 'Manage quiz overrides';
$string['quiz:preview'] = 'Preview quizzes'; $string['quiz:preview'] = 'Preview quizzes';
$string['quiz:regrade'] = 'Regrade quiz attempts'; $string['quiz:regrade'] = 'Regrade quiz attempts';
$string['quiz:reviewmyattempts'] = 'Review your own attempts'; $string['quiz:reviewmyattempts'] = 'Review your own attempts';
@ -632,6 +651,7 @@ $string['response'] = 'Response';
$string['responses'] = 'Responses'; $string['responses'] = 'Responses';
$string['results'] = 'Results'; $string['results'] = 'Results';
$string['reuseifpossible'] = 'reuse previously removed'; $string['reuseifpossible'] = 'reuse previously removed';
$string['reverttodefaults'] = 'Revert to quiz defaults';
$string['review'] = 'Review'; $string['review'] = 'Review';
$string['reviewafter'] = 'Allow review after quiz is closed'; $string['reviewafter'] = 'Allow review after quiz is closed';
$string['reviewalways'] = 'Allow review at any time'; $string['reviewalways'] = 'Allow review at any time';
@ -659,6 +679,7 @@ $string['savedfromdeletedcourse'] = 'Saved from deleted course \"$a\"';
$string['savegrades'] = 'Save grades'; $string['savegrades'] = 'Save grades';
$string['savemyanswers'] = 'Save my answers'; $string['savemyanswers'] = 'Save my answers';
$string['savenosubmit'] = 'Save without submitting'; $string['savenosubmit'] = 'Save without submitting';
$string['saveoverrideandstay'] = 'Save and enter another override';
$string['savequiz'] = 'Save this whole quiz'; $string['savequiz'] = 'Save this whole quiz';
$string['score'] = 'Raw score'; $string['score'] = 'Raw score';
$string['scores'] = 'Scores'; $string['scores'] = 'Scores';
@ -752,6 +773,7 @@ $string['updatesettings'] = 'Update quiz settings';
$string['upgradesure'] = '<div>In particular the quiz module will perform an extensive change of the quiz tables and this upgrade has not yet been sufficiently tested. You are very strongly urged to backup your database tables before proceeding.</div>'; $string['upgradesure'] = '<div>In particular the quiz module will perform an extensive change of the quiz tables and this upgrade has not yet been sufficiently tested. You are very strongly urged to backup your database tables before proceeding.</div>';
$string['url'] = 'URL'; $string['url'] = 'URL';
$string['usedcategorymoved'] = 'This category has been preserved and moved to the site level because it is a published category still in use by other courses.'; $string['usedcategorymoved'] = 'This category has been preserved and moved to the site level because it is a published category still in use by other courses.';
$string['useroverrides'] = 'User overrides';
$string['validate'] = 'Validate'; $string['validate'] = 'Validate';
$string['viewallanswers'] = 'View $a quiz attempts'; $string['viewallanswers'] = 'View $a quiz attempts';
$string['viewallreports'] = 'View reports for $a attempts'; $string['viewallreports'] = 'View reports for $a attempts';

View file

@ -626,7 +626,19 @@ class password_access_rule extends quiz_access_rule_base {
/// If they entered the right password, let them in. /// If they entered the right password, let them in.
$enteredpassword = optional_param('quizpassword', '', PARAM_RAW); $enteredpassword = optional_param('quizpassword', '', PARAM_RAW);
$validpassword = false;
if (strcmp($this->_quiz->password, $enteredpassword) === 0) { if (strcmp($this->_quiz->password, $enteredpassword) === 0) {
$validpassword = true;
} else if (isset($this->_quiz->extrapasswords)) {
// group overrides may have additional passwords
foreach ($this->_quiz->extrapasswords as $password) {
if (strcmp($password, $enteredpassword) === 0) {
$validpassword = true;
break;
}
}
}
if ($validpassword) {
$SESSION->passwordcheckedquizzes[$this->_quiz->id] = true; $SESSION->passwordcheckedquizzes[$this->_quiz->id] = true;
return; return;
} }

View file

@ -33,7 +33,7 @@
} }
$PAGE->set_url($url); $PAGE->set_url($url);
$attemptobj = new quiz_attempt($attemptid); $attemptobj = quiz_attempt::create($attemptid);
/// Check login. /// Check login.
require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());

View file

@ -90,6 +90,32 @@ class quiz {
$this->determine_layout(); $this->determine_layout();
} }
/**
* Static function to create a new quiz object for a specific user.
*
* @param integer $quizid the the quiz id.
* @param integer $userid the the userid.
* @return object the new quiz object
*/
static public function create($quizid, $userid) {
global $DB;
if (!$quiz = $DB->get_record('quiz', array('id' => $quizid))) {
throw new moodle_exception('invalidquizid', 'quiz');
}
if (!$course = $DB->get_record('course', array('id' => $quiz->course))) {
throw new moodle_exception('invalidcoursemodule');
}
if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) {
throw new moodle_exception('invalidcoursemodule');
}
// Update quiz with override information
$quiz = quiz_update_effective_access($quiz, $userid);
return new quiz($quiz, $cm, $course);
}
// Functions for loading more data ===================================================== // Functions for loading more data =====================================================
/** /**
* Convenience method. Calls {@link load_questions()} with the list of * Convenience method. Calls {@link load_questions()} with the list of
@ -418,17 +444,33 @@ class quiz_attempt extends quiz {
// Constructor ========================================================================= // Constructor =========================================================================
/** /**
* Constructor from just an attemptid. * Constructor assuming we already have the necessary data loaded.
* *
* @param integer $attemptid the id of the attempt to load. We automatically load the * @param object $attempt the row of the quiz_attempts table.
* associated quiz, course, etc. * @param object $quiz the quiz object for this attempt and user.
* @param object $cm the course_module object for this quiz.
* @param object $course the row from the course table for the course we belong to.
*/ */
function __construct($attemptid) { function __construct($attempt, $quiz, $cm, $course) {
$this->attempt = $attempt;
parent::__construct($quiz, $cm, $course);
$this->preload_questions();
$this->preload_question_states();
}
/**
* Static function to create a new quiz_attempt object given an attemptid.
*
* @param integer $attemptid the attempt id.
* @return object the new quiz_attempt object
*/
static public function create($attemptid) {
global $DB; global $DB;
if (!$this->attempt = quiz_load_attempt($attemptid)) {
if (!$attempt = quiz_load_attempt($attemptid)) {
throw new moodle_exception('invalidattemptid', 'quiz'); throw new moodle_exception('invalidattemptid', 'quiz');
} }
if (!$quiz = $DB->get_record('quiz', array('id' => $this->attempt->quiz))) { if (!$quiz = $DB->get_record('quiz', array('id' => $attempt->quiz))) {
throw new moodle_exception('invalidquizid', 'quiz'); throw new moodle_exception('invalidquizid', 'quiz');
} }
if (!$course = $DB->get_record('course', array('id' => $quiz->course))) { if (!$course = $DB->get_record('course', array('id' => $quiz->course))) {
@ -437,9 +479,10 @@ class quiz_attempt extends quiz {
if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) { if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) {
throw new moodle_exception('invalidcoursemodule'); throw new moodle_exception('invalidcoursemodule');
} }
parent::__construct($quiz, $cm, $course); // Update quiz with override information
$this->preload_questions(); $quiz = quiz_update_effective_access($quiz, $attempt->userid);
$this->preload_question_states();
return new quiz_attempt($attempt, $quiz, $cm, $course);
} }
// Functions for loading more data ===================================================== // Functions for loading more data =====================================================

View file

@ -279,6 +279,8 @@
$status = backup_quiz_question_instances($bf,$preferences,$quiz->id); $status = backup_quiz_question_instances($bf,$preferences,$quiz->id);
//Now we print to xml quiz_feedback (Course Level) //Now we print to xml quiz_feedback (Course Level)
$status = backup_quiz_feedback($bf,$preferences,$quiz->id); $status = backup_quiz_feedback($bf,$preferences,$quiz->id);
//Now we print to xml quiz_overrides (Course Level)
$status = backup_quiz_overrides($bf,$preferences,$quiz->id);
//if we've selected to backup users info, then execute: //if we've selected to backup users info, then execute:
// - backup_quiz_grades // - backup_quiz_grades
// - backup_quiz_attempts // - backup_quiz_attempts
@ -341,6 +343,42 @@
return $status; return $status;
} }
//Backup quiz_overrides contents (executed from quiz_backup_mods)
function backup_quiz_overrides ($bf,$preferences,$quiz) {
global $DB;
$status = true;
$douserdata = backup_userdata_selected($preferences,'quiz',$quiz);
$quiz_overrides = $DB->get_records('quiz_overrides',array('quiz' =>$quiz),'id');
//If there are quiz_overrides
if ($quiz_overrides) {
//Write start tag
$status = fwrite ($bf,start_tag("OVERRIDES",4,true));
//Iterate over each quiz_override
foreach ($quiz_overrides as $quiz_override) {
// Only backup user overrides if we are backing up user data
if ($douserdata || $quiz_override->userid == 0) {
//Start quiz override
$status = fwrite ($bf,start_tag("OVERRIDE",5,true));
//Print quiz_override contents
fwrite ($bf,full_tag("ID",6,false,$quiz_override->id));
fwrite ($bf,full_tag("USERID",6,false,$quiz_override->userid));
fwrite ($bf,full_tag("GROUPID",6,false,$quiz_override->groupid));
fwrite ($bf,full_tag("TIMEOPEN",6,false,$quiz_override->timeopen));
fwrite ($bf,full_tag("TIMECLOSE",6,false,$quiz_override->timeclose));
fwrite ($bf,full_tag("TIMELIMIT",6,false,$quiz_override->timelimit));
fwrite ($bf,full_tag("ATTEMPTS",6,false,$quiz_override->attempts));
fwrite ($bf,full_tag("PASSWORD",6,false,$quiz_override->password));
//End quiz override
$status = fwrite ($bf,end_tag("OVERRIDE",5,true));
}
}
//Write end tag
$status = fwrite ($bf,end_tag("OVERRIDES",4,true));
}
return $status;
}
//Backup quiz_question_instances contents (executed from quiz_backup_mods) //Backup quiz_question_instances contents (executed from quiz_backup_mods)
function backup_quiz_feedback ($bf,$preferences,$quiz) { function backup_quiz_feedback ($bf,$preferences,$quiz) {
global $DB; global $DB;

View file

@ -15,7 +15,7 @@
$PAGE->set_url('/mod/quiz/comment.php', array('attempt'=>$attemptid, 'question'=>$questionid)); $PAGE->set_url('/mod/quiz/comment.php', array('attempt'=>$attemptid, 'question'=>$questionid));
$attemptobj = new quiz_attempt($attemptid); $attemptobj = quiz_attempt::create($attemptid);
/// Can only grade finished attempts. /// Can only grade finished attempts.
if (!$attemptobj->is_finished()) { if (!$attemptobj->is_finished()) {

View file

@ -52,6 +52,16 @@ $capabilities = array(
) )
), ),
// Edit the quiz overrides
'mod/quiz:manageoverrides' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'legacy' => array(
'editingteacher' => CAP_ALLOW,
'admin' => CAP_ALLOW
)
),
// Preview the quiz. // Preview the quiz.
'mod/quiz:preview' => array( 'mod/quiz:preview' => array(
'captype' => 'write', // Only just a write. 'captype' => 'write', // Only just a write.

View file

@ -21,6 +21,8 @@ function xmldb_quiz_install() {
update_log_display_entry('quiz', 'start attempt', 'quiz', 'name'); update_log_display_entry('quiz', 'start attempt', 'quiz', 'name');
update_log_display_entry('quiz', 'close attempt', 'quiz', 'name'); update_log_display_entry('quiz', 'close attempt', 'quiz', 'name');
update_log_display_entry('quiz', 'continue attempt', 'quiz', 'name'); update_log_display_entry('quiz', 'continue attempt', 'quiz', 'name');
update_log_display_entry('quiz', 'edit override', 'quiz', 'name');
update_log_display_entry('quiz', 'delete override', 'quiz', 'name');
$record = new object(); $record = new object();
$record->name = 'overview'; $record->name = 'overview';

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/quiz/db" VERSION="20090420" COMMENT="XMLDB file for Moodle mod/quiz" <XMLDB PATH="mod/quiz/db" VERSION="20100220" COMMENT="XMLDB file for Moodle mod/quiz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd" xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
> >
@ -109,7 +109,7 @@
<KEY NAME="quizid" TYPE="foreign" FIELDS="quizid" REFTABLE="quiz" REFFIELDS="id" PREVIOUS="primary"/> <KEY NAME="quizid" TYPE="foreign" FIELDS="quizid" REFTABLE="quiz" REFFIELDS="id" PREVIOUS="primary"/>
</KEYS> </KEYS>
</TABLE> </TABLE>
<TABLE NAME="quiz_report" COMMENT="Lists all the installed quiz reports and their display order and so on. No need to worry about deleting old records. Only records with an equivalent directory are displayed." PREVIOUS="quiz_feedback"> <TABLE NAME="quiz_report" COMMENT="Lists all the installed quiz reports and their display order and so on. No need to worry about deleting old records. Only records with an equivalent directory are displayed." PREVIOUS="quiz_feedback" NEXT="quiz_overrides">
<FIELDS> <FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="name"/> <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="name"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="name of the report, same as the directory name" PREVIOUS="id" NEXT="displayorder"/> <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="name of the report, same as the directory name" PREVIOUS="id" NEXT="displayorder"/>
@ -122,5 +122,24 @@
<KEY NAME="primary" TYPE="primary" FIELDS="id"/> <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS> </KEYS>
</TABLE> </TABLE>
<TABLE NAME="quiz_overrides" COMMENT="The overrides to quiz settings on a per-user and per-group basis." PREVIOUS="quiz_report">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="quiz"/>
<FIELD NAME="quiz" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id" PREVIOUS="id" NEXT="groupid"/>
<FIELD NAME="groupid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Foreign key references groups.id. Can be null if this is a per-user override." PREVIOUS="quiz" NEXT="userid"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Foreign key references user.id. Can be null if this is a per-group override." PREVIOUS="groupid" NEXT="timeopen"/>
<FIELD NAME="timeopen" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Time at which students may start attempting this quiz. Can be null, in which case the quiz default is used." PREVIOUS="userid" NEXT="timeclose"/>
<FIELD NAME="timeclose" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Time by which students must have completed their attempt. Can be null, in which case the quiz default is used." PREVIOUS="timeopen" NEXT="timelimit"/>
<FIELD NAME="timelimit" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Time limit in seconds. Can be null, in which case the quiz default is used." PREVIOUS="timeclose" NEXT="attempts"/>
<FIELD NAME="attempts" TYPE="int" LENGTH="6" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="timelimit" NEXT="password"/>
<FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Quiz password. Can be null, in which case the quiz default is used." PREVIOUS="attempts"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="quiz"/>
<KEY NAME="quiz" TYPE="foreign" FIELDS="quiz" REFTABLE="quiz" REFFIELDS="id" PREVIOUS="primary" NEXT="groupid"/>
<KEY NAME="groupid" TYPE="foreign" FIELDS="groupid" REFTABLE="groups" REFFIELDS="id" PREVIOUS="quiz" NEXT="userid"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id" PREVIOUS="groupid"/>
</KEYS>
</TABLE>
</TABLES> </TABLES>
</XMLDB> </XMLDB>

View file

@ -292,6 +292,40 @@ function xmldb_quiz_upgrade($oldversion) {
upgrade_mod_savepoint($result, 2009042000, 'quiz'); upgrade_mod_savepoint($result, 2009042000, 'quiz');
} }
if ($result && $oldversion < 2010030501) {
/// fix log actions
update_log_display_entry('quiz', 'edit override', 'quiz', 'name');
update_log_display_entry('quiz', 'delete override', 'quiz', 'name');
/// Define table quiz_overrides to be created
$table = new xmldb_table('quiz_overrides');
/// Adding fields to table quiz_overrides
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('quiz', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
$table->add_field('groupid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
$table->add_field('timeopen', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
$table->add_field('timeclose', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
$table->add_field('timelimit', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
$table->add_field('attempts', XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, null, null, null);
$table->add_field('password', XMLDB_TYPE_CHAR, '255', null, null, null, null);
/// Adding keys to table quiz_overrides
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('quiz', XMLDB_KEY_FOREIGN, array('quiz'), 'quiz', array('id'));
$table->add_key('groupid', XMLDB_KEY_FOREIGN, array('groupid'), 'groups', array('id'));
$table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
/// Conditionally launch create table for quiz_overrides
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
/// quiz savepoint reached
upgrade_mod_savepoint($result, 2010030501, 'quiz');
}
return $result; return $result;
} }

View file

@ -179,6 +179,7 @@ function quiz_delete_instance($id) {
} }
quiz_delete_all_attempts($quiz); quiz_delete_all_attempts($quiz);
quiz_delete_all_overrides($quiz);
$DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id)); $DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id));
$DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
@ -195,6 +196,147 @@ function quiz_delete_instance($id) {
return true; return true;
} }
/**
* Deletes a quiz override from the database and clears any corresponding calendar events
*
* @param object $quiz The quiz object.
* @param integer $overrideid The id of the override being deleted
* @return bool true on success
*/
function quiz_delete_override($quiz, $overrideid) {
global $DB;
if (!$override = $DB->get_record('quiz_overrides', array('id' => $overrideid))) {
return false;
}
$groupid = empty($override->groupid)? 0 : $override->groupid;
$userid = empty($override->userid)? 0 : $override->userid;
// Delete the events
$events = $DB->get_records('event', array('modulename'=>'quiz', 'instance'=>$quiz->id, 'groupid'=>$groupid, 'userid'=>$userid));
foreach($events as $event) {
$eventold = calendar_event::load($event);
$eventold->delete();
}
$DB->delete_records('quiz_overrides', array('id' => $overrideid));
return true;
}
/**
* Deletes all quiz overrides from the database and clears any corresponding calendar events
*
* @param object $quiz The quiz object.
*/
function quiz_delete_all_overrides($quiz) {
global $DB;
$overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id), 'id');
foreach ($overrides as $override) {
quiz_delete_override($quiz, $override->id);
}
}
/**
* Updates a quiz object with override information for a user.
*
* Algorithm: For each quiz setting, if there is a matching user-specific override,
* then use that otherwise, if there are group-specific overrides, return the most
* lenient combination of them. If neither applies, leave the quiz setting unchanged.
*
* Special case: if there is more than one password that applies to the user, then
* quiz->extrapasswords will contain an array of strings giving the remaining
* passwords.
*
* @param object $quiz The quiz object.
* @param integer $userid The userid.
* @return object $quiz The updated quiz object.
*/
function quiz_update_effective_access($quiz, $userid) {
global $DB;
// check for user override
$override = $DB->get_record('quiz_overrides', array('quiz' => $quiz->id, 'userid' => $userid));
if (!$override) {
$override = new stdclass;
$override->timeopen = null;
$override->timeclose = null;
$override->timelimit = null;
$override->attempts = null;
$override->password = null;
}
// check for group overrides
$groupings = groups_get_user_groups($quiz->course, $userid);
$groupingid = empty($cm->groupingid)? 0 : $cm->groupingid;
if (!empty($groupings[$groupingid])) {
// Select all overrides that apply to the User's groups
list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[$groupingid]));
$sql = "SELECT * FROM {quiz_overrides}
WHERE groupid $extra AND quiz = ?";
$params[] = $qiuz->id;
$records = $DB->get_records_sql($sql, $params);
// Combine the overrides
$opens = array();
$closes = array();
$limits = array();
$attempts = array();
$passwords = array();
foreach ($records as $gpoverride) {
if (isset($gpoverride->timeopen)) {
$opens[] = $gpoverride->timeopen;
}
if (isset($gpoverride->timeclose)) {
$closes[] = $gpoverride->timeclose;
}
if (isset($gpoverride->timelimit)) {
$limits[] = $gpoverride->timelimit;
}
if (isset($gpoverride->attempts)) {
$attempts[] = $gpoverride->attempts;
}
if (isset($gpoverride->password)) {
$passwords[] = $gpoverride->password;
}
}
// If there is a user override for a setting, ignore the group override
if (is_null($override->timeopen) && count($opens)) {
$override->timeopen = min($opens);
}
if (is_null($override->timeclose) && count($closes)) {
$override->timeclose = max($closes);
}
if (is_null($override->timelimit) && count($limits)) {
$override->timelimit = max($limits);
}
if (is_null($override->attempts) && count($attempts)) {
$override->attempts = max($attempts);
}
if (is_null($override->password) && count($passwords)) {
$override->password = array_shift($passwords);
if (count($passwords)) {
$override->extrapasswords = $passwords;
}
}
}
// merge with quiz defaults
$keys = array('timeopen','timeclose', 'timelimit', 'attempts', 'password', 'extrapasswords');
foreach ($keys as $key) {
if (isset($override->{$key})) {
$quiz->{$key} = $override->{$key};
}
}
return $quiz;
}
/** /**
* Delete all the attempts belonging to a quiz. * Delete all the attempts belonging to a quiz.
* *
@ -639,74 +781,11 @@ function quiz_refresh_events($courseid = 0) {
return true; return true;
} }
} }
$moduleid = $DB->get_field('modules', 'id', array('name' => 'quiz'));
foreach ($quizzes as $quiz) { foreach ($quizzes as $quiz) {
$cm = get_coursemodule_from_id('quiz', $quiz->id); quiz_update_events($quiz);
$event = NULL;
$event2 = NULL;
$event2old = NULL;
if ($events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id), 'timestart')) {
$event = array_shift($events);
if (!empty($events)) {
$event2old = array_shift($events);
if (!empty($events)) {
foreach ($events as $badevent) {
$badevent = calendar_event::load($badevent);
$badevent->delete();
}
}
}
} }
$event->name = $quiz->name;
$event->description = format_module_intro('quiz', $quiz, $cm->id);
$event->courseid = $quiz->course;
$event->groupid = 0;
$event->userid = 0;
$event->modulename = 'quiz';
$event->instance = $quiz->id;
$event->visible = instance_is_visible('quiz', $quiz);
$event->timestart = $quiz->timeopen;
$event->eventtype = 'open';
$event->timeduration = ($quiz->timeclose - $quiz->timeopen);
if ($event->timeduration > QUIZ_MAX_EVENT_LENGTH) { /// Set up two events
$event2 = $event;
$event->name = $quiz->name.' ('.get_string('quizopens', 'quiz').')';
$event->timeduration = 0;
$event2->name = $quiz->name.' ('.get_string('quizcloses', 'quiz').')';
$event2->timestart = $quiz->timeclose;
$event2->eventtype = 'close';
$event2->timeduration = 0;
if (empty($event2old->id)) {
unset($event2->id);
calendar_event::create($event2);
} else {
$event2->id = $event2old->id;
$event2 = calendar_event::load($event2);
$event2->update($event2);
}
} else if (!empty($event2old->id)) {
$event2old = calendar_event::load($event2old);
$event2old->delete();
}
if (empty($event->id)) {
if (!empty($event->timestart)) {
calendar_event::create($event);
}
} else {
$event = calendar_event::load($event);
$event->update($event);
}
}
return true; return true;
} }
@ -1079,49 +1158,135 @@ function quiz_after_add_or_update($quiz) {
} }
// Update the events relating to this quiz. // Update the events relating to this quiz.
// This is slightly inefficient, deleting the old events and creating new ones. However, quiz_update_events($quiz);
// there are at most two events, and this keeps the code simpler.
if ($events = $DB->get_records('event', array('modulename'=>'quiz', 'instance'=>$quiz->id))) { //update related grade item
foreach($events as $event) { quiz_grade_item_update($quiz);
$event2old = calendar_event::load($event);
$event2old->delete();
} }
/**
* This function updates the events associated to the quiz.
* If $override is non-zero, then it updates only the events
* associated with the specified override.
*
* @uses QUIZ_MAX_EVENT_LENGTH
* @param object $quiz the quiz object.
* @param object optional $override limit to a specific override
*/
function quiz_update_events($quiz, $override = null) {
global $DB;
// Load the old events relating to this quiz.
$conds = array('modulename'=>'quiz',
'instance'=>$quiz->id);
if (!empty($override)) {
// only load events for this override
$conds['groupid'] = isset($override->groupid)? $override->groupid : 0;
$conds['userid'] = isset($override->userid)? $override->userid : 0;
} }
$oldevents = $DB->get_records('event', $conds);
// Now make a todo list of all that needs to be updated
if (empty($override)) {
// We are updating the primary settings for the quiz, so we
// need to add all the overrides
$overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id));
// as well as the original quiz (empty override)
$overrides[] = new stdClass;
}
else {
// Just do the one override
$overrides = array($override);
}
foreach ($overrides as $current) {
$groupid = isset($current->groupid)? $current->groupid : 0;
$userid = isset($current->userid)? $current->userid : 0;
$timeopen = isset($current->timeopen)? $current->timeopen : $quiz->timeopen;
$timeclose = isset($current->timeclose)? $current->timeclose : $quiz->timeclose;
// only add open/close events for an override if they differ from the quiz default
$addopen = empty($current->id) || !empty($current->timeopen);
$addclose = empty($current->id) || !empty($current->timeclose);
$event = new stdClass; $event = new stdClass;
$event->description = $quiz->intro; $event->description = $quiz->intro;
$event->courseid = $quiz->course; $event->courseid = ($userid) ? 0 : $quiz->course; // Events module won't show user events when the courseid is nonzero
$event->groupid = 0; $event->groupid = $groupid;
$event->userid = 0; $event->userid = $userid;
$event->modulename = 'quiz'; $event->modulename = 'quiz';
$event->instance = $quiz->id; $event->instance = $quiz->id;
$event->timestart = $quiz->timeopen; $event->timestart = $timeopen;
$event->timeduration = $quiz->timeclose - $quiz->timeopen; $event->timeduration = max($timeclose - $timeopen, 0);
$event->visible = instance_is_visible('quiz', $quiz); $event->visible = instance_is_visible('quiz', $quiz);
$event->eventtype = 'open'; $event->eventtype = 'open';
if ($quiz->timeclose and $quiz->timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) { // Determine the event name
if ($groupid) {
$params = new stdClass;
$params->quiz = $quiz->name;
$params->group = groups_get_group_name($groupid);
if ($params->group === false) {
// group doesn't exist, just skip it
continue;
}
$eventname = get_string('overridegroupeventname', 'quiz', $params);
}
else if ($userid) {
$params = new stdClass;
$params->quiz = $quiz->name;
$eventname = get_string('overrideusereventname', 'quiz', $params);
} else {
$eventname = $quiz->name;
}
if ($addopen or $addclose) {
if ($timeclose and $timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) {
// Single event for the whole quiz. // Single event for the whole quiz.
$event->name = $quiz->name; if ($oldevent = array_shift($oldevents)) {
$event->id = $oldevent->id;
}
else {
unset($event->id);
}
$event->name = $eventname;
// calendar_event::create will reuse a db record if the id field is set
calendar_event::create($event); calendar_event::create($event);
} else { } else {
// Separate start and end events. // Separate start and end events.
$event->timeduration = 0; $event->timeduration = 0;
if ($quiz->timeopen) { if ($timeopen && $addopen) {
$event->name = $quiz->name.' ('.get_string('quizopens', 'quiz').')'; if ($oldevent = array_shift($oldevents)) {
calendar_event::create($event); $event->id = $oldevent->id;
unset($event->id); // So we can use the same object for the close event.
} }
if ($quiz->timeclose) { else {
$event->name = $quiz->name.' ('.get_string('quizcloses', 'quiz').')'; unset($event->id);
$event->timestart = $quiz->timeclose; }
$event->name = $eventname.' ('.get_string('quizopens', 'quiz').')';
// calendar_event::create will reuse a db record if the id field is set
calendar_event::create($event);
}
if ($timeclose && $addclose) {
if ($oldevent = array_shift($oldevents)) {
$event->id = $oldevent->id;
}
else {
unset($event->id);
}
$event->name = $eventname.' ('.get_string('quizcloses', 'quiz').')';
$event->timestart = $timeclose;
$event->eventtype = 'close'; $event->eventtype = 'close';
calendar_event::create($event); calendar_event::create($event);
} }
} }
}
}
//update related grade item // Delete any leftover events
quiz_grade_item_update($quiz); foreach ($oldevents as $badevent) {
$badevent = calendar_event::load($badevent);
$badevent->delete();
}
} }
/** /**

212
mod/quiz/override_form.php Normal file
View file

@ -0,0 +1,212 @@
<?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/>.
/**
* Settings form for overrides in the quiz module.
*
* @package mod_quiz
* @copyright 2010 Matt Petro
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once $CFG->libdir.'/formslib.php';
class quiz_override_form extends moodleform {
protected $cm; // course module object
protected $quiz; // quiz object
protected $context; // context object
protected $groupmode; // editing group override (true) or user override (false)
protected $groupid; // groupid, if provided
protected $userid; // userid, if provided
public function quiz_override_form($submiturl, $cm, $quiz, $context, $groupmode, $override) {
$this->cm = $cm;
$this->quiz = $quiz;
$this->context = $context;
$this->groupmode = $groupmode;
$this->groupid = empty($override->groupid) ? 0 : $override->groupid;
$this->userid = empty($override->userid) ? 0 : $override->userid;
parent::moodleform($submiturl, null, 'post');
}
public function definition() {
global $CFG, $USER, $DB;
$cm = $this->cm;
$mform = $this->_form;
$mform->addElement('header', 'override', get_string('override', 'quiz'));
if ($this->groupmode) {
// group override
if ($this->groupid) {
// There is already a groupid, so freeze the selector
$groupchoices = array();
$groupchoices[$this->groupid] = groups_get_group_name($this->groupid);
$mform->addElement('select', 'groupid', get_string('overridegroup', 'quiz'), $groupchoices);
$mform->freeze('groupid');
} else {
// Prepare the list of groups
$groups = groups_get_all_groups($cm->course, null, $cm->groupingid);
if (empty($groups)) {
$groups = array();
}
$groupchoices = array();
foreach ($groups as $group) {
$groupchoices[$group->id] = $group->name;
}
unset($groups);
if (count($groupchoices) == 0) {
$groupchoices[0] = get_string('none');
}
$mform->addElement('select', 'groupid', get_string('overridegroup', 'quiz'), $groupchoices);
$mform->addRule('groupid', get_string('required'), 'required', null, 'client');
}
} else {
//user override
if ($this->userid) {
// There is already a userid, so freeze the selector
$user = $DB->get_record('user', array('id'=>$this->userid));
$userchoices = array();
$userchoices[$this->userid] = fullname($user);
$mform->addElement('select', 'userid', get_string('overrideuser', 'quiz'), $userchoices);
$mform->freeze('userid');
} else {
// Prepare the list of users
if (!empty($cm->groupingid)) {
$groups = groups_get_all_groups($cm->course, 0, $cm->groupingid);
$groups = array_keys($groups);
} else {
$groups = null;
}
$users = get_users_by_capability($this->context, 'mod/quiz:attempt', 'u.id,u.firstname,u.lastname,u.email' ,
'firstname ASC, lastname ASC', '', '', $groups, '', false, true);
if (empty($users)) {
$users = array();
}
$userchoices = array();
foreach ($users as $id=>$user) {
if (empty($invalidusers[$id]) || (!empty($override) && $id == $override->userid)) {
$userchoices[$id] = fullname($user) . ', ' . $user->email;
}
}
unset($users);
if (count($userchoices) == 0) {
$userchoices[0] = get_string('none');
}
$mform->addElement('searchableselector', 'userid', get_string('overrideuser', 'quiz'), $userchoices);
$mform->addRule('userid', get_string('required'), 'required', null, 'client');
}
}
// Password
// This field has to be above the date and timelimit fields,
// otherwise browsers will clear it when those fields are changed
$mform->addElement('passwordunmask', 'password', get_string('requirepassword', 'quiz'));
$mform->setType('password', PARAM_TEXT);
$mform->setHelpButton('password', array('requirepassword', get_string('requirepassword', 'quiz'), 'quiz'));
$mform->setDefault('password', $this->quiz->password);
// Open and close dates.
$mform->addElement('date_time_selector', 'timeopen', get_string('quizopen', 'quiz'), array('optional' => true));
$mform->setHelpButton('timeopen', array('timeopen', get_string('quizopen', 'quiz'), 'quiz'));
$mform->setDefault('timeopen', $this->quiz->timeopen);
$mform->addElement('date_time_selector', 'timeclose', get_string('quizclose', 'quiz'), array('optional' => true));
$mform->setHelpButton('timeclose', array('timeopen', get_string('quizclose', 'quiz'), 'quiz'));
$mform->setDefault('timeclose', $this->quiz->timeclose);
// Time limit.
$mform->addElement('duration', 'timelimit', get_string('quiztimer', 'quiz'), array('optional' => true));
$mform->setHelpButton('timelimit', array('timelimit', get_string('quiztimer','quiz'), 'quiz'));
$mform->setDefault('timelimit', $this->quiz->timelimit);
// Number of attempts.
$attemptoptions = array('0' => get_string('unlimited'));
for ($i = 1; $i <= QUIZ_MAX_ATTEMPT_OPTION; $i++) {
$attemptoptions[$i] = $i;
}
$mform->addElement('select', 'attempts', get_string('attemptsallowed', 'quiz'), $attemptoptions);
$mform->setHelpButton('attempts', array('attempts', get_string('attemptsallowed','quiz'), 'quiz'));
$mform->setDefault('attempts', $this->quiz->attempts);
// Submit buttons
$mform->addElement('submit', 'resetbutton', get_string('reverttodefaults','quiz'));
$buttonarray = array();
$buttonarray[] = $mform->createElement('submit', 'submitbutton', get_string('save', 'quiz'));
$buttonarray[] = $mform->createElement('submit', 'againbutton', get_string('saveoverrideandstay', 'quiz'));
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonbar', '', array(' '), false);
$mform->closeHeaderBefore('buttonbar');
}
// form verification
public function validation($data, $files) {
global $COURSE, $DB;
$errors = parent::validation($data, $files);
$mform =& $this->_form;
$quiz = $this->quiz;
if ($mform->elementExists('userid')) {
if (empty($data['userid'])) {
$errors['userid'] = get_string('required');
}
}
if ($mform->elementExists('groupid')) {
if (empty($data['groupid'])) {
$errors['groupid'] = get_string('required');
}
}
// Ensure that the dates make sense
if (!empty($data['timeopen']) && !empty($data['timeclose'])) {
if ($data['timeclose'] < $data['timeopen'] ) {
$errors['timeclose'] = get_string('closebeforeopen', 'quiz');
}
}
// Ensure that at least one quiz setting was changed
$changed = false;
$keys = array('timeopen','timeclose', 'timelimit', 'attempts', 'password');
foreach ($keys as $key) {
if ($data[$key] != $quiz->{$key}) {
$changed = true;
break;
}
}
if (!$changed) {
$errors['timeopen'] = get_string('nooverridedata', 'quiz');
}
return $errors;
}
}

View file

@ -0,0 +1,97 @@
<?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/>.
/**
* This page handles deleting quiz overrides
*
* @package mod_quiz
* @copyright 2010 Matt Petro
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__) . '/../../config.php');
require_once($CFG->dirroot.'/mod/quiz/lib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
require_once($CFG->dirroot.'/mod/quiz/override_form.php');
$overrideid = required_param('id', PARAM_INT); // override ID
$confirm = optional_param('confirm', false, PARAM_BOOL); // already confirmed?
if (! $override = $DB->get_record('quiz_overrides', array('id' => $overrideid))) {
print_error('invalidoverrideid', 'quiz');
}
if (! $quiz = $DB->get_record('quiz', array('id' => $override->quiz))) {
print_error('invalidcoursemodule');
}
if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $quiz->course)) {
print_error('invalidcoursemodule');
}
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
require_login($cm->course, false, $cm);
// Check the user has the required capabilities to modify an override
require_capability('mod/quiz:manageoverrides', $context);
$url = new moodle_url('/mod/quiz/overridedelete.php', array('id'=>$override->id));
$confirmurl = new moodle_url($url, array('id'=>$override->id, 'confirm'=>1));
$cancelurl = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id));
if (!empty($override->userid)) {
$cancelurl->param('mode', 'user');
}
// If confirm is set (PARAM_BOOL) then we have confirmation of intention to delete
if ($confirm) {
// Confirm the session key to stop CSRF
require_sesskey();
// Remove the override
quiz_delete_override($quiz, $override->id);
add_to_log($cm->course, 'quiz', 'delete override',
"overrides.php?cmid=$cm->id", $quiz->id, $cm->id);
// And redirect
redirect($cancelurl);
}
// Prepare the page to show the confirmation form
$stroverride = get_string('override', 'quiz');
$title = get_string('deletecheck', null, $stroverride);
$PAGE->set_url($url);
$PAGE->navbar->add($title);
$PAGE->set_title($title);
$PAGE->set_heading($stroverride);
echo $OUTPUT->header();
if ($override->groupid) {
$group = $DB->get_record('groups', array('id' => $override->groupid), 'id,name');
$confirmstr = get_string("overridedeletegroupsure", "quiz", $group->name);
}
else {
$user = $DB->get_record('user', array('id' => $override->userid), 'id,firstname,lastname');
$confirmstr = get_string("overridedeleteusersure", "quiz", fullname($user));
}
echo $OUTPUT->confirm($confirmstr, $confirmurl, $cancelurl);
echo $OUTPUT->footer();

200
mod/quiz/overrideedit.php Normal file
View file

@ -0,0 +1,200 @@
<?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/>.
/**
* This page handles editing and creation of quiz overrides
*
* @package mod_quiz
* @copyright 2010 Matt Petro
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__) . '/../../config.php');
require_once($CFG->dirroot.'/mod/quiz/lib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
require_once($CFG->dirroot.'/mod/quiz/override_form.php');
$cmid = optional_param('cmid', 0, PARAM_INT); // course module ID, if new override
$overrideid = optional_param('id', 0, PARAM_INT); // override ID, if editing existing override
$action = optional_param('action', null, PARAM_ALPHA); // if creating new override, one of 'adduser','addgroup', or 'duplicate'
$reset = optional_param('reset', false, PARAM_BOOL); // reset form to defaults
$override = null;
if ($overrideid) {
if (! $override = $DB->get_record('quiz_overrides', array('id' => $overrideid))) {
print_error('invalidoverrideid', 'quiz');
}
if (! $quiz = $DB->get_record('quiz', array('id' => $override->quiz))) {
print_error('invalidcoursemodule');
}
if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $quiz->course)) {
print_error('invalidcoursemodule');
}
} else if ($cmid) {
if (! $cm = get_coursemodule_from_id('quiz', $cmid)) {
print_error('invalidcoursemodule');
}
if (! $quiz = $DB->get_record('quiz', array('id' => $cm->instance))) {
print_error('invalidcoursemodule');
}
} else {
print_error('invalidcoursemodule');
}
$url = new moodle_url('/mod/quiz/overrideedit.php');
if ($action) {
$url->param('action', $action);
}
if ($overrideid) {
$url->param('id', $overrideid);
} else {
$url->param('cmid', $cmid);
}
$PAGE->set_url($url);
require_login($cm->course, false, $cm);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
// Add or edit an override
require_capability('mod/quiz:manageoverrides', $context);
if ($overrideid) {
// editing override
$data = clone $override;
}
else {
// new override
$data = new object();
}
// merge quiz defaults with data
$keys = array('timeopen','timeclose', 'timelimit', 'attempts', 'password');
foreach ($keys as $key) {
if (!isset($data->{$key}) || $reset) {
$data->{$key} = $quiz->{$key};
}
}
// If we are duplicating an override, then clear the user/group and override id since they will change
if ($action === 'duplicate') {
$override->id = null;
$override->userid = null;
$override->groupid = null;
}
$groupmode = !empty($data->groupid) || ($action === 'addgroup' && empty($overrideid)); // true if group-based override
$overridelisturl = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id));
if (!$groupmode) {
$overridelisturl->param('mode', 'user');
}
// Setup the form.
$mform = new quiz_override_form($url, $cm, $quiz, $context, $groupmode, $override);
$mform->set_data($data);
if ($mform->is_cancelled()) {
// redirect back to override list
redirect($overridelisturl);
} else if (optional_param('resetbutton', 0, PARAM_ALPHA)) {
// redirect back to current page and reset the form.
$url->param('reset', true);
redirect($url);
} else if ($fromform = $mform->get_data()) {
// Process the data
$fromform->quiz = $quiz->id;
// Replace unchanged values with null
foreach ($keys as $key) {
if ($fromform->{$key} == $quiz->{$key}) {
$fromform->{$key} = null;
}
}
// See if we are replacing an existing override
$userorgroupchanged = false;
if (empty($override->id)) {
$userorgroupchanged = true;
} else if (!empty($fromform->userid)) {
$userorgroupchanged = $fromform->userid !== $override->userid;
} else {
$userorgroupchanged = $fromform->groupid !== $override->groupid;
}
if ($userorgroupchanged) {
$conditions = array(
'quiz' => $quiz->id,
'userid' => empty($fromform->userid)? null : $fromform->userid,
'groupid' => empty($fromform->groupid)? null : $fromform->groupid);
if ($oldoverride = $DB->get_record('quiz_overrides', $conditions)) {
// There is an old override, so we merge any new settings on top of
// the older override
foreach ($keys as $key) {
if (is_null($fromform->{$key})) {
$fromform->{$key} = $oldoverride->{$key};
}
}
// Delete the old override
$DB->delete_records('quiz_overrides', array('id' => $oldoverride->id));
}
}
if (!empty($override->id)) {
$fromform->id = $override->id;
$DB->update_record('quiz_overrides', $fromform);
}
else {
unset($fromform->id);
$fromform->id = $DB->insert_record('quiz_overrides', $fromform);
}
quiz_update_events($quiz, $fromform);
add_to_log($cm->course, 'quiz', 'edit override',
"overrideedit.php?id=$fromform->id", $quiz->id, $cm->id);
if (!empty($fromform->submitbutton)) {
// redirect back to override list
redirect($overridelisturl);
}
// 'again' button pressed, so redirect back to this page
$url->remove_params('cmid');
$url->param('action', 'duplicate');
$url->param('id', $fromform->id);
redirect($url);
}
// Print the form
$pagetitle = get_string('editoverride', 'quiz');
$PAGE->navbar->add($pagetitle);
$PAGE->set_title($pagetitle);
echo $OUTPUT->header();
echo $OUTPUT->heading($pagetitle);
$mform->display();
echo $OUTPUT->footer();

217
mod/quiz/overrides.php Normal file
View file

@ -0,0 +1,217 @@
<?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/>.
/**
* This page handles listing of quiz overrides
*
* @package mod_quiz
* @copyright 2010 Matt Petro
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__) . '/../../config.php');
require_once($CFG->dirroot.'/mod/quiz/lib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
require_once($CFG->dirroot.'/mod/quiz/override_form.php');
$cmid = required_param('cmid', PARAM_INT); // course module ID, or
$mode = optional_param('mode', 'group', PARAM_ALPHA); // one of 'user' or 'group'
$groupmode = ($mode == "group");
if (! $cm = get_coursemodule_from_id('quiz', $cmid)) {
print_error('invalidcoursemodule');
}
if (! $quiz = $DB->get_record('quiz', array('id' => $cm->instance))) {
print_error('invalidcoursemodule');
}
$url = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id, 'mode'=>$mode));
$PAGE->set_url($url);
require_login($cm->course, false, $cm);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
// Check the user has the required capabilities to list overrides
require_capability('mod/quiz:manageoverrides', $context);
// Display a list of overrides
$PAGE->set_title(get_string('overrides', 'quiz'));
echo $OUTPUT->header();
// Print heading and tabs (if there is more than one).
$currenttab = 'overrides';
include('tabs.php');
// Fetch all overrides
$conds = array('quiz' => $quiz->id);
if ($groupmode) {
$colname = get_string('group');
$sql = 'SELECT o.*, g.name
FROM {quiz_overrides} o LEFT JOIN {groups} g
ON o.groupid = g.id
WHERE o.groupid IS NOT NULL
AND o.quiz = ?
ORDER BY g.name';
}
else {
$colname = get_string('user');
$sql = 'SELECT o.*, u.firstname, u.lastname, u.id as uid
FROM {quiz_overrides} o LEFT JOIN {user} u
ON o.userid = u.id
WHERE o.userid IS NOT NULL
AND o.quiz = ?
ORDER BY u.lastname, u.firstname';
}
$params = array($quiz->id);
$overrides = $DB->get_records_sql($sql, $params);
// 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'),
);
$userurl = new moodle_url('/user/view.php', array());
$groupurl = new moodle_url('/group/overview.php', array('id' => $cm->course));
$overridedeleteurl = new moodle_url('/mod/quiz/overridedelete.php');
$overrideediturl = new moodle_url('/mod/quiz/overrideedit.php');
foreach ($overrides as $override) {
$fields = array();
$values = array();
// check for orphaned overrides
if (!isset($override->name) && !isset($override->uid)) {
// no corresponding user/group record, so remove the override
quiz_delete_override($quiz, $override->id);
continue;
}
// Format timeopen
if (isset($override->timeopen)) {
$fields[] = get_string('quizopens', 'quiz');
$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');
$values[] = ($override->password !== '')? get_string('enabled', 'quiz') : get_string('none', 'quiz');
}
// Icons:
// edit
$editurlstr = $overrideediturl->out(true, array('id' => $override->id));
$iconstr = '<a title="' . get_string('edit') . '" href="'. $editurlstr . '">' .
'<img src="' . $OUTPUT->pix_url('t/edit') . '" class="iconsmall" alt="' . get_string('edit') . '" /></a> ';
// duplicate
$copyurlstr = $overrideediturl->out(true, array('id' => $override->id, 'action' => 'duplicate'));
$iconstr .= '<a title="' . get_string('copy') . '" href="' . $copyurlstr . '">' .
'<img src="' . $OUTPUT->pix_url('t/copy') . '" class="iconsmall" alt="' . get_string('copy') . '" /></a> ';
// delete
$deleteurlstr = $overridedeleteurl->out(true, array('id' => $override->id, 'sesskey' => sesskey()));
$iconstr .= '<a title="' . get_string('delete') . '" href="' . $deleteurlstr . '">' .
'<img src="' . $OUTPUT->pix_url('t/delete') . '" class="iconsmall" alt="' . get_string('delete') . '" /></a> ';
if ($groupmode) {
$usergroupstr = '<a href="' . $groupurl->out(true, array('group' => $override->groupid)) . '" >' . $override->name . '</a>';
}
else {
$usergroupstr = '<a href="' . $userurl->out(true, array('id' => $override->userid)) . '" >' . fullname($override) . '</a>';
}
if (!empty($table->data)) {
$table->data[] = 'hr';
}
$usergroupcell = new html_table_cell();
$usergroupcell->rowspan = count($fields);
$usergroupcell->text = $usergroupstr;
$actioncell = new html_table_cell();
$actioncell->rowspan = count($fields);
$actioncell->text = $iconstr;
for ($i = 0; $i < count($fields); ++$i) {
$row = new html_table_row();
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) {
$row->cells[] = $actioncell;
}
$table->data[] = $row;
}
}
// Output the table and button
echo html_writer::start_tag('div', array('id' => 'quizoverrides'));
if (count($table->data)) {
echo $OUTPUT->table($table);
}
echo html_writer::start_tag('div', array('class' => 'buttons'));
if ($groupmode) {
echo $OUTPUT->single_button($overrideediturl->out(true, array('action' => 'addgroup', 'cmid' => $cm->id)),
get_string('addnewgroupoverride', 'quiz'));
} else {
echo $OUTPUT->single_button($overrideediturl->out(true, array('action' => 'adduser', 'cmid' => $cm->id)),
get_string('addnewuseroverride', 'quiz'));
}
echo html_writer::end_tag('div');
echo html_writer::end_tag('div');
// Finish the page
echo $OUTPUT->footer();

View file

@ -27,7 +27,7 @@ $submittedquestionids = required_param('questionids', PARAM_SEQUENCE);
$finishattempt = optional_param('finishattempt', 0, PARAM_BOOL); $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
$timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer. $timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer.
$attemptobj = new quiz_attempt($attemptid); $attemptobj = quiz_attempt::create($attemptid);
/// Set $nexturl now. It will be updated if a particular question was sumbitted in /// Set $nexturl now. It will be updated if a particular question was sumbitted in
/// adaptive mode. /// adaptive mode.

View file

@ -113,6 +113,8 @@
$status = quiz_question_instances_restore_mods($newid,$info,$restore); $status = quiz_question_instances_restore_mods($newid,$info,$restore);
//We have to restore the feedback now (course level table) //We have to restore the feedback now (course level table)
$status = quiz_feedback_restore_mods($newid, $info, $restore, $quiz); $status = quiz_feedback_restore_mods($newid, $info, $restore, $quiz);
//We have to restore the overrides now (course level table)
$status = quiz_overrides_restore_mods($newid, $info, $restore, $quiz);
//Now check if want to restore user data and do it. //Now check if want to restore user data and do it.
if (restore_userdata_selected($restore,'quiz',$mod->id)) { if (restore_userdata_selected($restore,'quiz',$mod->id)) {
//Restore quiz_attempts //Restore quiz_attempts
@ -241,6 +243,78 @@
return $status; return $status;
} }
//This function restores the quiz_overrides
function quiz_overrides_restore_mods($quiz_id, $info, $restore, $quiz) {
global $DB;
$douserdata = restore_userdata_selected($restore,'quiz',$quiz_id);
$status = true;
//Get the quiz_feedback array
if (array_key_exists('OVERRIDES', $info['MOD']['#'])) {
$overrides = $info['MOD']['#']['OVERRIDES']['0']['#']['OVERRIDE'];
//Iterate over the feedbacks
foreach ($overrides as $override_info) {
//traverse_xmlize($override_info); //Debug
//print_object ($GLOBALS['traverse_array']); //Debug
//$GLOBALS['traverse_array']=""; //Debug
//We'll need this later!!
$oldid = backup_todb($override_info['#']['ID']['0']['#']);
//Now, build the quiz_overrides record structure
$override = new stdClass();
$override->quiz = $quiz_id;
$override->groupid = backup_todb($override_info['#']['GROUPID']['0']['#']);
$override->userid = backup_todb($override_info['#']['USERID']['0']['#']);
$override->timeopen = backup_todb($override_info['#']['TIMEOPEN']['0']['#']);
$override->timeclose = backup_todb($override_info['#']['TIMECLOSE']['0']['#']);
$override->timelimit = backup_todb($override_info['#']['TIMELIMIT']['0']['#']);
$override->attempts = backup_todb($override_info['#']['ATTEMPTS']['0']['#']);
$override->password = backup_todb($override_info['#']['PASSWORD']['0']['#']);
// Only restore user overrides if we are restoring user data
if ($douserdata || $override->userid == 0) {
//We have to recode the userid field
if ($override->userid) {
if (!$user = backup_getid($restore->backup_unique_code,"user",$override->userid)) {
debugging("override can not be restored, user id $override->userid not present in backup");
// do not not block the restore
continue;
}
$override->userid = $user->new_id;
}
//We have to recode the groupid field
if ($override->groupid) {
if (!$group = backup_getid($restore->backup_unique_code,"groups",$override->groupid)) {
debugging("override can not be restored, group id $override->groupid not present in backup");
// do not not block the restore
continue;
}
$override->groupid = $group->new_id;
}
//The structure is equal to the db, so insert the quiz_question_instances
$newid = $DB->insert_record('quiz_overrides', $override);
if ($newid) {
//We have the newid, update backup_ids
backup_putid($restore->backup_unique_code, 'quiz_overrides', $oldid, $newid);
} else {
$status = false;
}
}
}
}
return $status;
}
//This function restores the quiz_attempts //This function restores the quiz_attempts
function quiz_attempts_restore_mods($quiz_id,$info,$restore) { function quiz_attempts_restore_mods($quiz_id,$info,$restore) {
global $CFG, $DB; global $CFG, $DB;

View file

@ -24,7 +24,7 @@
} }
$PAGE->set_url($url); $PAGE->set_url($url);
$attemptobj = new quiz_attempt($attemptid); $attemptobj = quiz_attempt::create($attemptid);
/// Check login. /// Check login.
require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());

View file

@ -21,7 +21,7 @@
} }
$PAGE->set_url($url); $PAGE->set_url($url);
$attemptobj = new quiz_attempt($attemptid); $attemptobj = quiz_attempt::create($attemptid);
/// Check login. /// Check login.
require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());

View file

@ -28,7 +28,7 @@ if (!$quiz = $DB->get_record('quiz', array('id' => $cm->instance))) {
print_error('invalidcoursemodule'); print_error('invalidcoursemodule');
} }
$quizobj = new quiz($quiz, $cm, $course); $quizobj = quiz::create($quiz->id, $USER->id);
/// Check login and sesskey. /// Check login and sesskey.
require_login($quizobj->get_courseid(), false, $quizobj->get_cm()); require_login($quizobj->get_courseid(), false, $quizobj->get_cm());

View file

@ -14,7 +14,7 @@ $attemptid = required_param('attempt', PARAM_INT); // The attempt to summarise.
$PAGE->set_url('/mod/quiz/summary.php', array('attempt'=>$attemptid)); $PAGE->set_url('/mod/quiz/summary.php', array('attempt'=>$attemptid));
$attemptobj = new quiz_attempt($attemptid); $attemptobj = quiz_attempt::create($attemptid);
/// Check login. /// Check login.
require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());

View file

@ -45,6 +45,9 @@ if (has_capability('mod/quiz:preview', $context)) {
if (has_capability('mod/quiz:manage', $context)) { if (has_capability('mod/quiz:manage', $context)) {
$row[] = new tabobject('edit', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", "<img src=\"" . $OUTPUT->pix_url('t/edit') . "\" class=\"iconsmall\" alt=\"$stredit\" /> $stredit",$stredit); $row[] = new tabobject('edit', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", "<img src=\"" . $OUTPUT->pix_url('t/edit') . "\" class=\"iconsmall\" alt=\"$stredit\" /> $stredit",$stredit);
} }
if (has_capability('mod/quiz:manageoverrides', $context)) {
$row[] = new tabobject('overrides', "$CFG->wwwroot/mod/quiz/overrides.php?cmid=$cm->id", get_string('overrides', 'quiz'));
}
if ($currenttab == 'info' && count($row) == 1) { if ($currenttab == 'info' && count($row) == 1) {
// Don't show only an info tab (e.g. to students). // Don't show only an info tab (e.g. to students).
@ -91,6 +94,20 @@ if ($currenttab == 'edit' and isset($mode)) {
} }
if ($currenttab == 'overrides' and isset($mode)) {
$activated[] = 'overrides';
$row = array();
$currenttab = $mode;
$strgroup = get_string('groupoverrides', 'quiz');
$struser = get_string('useroverrides', 'quiz');
$row[] = new tabobject('group', "$CFG->wwwroot/mod/quiz/overrides.php?cmid=$cm->id", $strgroup);
$row[] = new tabobject('user', "$CFG->wwwroot/mod/quiz/overrides.php?cmid=$cm->id&amp;mode=user", $struser);
$tabs[] = $row;
}
if (!$quiz->questions) { if (!$quiz->questions) {
$inactive += array('info', 'reports', 'preview'); $inactive += array('info', 'reports', 'preview');
} }

View file

@ -5,7 +5,7 @@
// This fragment is called by moodle_needs_upgrading() and /admin/index.php // This fragment is called by moodle_needs_upgrading() and /admin/index.php
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
$module->version = 2009111900; // The (date) version of this module $module->version = 2010030501; // The (date) version of this module
$module->requires = 2009041700; // Requires this Moodle version $module->requires = 2009041700; // Requires this Moodle version
$module->cron = 0; // How often should cron check this module (seconds)? $module->cron = 0; // How often should cron check this module (seconds)?

View file

@ -44,7 +44,7 @@
/// Create an object to manage all the other (non-roles) access rules. /// Create an object to manage all the other (non-roles) access rules.
$timenow = time(); $timenow = time();
$accessmanager = new quiz_access_manager(new quiz($quiz, $cm, $course), $timenow, $accessmanager = new quiz_access_manager(quiz::create($quiz->id, $USER->id), $timenow,
has_capability('mod/quiz:ignoretimelimits', $context, NULL, false)); has_capability('mod/quiz:ignoretimelimits', $context, NULL, false));
/// If no questions have been set up yet redirect to edit.php /// If no questions have been set up yet redirect to edit.php
@ -129,6 +129,9 @@
exit; exit;
} }
/// Update the quiz with overrides for the current user
$quiz = quiz_update_effective_access($quiz, $USER->id);
/// Get this user's attempts. /// Get this user's attempts.
$attempts = quiz_get_user_attempts($quiz->id, $USER->id); $attempts = quiz_get_user_attempts($quiz->id, $USER->id);
$lastfinishedattempt = end($attempts); $lastfinishedattempt = end($attempts);

View file

@ -1203,4 +1203,21 @@ css id's of question bank*/
#categoryquestions .header { #categoryquestions .header {
border: 0 none; border: 0 none;
} }
#mod-quiz-overrides div#quizoverrides{
margin-top: 1.5em;
}
#mod-quiz-overrides div#quizoverrides table{
width: 100%;
}
#mod-quiz-overrides div#quizoverrides table .colname{
width: 25%;
}
#mod-quiz-overrides div#quizoverrides table .colvalue{
width: 50%;
}
#mod-quiz-overrides div#quizoverrides table .colaction{
width: 10%;
}
#mod-quiz-overrides div#quizoverrides .buttons{
text-align: center;
}