diff --git a/lang/en_utf8/quiz.php b/lang/en_utf8/quiz.php index d230e86286d..10a186898ad 100644 --- a/lang/en_utf8/quiz.php +++ b/lang/en_utf8/quiz.php @@ -26,8 +26,10 @@ $string['addingrandomsamatch'] = 'Adding a Random Short-Answer Matching question $string['addingshortanswer'] = 'Adding a Short-Answer question'; $string['addingtruefalse'] = 'Adding a True/False question'; $string['addmoreoverallfeedbacks'] = 'Add {no} more feedback fields'; +$string['addnewgroupoverride'] = 'Add group override'; $string['addnewpagesafterselected'] = 'Add new pages after selected questions'; $string['addnewquestionsqbank'] = 'Add questions to the category $a->catname: $a->link'; +$string['addnewuseroverride'] = 'Add user override'; $string['addpagehere'] = 'Add page here'; $string['addquestion'] = 'Add question'; $string['addquestions'] = 'Add questions'; @@ -220,6 +222,7 @@ $string['deletequestionscheck'] = 'Are you absolutely sure you want to delete th $string['deleteselected'] = 'Delete selected'; $string['deletingquestionattempts'] = 'Deleting question attempts'; $string['description'] = 'Description'; +$string['disabled'] = 'Disabled'; $string['discrimination'] = 'Discrim. Index'; $string['displayoptions'] = 'Display options'; $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['editingshortanswer'] = 'Editing a Short-Answer question'; $string['editingtruefalse'] = 'Editing a True/False question'; +$string['editoverride'] = 'Edit override'; $string['editqcats'] = 'Edit questions categories'; $string['editquestions'] = 'Edit questions'; $string['editquiz'] = 'Edit Quiz'; @@ -268,6 +272,7 @@ in course \'$a->coursename\' You can review this attempt at $a->quizreviewurl.'; $string['emailnotifysubject'] = '$a->studentname has completed quiz $a->quizname'; $string['empty'] = 'Empty'; +$string['enabled'] = 'Enabled'; $string['endtest'] = 'End test ...'; $string['erroraccessingreport'] = 'You cannot access this report'; $string['errorinquestion'] = 'Error in question'; @@ -350,6 +355,7 @@ $string['gradingdetailsadjustment'] = 'With previous penalties this gives _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; return; } diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php index a6bd11e7a4c..169ee8066e4 100644 --- a/mod/quiz/attempt.php +++ b/mod/quiz/attempt.php @@ -33,7 +33,7 @@ } $PAGE->set_url($url); - $attemptobj = new quiz_attempt($attemptid); + $attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 6ab0698d56e..2b17e1280e3 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -90,6 +90,32 @@ class quiz { $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 ===================================================== /** * Convenience method. Calls {@link load_questions()} with the list of @@ -418,17 +444,33 @@ class quiz_attempt extends quiz { // 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 - * associated quiz, course, etc. + * @param object $attempt the row of the quiz_attempts table. + * @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; - if (!$this->attempt = quiz_load_attempt($attemptid)) { + + if (!$attempt = quiz_load_attempt($attemptid)) { 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'); } 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)) { throw new moodle_exception('invalidcoursemodule'); } - parent::__construct($quiz, $cm, $course); - $this->preload_questions(); - $this->preload_question_states(); + // Update quiz with override information + $quiz = quiz_update_effective_access($quiz, $attempt->userid); + + return new quiz_attempt($attempt, $quiz, $cm, $course); } // Functions for loading more data ===================================================== diff --git a/mod/quiz/backuplib.php b/mod/quiz/backuplib.php index 0b3074b5afa..6be2160c7c0 100644 --- a/mod/quiz/backuplib.php +++ b/mod/quiz/backuplib.php @@ -279,6 +279,8 @@ $status = backup_quiz_question_instances($bf,$preferences,$quiz->id); //Now we print to xml quiz_feedback (Course Level) $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: // - backup_quiz_grades // - backup_quiz_attempts @@ -341,6 +343,42 @@ 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) function backup_quiz_feedback ($bf,$preferences,$quiz) { global $DB; diff --git a/mod/quiz/comment.php b/mod/quiz/comment.php index 4884b9950a2..a02aeffd03d 100644 --- a/mod/quiz/comment.php +++ b/mod/quiz/comment.php @@ -15,7 +15,7 @@ $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. if (!$attemptobj->is_finished()) { diff --git a/mod/quiz/db/access.php b/mod/quiz/db/access.php index ea55aff9b7d..2ab637d8292 100644 --- a/mod/quiz/db/access.php +++ b/mod/quiz/db/access.php @@ -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. 'mod/quiz:preview' => array( 'captype' => 'write', // Only just a write. diff --git a/mod/quiz/db/install.php b/mod/quiz/db/install.php index a23a1769713..1266488dc81 100644 --- a/mod/quiz/db/install.php +++ b/mod/quiz/db/install.php @@ -21,6 +21,8 @@ function xmldb_quiz_install() { update_log_display_entry('quiz', 'start 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', 'edit override', 'quiz', 'name'); + update_log_display_entry('quiz', 'delete override', 'quiz', 'name'); $record = new object(); $record->name = 'overview'; diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml index be9eca65fc4..d85a3fec5b8 100755 --- a/mod/quiz/db/install.xml +++ b/mod/quiz/db/install.xml @@ -1,5 +1,5 @@ - @@ -109,7 +109,7 @@ - +
@@ -122,5 +122,24 @@
+ + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php index 56535a45967..2d6e084a3c1 100644 --- a/mod/quiz/db/upgrade.php +++ b/mod/quiz/db/upgrade.php @@ -292,6 +292,40 @@ function xmldb_quiz_upgrade($oldversion) { 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; } diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 2f2c5896e97..204376b6d2f 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -179,6 +179,7 @@ function quiz_delete_instance($id) { } quiz_delete_all_attempts($quiz); + quiz_delete_all_overrides($quiz); $DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id)); $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); @@ -195,6 +196,147 @@ function quiz_delete_instance($id) { 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. * @@ -639,74 +781,11 @@ function quiz_refresh_events($courseid = 0) { return true; } } - $moduleid = $DB->get_field('modules', 'id', array('name' => 'quiz')); foreach ($quizzes as $quiz) { - $cm = get_coursemodule_from_id('quiz', $quiz->id); - $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); - } - + quiz_update_events($quiz); } + return true; } @@ -1079,49 +1158,135 @@ function quiz_after_add_or_update($quiz) { } // Update the events relating to this quiz. - // This is slightly inefficient, deleting the old events and creating new ones. However, - // there are at most two events, and this keeps the code simpler. - if ($events = $DB->get_records('event', array('modulename'=>'quiz', 'instance'=>$quiz->id))) { - foreach($events as $event) { - $event2old = calendar_event::load($event); - $event2old->delete(); - } - } - - $event = new stdClass; - $event->description = $quiz->intro; - $event->courseid = $quiz->course; - $event->groupid = 0; - $event->userid = 0; - $event->modulename = 'quiz'; - $event->instance = $quiz->id; - $event->timestart = $quiz->timeopen; - $event->timeduration = $quiz->timeclose - $quiz->timeopen; - $event->visible = instance_is_visible('quiz', $quiz); - $event->eventtype = 'open'; - - if ($quiz->timeclose and $quiz->timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) { - // Single event for the whole quiz. - $event->name = $quiz->name; - calendar_event::create($event); - } else { - // Separate start and end events. - $event->timeduration = 0; - if ($quiz->timeopen) { - $event->name = $quiz->name.' ('.get_string('quizopens', 'quiz').')'; - calendar_event::create($event); - unset($event->id); // So we can use the same object for the close event. - } - if ($quiz->timeclose) { - $event->name = $quiz->name.' ('.get_string('quizcloses', 'quiz').')'; - $event->timestart = $quiz->timeclose; - $event->eventtype = 'close'; - calendar_event::create($event); - } - } + quiz_update_events($quiz); //update related grade item quiz_grade_item_update($quiz); + +} + +/** + * 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->description = $quiz->intro; + $event->courseid = ($userid) ? 0 : $quiz->course; // Events module won't show user events when the courseid is nonzero + $event->groupid = $groupid; + $event->userid = $userid; + $event->modulename = 'quiz'; + $event->instance = $quiz->id; + $event->timestart = $timeopen; + $event->timeduration = max($timeclose - $timeopen, 0); + $event->visible = instance_is_visible('quiz', $quiz); + $event->eventtype = 'open'; + + // 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. + 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); + } else { + // Separate start and end events. + $event->timeduration = 0; + if ($timeopen && $addopen) { + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } + else { + unset($event->id); + } + $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'; + calendar_event::create($event); + } + } + } + } + + // Delete any leftover events + foreach ($oldevents as $badevent) { + $badevent = calendar_event::load($badevent); + $badevent->delete(); + } } /** diff --git a/mod/quiz/override_form.php b/mod/quiz/override_form.php new file mode 100644 index 00000000000..788cae71256 --- /dev/null +++ b/mod/quiz/override_form.php @@ -0,0 +1,212 @@ +. + + +/** + * 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; + } +} diff --git a/mod/quiz/overridedelete.php b/mod/quiz/overridedelete.php new file mode 100644 index 00000000000..f0fa4edf962 --- /dev/null +++ b/mod/quiz/overridedelete.php @@ -0,0 +1,97 @@ +. + + +/** + * 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(); diff --git a/mod/quiz/overrideedit.php b/mod/quiz/overrideedit.php new file mode 100644 index 00000000000..75beb6e58ec --- /dev/null +++ b/mod/quiz/overrideedit.php @@ -0,0 +1,200 @@ +. + + +/** + * 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(); diff --git a/mod/quiz/overrides.php b/mod/quiz/overrides.php new file mode 100644 index 00000000000..b595b49f16e --- /dev/null +++ b/mod/quiz/overrides.php @@ -0,0 +1,217 @@ +. + + +/** + * 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 = '' . + '' . get_string('edit') . ' '; + // duplicate + $copyurlstr = $overrideediturl->out(true, array('id' => $override->id, 'action' => 'duplicate')); + $iconstr .= '' . + '' . get_string('copy') . ' '; + // delete + $deleteurlstr = $overridedeleteurl->out(true, array('id' => $override->id, 'sesskey' => sesskey())); + $iconstr .= '' . + '' . get_string('delete') . ' '; + + if ($groupmode) { + $usergroupstr = '' . $override->name . ''; + } + else { + $usergroupstr = '' . fullname($override) . ''; + } + + 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(); diff --git a/mod/quiz/processattempt.php b/mod/quiz/processattempt.php index 81671d8ccbd..8cf41eec4e0 100644 --- a/mod/quiz/processattempt.php +++ b/mod/quiz/processattempt.php @@ -27,7 +27,7 @@ $submittedquestionids = required_param('questionids', PARAM_SEQUENCE); $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL); $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 /// adaptive mode. diff --git a/mod/quiz/restorelib.php b/mod/quiz/restorelib.php index 4b5fc323632..1709d4d330a 100644 --- a/mod/quiz/restorelib.php +++ b/mod/quiz/restorelib.php @@ -113,6 +113,8 @@ $status = quiz_question_instances_restore_mods($newid,$info,$restore); //We have to restore the feedback now (course level table) $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. if (restore_userdata_selected($restore,'quiz',$mod->id)) { //Restore quiz_attempts @@ -241,6 +243,78 @@ 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 function quiz_attempts_restore_mods($quiz_id,$info,$restore) { global $CFG, $DB; diff --git a/mod/quiz/review.php b/mod/quiz/review.php index 600e572dbd8..944ac1da8c3 100644 --- a/mod/quiz/review.php +++ b/mod/quiz/review.php @@ -24,7 +24,7 @@ } $PAGE->set_url($url); - $attemptobj = new quiz_attempt($attemptid); + $attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); diff --git a/mod/quiz/reviewquestion.php b/mod/quiz/reviewquestion.php index aa41cbe744a..7242e5aa764 100644 --- a/mod/quiz/reviewquestion.php +++ b/mod/quiz/reviewquestion.php @@ -21,7 +21,7 @@ } $PAGE->set_url($url); - $attemptobj = new quiz_attempt($attemptid); + $attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); diff --git a/mod/quiz/startattempt.php b/mod/quiz/startattempt.php index 5b27af7c728..d1d11579679 100644 --- a/mod/quiz/startattempt.php +++ b/mod/quiz/startattempt.php @@ -28,7 +28,7 @@ if (!$quiz = $DB->get_record('quiz', array('id' => $cm->instance))) { print_error('invalidcoursemodule'); } -$quizobj = new quiz($quiz, $cm, $course); +$quizobj = quiz::create($quiz->id, $USER->id); /// Check login and sesskey. require_login($quizobj->get_courseid(), false, $quizobj->get_cm()); diff --git a/mod/quiz/summary.php b/mod/quiz/summary.php index 2a85f5e927a..3e39c002966 100644 --- a/mod/quiz/summary.php +++ b/mod/quiz/summary.php @@ -14,7 +14,7 @@ $attemptid = required_param('attempt', PARAM_INT); // The attempt to summarise. $PAGE->set_url('/mod/quiz/summary.php', array('attempt'=>$attemptid)); -$attemptobj = new quiz_attempt($attemptid); +$attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); diff --git a/mod/quiz/tabs.php b/mod/quiz/tabs.php index 18cbfbfe673..fe613833a14 100644 --- a/mod/quiz/tabs.php +++ b/mod/quiz/tabs.php @@ -45,6 +45,9 @@ if (has_capability('mod/quiz:preview', $context)) { if (has_capability('mod/quiz:manage', $context)) { $row[] = new tabobject('edit', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", "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) { // 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&mode=user", $struser); + $tabs[] = $row; +} + if (!$quiz->questions) { $inactive += array('info', 'reports', 'preview'); } diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 0f229bd5741..b3d41026635 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -5,7 +5,7 @@ // 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->cron = 0; // How often should cron check this module (seconds)? diff --git a/mod/quiz/view.php b/mod/quiz/view.php index b1569fac1e1..8e77cbd5af8 100644 --- a/mod/quiz/view.php +++ b/mod/quiz/view.php @@ -44,7 +44,7 @@ /// Create an object to manage all the other (non-roles) access rules. $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)); /// If no questions have been set up yet redirect to edit.php @@ -82,7 +82,7 @@ $PAGE->set_title($title); $PAGE->set_heading($course->fullname); - + echo $OUTPUT->header(); /// Print heading and tabs (if there is more than one). @@ -129,6 +129,9 @@ exit; } +/// Update the quiz with overrides for the current user + $quiz = quiz_update_effective_access($quiz, $USER->id); + /// Get this user's attempts. $attempts = quiz_get_user_attempts($quiz->id, $USER->id); $lastfinishedattempt = end($attempts); diff --git a/theme/standardold/style/mod_quiz.css b/theme/standardold/style/mod_quiz.css index fd2769dd920..7f23f4bfa98 100644 --- a/theme/standardold/style/mod_quiz.css +++ b/theme/standardold/style/mod_quiz.css @@ -1203,4 +1203,21 @@ css id's of question bank*/ #categoryquestions .header { 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; +}