|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00030 defined('MOODLE_INTERNAL') || die(); 00031 00032 require_once($CFG->libdir . '/eventslib.php'); 00033 require_once($CFG->dirroot . '/calendar/lib.php'); 00034 00035 00039 define('QUIZ_MAX_ATTEMPT_OPTION', 10); 00040 define('QUIZ_MAX_QPP_OPTION', 50); 00041 define('QUIZ_MAX_DECIMAL_OPTION', 5); 00042 define('QUIZ_MAX_Q_DECIMAL_OPTION', 7); 00049 define('QUIZ_GRADEHIGHEST', '1'); 00050 define('QUIZ_GRADEAVERAGE', '2'); 00051 define('QUIZ_ATTEMPTFIRST', '3'); 00052 define('QUIZ_ATTEMPTLAST', '4'); 00059 define('QUIZ_MAX_EVENT_LENGTH', 5*24*60*60); // 5 days 00060 00071 function quiz_add_instance($quiz) { 00072 global $DB; 00073 $cmid = $quiz->coursemodule; 00074 00075 // Process the options from the form. 00076 $quiz->created = time(); 00077 $quiz->questions = ''; 00078 $result = quiz_process_options($quiz); 00079 if ($result && is_string($result)) { 00080 return $result; 00081 } 00082 00083 // Try to store it in the database. 00084 $quiz->id = $DB->insert_record('quiz', $quiz); 00085 00086 // Do the processing required after an add or an update. 00087 quiz_after_add_or_update($quiz); 00088 00089 return $quiz->id; 00090 } 00091 00100 function quiz_update_instance($quiz, $mform) { 00101 global $CFG, $DB; 00102 00103 // Process the options from the form. 00104 $result = quiz_process_options($quiz); 00105 if ($result && is_string($result)) { 00106 return $result; 00107 } 00108 00109 $oldquiz = $DB->get_record('quiz', array('id' => $quiz->instance)); 00110 00111 // Repaginate, if asked to. 00112 if (!$quiz->shufflequestions && !empty($quiz->repaginatenow)) { 00113 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 00114 $quiz->questions = quiz_repaginate(quiz_clean_layout($oldquiz->questions, true), 00115 $quiz->questionsperpage); 00116 } 00117 unset($quiz->repaginatenow); 00118 00119 // Update the database. 00120 $quiz->id = $quiz->instance; 00121 $DB->update_record('quiz', $quiz); 00122 00123 // Do the processing required after an add or an update. 00124 quiz_after_add_or_update($quiz); 00125 00126 if ($oldquiz->grademethod != $quiz->grademethod) { 00127 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 00128 $quiz->sumgrades = $oldquiz->sumgrades; 00129 $quiz->grade = $oldquiz->grade; 00130 quiz_update_all_final_grades($quiz); 00131 quiz_update_grades($quiz); 00132 } 00133 00134 // Delete any previous preview attempts 00135 quiz_delete_previews($quiz); 00136 00137 return true; 00138 } 00139 00148 function quiz_delete_instance($id) { 00149 global $DB; 00150 00151 $quiz = $DB->get_record('quiz', array('id' => $id), '*', MUST_EXIST); 00152 00153 quiz_delete_all_attempts($quiz); 00154 quiz_delete_all_overrides($quiz); 00155 00156 $DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id)); 00157 $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); 00158 00159 $events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id)); 00160 foreach ($events as $event) { 00161 $event = calendar_event::load($event); 00162 $event->delete(); 00163 } 00164 00165 quiz_grade_item_delete($quiz); 00166 $DB->delete_records('quiz', array('id' => $quiz->id)); 00167 00168 return true; 00169 } 00170 00178 function quiz_delete_override($quiz, $overrideid) { 00179 global $DB; 00180 00181 $override = $DB->get_record('quiz_overrides', array('id' => $overrideid), '*', MUST_EXIST); 00182 00183 // Delete the events 00184 $events = $DB->get_records('event', array('modulename' => 'quiz', 00185 'instance' => $quiz->id, 'groupid' => (int)$override->groupid, 00186 'userid' => (int)$override->userid)); 00187 foreach ($events as $event) { 00188 $eventold = calendar_event::load($event); 00189 $eventold->delete(); 00190 } 00191 00192 $DB->delete_records('quiz_overrides', array('id' => $overrideid)); 00193 return true; 00194 } 00195 00201 function quiz_delete_all_overrides($quiz) { 00202 global $DB; 00203 00204 $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id), 'id'); 00205 foreach ($overrides as $override) { 00206 quiz_delete_override($quiz, $override->id); 00207 } 00208 } 00209 00225 function quiz_update_effective_access($quiz, $userid) { 00226 global $DB; 00227 00228 // check for user override 00229 $override = $DB->get_record('quiz_overrides', array('quiz' => $quiz->id, 'userid' => $userid)); 00230 00231 if (!$override) { 00232 $override = new stdClass(); 00233 $override->timeopen = null; 00234 $override->timeclose = null; 00235 $override->timelimit = null; 00236 $override->attempts = null; 00237 $override->password = null; 00238 } 00239 00240 // check for group overrides 00241 $groupings = groups_get_user_groups($quiz->course, $userid); 00242 00243 if (!empty($groupings[0])) { 00244 // Select all overrides that apply to the User's groups 00245 list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0])); 00246 $sql = "SELECT * FROM {quiz_overrides} 00247 WHERE groupid $extra AND quiz = ?"; 00248 $params[] = $quiz->id; 00249 $records = $DB->get_records_sql($sql, $params); 00250 00251 // Combine the overrides 00252 $opens = array(); 00253 $closes = array(); 00254 $limits = array(); 00255 $attempts = array(); 00256 $passwords = array(); 00257 00258 foreach ($records as $gpoverride) { 00259 if (isset($gpoverride->timeopen)) { 00260 $opens[] = $gpoverride->timeopen; 00261 } 00262 if (isset($gpoverride->timeclose)) { 00263 $closes[] = $gpoverride->timeclose; 00264 } 00265 if (isset($gpoverride->timelimit)) { 00266 $limits[] = $gpoverride->timelimit; 00267 } 00268 if (isset($gpoverride->attempts)) { 00269 $attempts[] = $gpoverride->attempts; 00270 } 00271 if (isset($gpoverride->password)) { 00272 $passwords[] = $gpoverride->password; 00273 } 00274 } 00275 // If there is a user override for a setting, ignore the group override 00276 if (is_null($override->timeopen) && count($opens)) { 00277 $override->timeopen = min($opens); 00278 } 00279 if (is_null($override->timeclose) && count($closes)) { 00280 $override->timeclose = max($closes); 00281 } 00282 if (is_null($override->timelimit) && count($limits)) { 00283 $override->timelimit = max($limits); 00284 } 00285 if (is_null($override->attempts) && count($attempts)) { 00286 $override->attempts = max($attempts); 00287 } 00288 if (is_null($override->password) && count($passwords)) { 00289 $override->password = array_shift($passwords); 00290 if (count($passwords)) { 00291 $override->extrapasswords = $passwords; 00292 } 00293 } 00294 00295 } 00296 00297 // merge with quiz defaults 00298 $keys = array('timeopen', 'timeclose', 'timelimit', 'attempts', 'password', 'extrapasswords'); 00299 foreach ($keys as $key) { 00300 if (isset($override->{$key})) { 00301 $quiz->{$key} = $override->{$key}; 00302 } 00303 } 00304 00305 return $quiz; 00306 } 00307 00313 function quiz_delete_all_attempts($quiz) { 00314 global $CFG, $DB; 00315 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 00316 question_engine::delete_questions_usage_by_activities(new qubaids_for_quiz($quiz->id)); 00317 $DB->delete_records('quiz_attempts', array('quiz' => $quiz->id)); 00318 $DB->delete_records('quiz_grades', array('quiz' => $quiz->id)); 00319 } 00320 00329 function quiz_get_best_grade($quiz, $userid) { 00330 global $DB; 00331 $grade = $DB->get_field('quiz_grades', 'grade', 00332 array('quiz' => $quiz->id, 'userid' => $userid)); 00333 00334 // Need to detect errors/no result, without catching 0 grades. 00335 if ($grade === false) { 00336 return null; 00337 } 00338 00339 return $grade + 0; // Convert to number. 00340 } 00341 00350 function quiz_has_grades($quiz) { 00351 return $quiz->grade >= 0.000005 && $quiz->sumgrades >= 0.000005; 00352 } 00353 00367 function quiz_user_outline($course, $user, $mod, $quiz) { 00368 global $DB, $CFG; 00369 require_once("$CFG->libdir/gradelib.php"); 00370 $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id); 00371 00372 if (empty($grades->items[0]->grades)) { 00373 return null; 00374 } else { 00375 $grade = reset($grades->items[0]->grades); 00376 } 00377 00378 $result = new stdClass(); 00379 $result->info = get_string('grade') . ': ' . $grade->str_long_grade; 00380 00381 //datesubmitted == time created. dategraded == time modified or time overridden 00382 //if grade was last modified by the user themselves use date graded. Otherwise use 00383 // date submitted 00384 // TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704 00385 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) { 00386 $result->time = $grade->dategraded; 00387 } else { 00388 $result->time = $grade->datesubmitted; 00389 } 00390 00391 return $result; 00392 } 00393 00405 function quiz_user_complete($course, $user, $mod, $quiz) { 00406 global $DB, $CFG, $OUTPUT; 00407 require_once("$CFG->libdir/gradelib.php"); 00408 00409 $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id); 00410 if (!empty($grades->items[0]->grades)) { 00411 $grade = reset($grades->items[0]->grades); 00412 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade); 00413 if ($grade->str_feedback) { 00414 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback); 00415 } 00416 } 00417 00418 if ($attempts = $DB->get_records('quiz_attempts', 00419 array('userid' => $user->id, 'quiz' => $quiz->id), 'attempt')) { 00420 foreach ($attempts as $attempt) { 00421 echo get_string('attempt', 'quiz').' '.$attempt->attempt.': '; 00422 if ($attempt->timefinish == 0) { 00423 print_string('unfinished'); 00424 } else { 00425 echo quiz_format_grade($quiz, $attempt->sumgrades) . '/' . 00426 quiz_format_grade($quiz, $quiz->sumgrades); 00427 } 00428 echo ' - '.userdate($attempt->timemodified).'<br />'; 00429 } 00430 } else { 00431 print_string('noattempts', 'quiz'); 00432 } 00433 00434 return true; 00435 } 00436 00442 function quiz_cron() { 00443 global $DB, $CFG; 00444 00445 // First handle standard plugins. 00446 cron_execute_plugin_type('quiz', 'quiz reports'); 00447 00448 // The deal with any plugins that do it the legacy way. 00449 mtrace("Starting legacy quiz reports"); 00450 $timenow = time(); 00451 if ($reports = $DB->get_records_select('quiz_reports', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) { 00452 foreach ($reports as $report) { 00453 $cronfile = "$CFG->dirroot/mod/quiz/report/$report->name/cron.php"; 00454 if (file_exists($cronfile)) { 00455 include_once($cronfile); 00456 $cron_function = 'quiz_report_'.$report->name."_cron"; 00457 if (function_exists($cron_function)) { 00458 mtrace("Processing quiz report cron function $cron_function ...", ''); 00459 $pre_dbqueries = null; 00460 $pre_dbqueries = $DB->perf_get_queries(); 00461 $pre_time = microtime(1); 00462 if ($cron_function()) { 00463 $DB->set_field('quiz_reports', "lastcron", $timenow, array("id"=>$report->id)); 00464 } 00465 if (isset($pre_dbqueries)) { 00466 mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries"); 00467 mtrace("... used " . (microtime(1) - $pre_time) . " seconds"); 00468 } 00469 @set_time_limit(0); 00470 mtrace("done."); 00471 } 00472 } 00473 } 00474 } 00475 mtrace("Finished legacy quiz reports"); 00476 } 00477 00486 function quiz_get_user_attempts($quizid, $userid, $status = 'finished', $includepreviews = false) { 00487 global $DB; 00488 $status_condition = array( 00489 'all' => '', 00490 'finished' => ' AND timefinish > 0', 00491 'unfinished' => ' AND timefinish = 0' 00492 ); 00493 $previewclause = ''; 00494 if (!$includepreviews) { 00495 $previewclause = ' AND preview = 0'; 00496 } 00497 return $DB->get_records_select('quiz_attempts', 00498 'quiz = ? AND userid = ?' . $previewclause . $status_condition[$status], 00499 array($quizid, $userid), 'attempt ASC'); 00500 } 00501 00510 function quiz_get_user_grades($quiz, $userid = 0) { 00511 global $CFG, $DB; 00512 00513 $params = array($quiz->id); 00514 $usertest = ''; 00515 if ($userid) { 00516 $params[] = $userid; 00517 $usertest = 'AND u.id = ?'; 00518 } 00519 return $DB->get_records_sql(" 00520 SELECT 00521 u.id, 00522 u.id AS userid, 00523 qg.grade AS rawgrade, 00524 qg.timemodified AS dategraded, 00525 MAX(qa.timefinish) AS datesubmitted 00526 00527 FROM {user} u 00528 JOIN {quiz_grades} qg ON u.id = qg.userid 00529 JOIN {quiz_attempts} qa ON qa.quiz = qg.quiz AND qa.userid = u.id 00530 00531 WHERE qg.quiz = ? 00532 $usertest 00533 GROUP BY u.id, qg.grade, qg.timemodified", $params); 00534 } 00535 00543 function quiz_format_grade($quiz, $grade) { 00544 if (is_null($grade)) { 00545 return get_string('notyetgraded', 'quiz'); 00546 } 00547 return format_float($grade, $quiz->decimalpoints); 00548 } 00549 00557 function quiz_format_question_grade($quiz, $grade) { 00558 if (empty($quiz->questiondecimalpoints)) { 00559 $quiz->questiondecimalpoints = -1; 00560 } 00561 if ($quiz->questiondecimalpoints == -1) { 00562 return format_float($grade, $quiz->decimalpoints); 00563 } else { 00564 return format_float($grade, $quiz->questiondecimalpoints); 00565 } 00566 } 00567 00574 function quiz_update_grades($quiz, $userid = 0, $nullifnone = true) { 00575 global $CFG, $DB; 00576 require_once($CFG->libdir.'/gradelib.php'); 00577 00578 if ($quiz->grade == 0) { 00579 quiz_grade_item_update($quiz); 00580 00581 } else if ($grades = quiz_get_user_grades($quiz, $userid)) { 00582 quiz_grade_item_update($quiz, $grades); 00583 00584 } else if ($userid && $nullifnone) { 00585 $grade = new stdClass(); 00586 $grade->userid = $userid; 00587 $grade->rawgrade = null; 00588 quiz_grade_item_update($quiz, $grade); 00589 00590 } else { 00591 quiz_grade_item_update($quiz); 00592 } 00593 } 00594 00598 function quiz_upgrade_grades() { 00599 global $DB; 00600 00601 $sql = "SELECT COUNT('x') 00602 FROM {quiz} a, {course_modules} cm, {modules} m 00603 WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id"; 00604 $count = $DB->count_records_sql($sql); 00605 00606 $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid 00607 FROM {quiz} a, {course_modules} cm, {modules} m 00608 WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id"; 00609 $rs = $DB->get_recordset_sql($sql); 00610 if ($rs->valid()) { 00611 $pbar = new progress_bar('quizupgradegrades', 500, true); 00612 $i=0; 00613 foreach ($rs as $quiz) { 00614 $i++; 00615 upgrade_set_timeout(60*5); // set up timeout, may also abort execution 00616 quiz_update_grades($quiz, 0, false); 00617 $pbar->update($i, $count, "Updating Quiz grades ($i/$count)."); 00618 } 00619 } 00620 $rs->close(); 00621 } 00622 00630 function quiz_grade_item_update($quiz, $grades = null) { 00631 global $CFG, $OUTPUT; 00632 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 00633 require_once($CFG->libdir.'/gradelib.php'); 00634 00635 if (array_key_exists('cmidnumber', $quiz)) { // may not be always present 00636 $params = array('itemname' => $quiz->name, 'idnumber' => $quiz->cmidnumber); 00637 } else { 00638 $params = array('itemname' => $quiz->name); 00639 } 00640 00641 if ($quiz->grade > 0) { 00642 $params['gradetype'] = GRADE_TYPE_VALUE; 00643 $params['grademax'] = $quiz->grade; 00644 $params['grademin'] = 0; 00645 00646 } else { 00647 $params['gradetype'] = GRADE_TYPE_NONE; 00648 } 00649 00650 // description by TJ: 00651 // 1. If the quiz is set to not show grades while the quiz is still open, 00652 // and is set to show grades after the quiz is closed, then create the 00653 // grade_item with a show-after date that is the quiz close date. 00654 // 2. If the quiz is set to not show grades at either of those times, 00655 // create the grade_item as hidden. 00656 // 3. If the quiz is set to show grades, create the grade_item visible. 00657 $openreviewoptions = mod_quiz_display_options::make_from_quiz($quiz, 00658 mod_quiz_display_options::LATER_WHILE_OPEN); 00659 $closedreviewoptions = mod_quiz_display_options::make_from_quiz($quiz, 00660 mod_quiz_display_options::AFTER_CLOSE); 00661 if ($openreviewoptions->marks < question_display_options::MARK_AND_MAX && 00662 $closedreviewoptions->marks < question_display_options::MARK_AND_MAX) { 00663 $params['hidden'] = 1; 00664 00665 } else if ($openreviewoptions->marks < question_display_options::MARK_AND_MAX && 00666 $closedreviewoptions->marks >= question_display_options::MARK_AND_MAX) { 00667 if ($quiz->timeclose) { 00668 $params['hidden'] = $quiz->timeclose; 00669 } else { 00670 $params['hidden'] = 1; 00671 } 00672 00673 } else { 00674 // a) both open and closed enabled 00675 // b) open enabled, closed disabled - we can not "hide after", 00676 // grades are kept visible even after closing 00677 $params['hidden'] = 0; 00678 } 00679 00680 if ($grades === 'reset') { 00681 $params['reset'] = true; 00682 $grades = null; 00683 } 00684 00685 $gradebook_grades = grade_get_grades($quiz->course, 'mod', 'quiz', $quiz->id); 00686 if (!empty($gradebook_grades->items)) { 00687 $grade_item = $gradebook_grades->items[0]; 00688 if ($grade_item->locked) { 00689 $confirm_regrade = optional_param('confirm_regrade', 0, PARAM_INT); 00690 if (!$confirm_regrade) { 00691 $message = get_string('gradeitemislocked', 'grades'); 00692 $back_link = $CFG->wwwroot . '/mod/quiz/report.php?q=' . $quiz->id . 00693 '&mode=overview'; 00694 $regrade_link = qualified_me() . '&confirm_regrade=1'; 00695 echo $OUTPUT->box_start('generalbox', 'notice'); 00696 echo '<p>'. $message .'</p>'; 00697 echo $OUTPUT->container_start('buttons'); 00698 echo $OUTPUT->single_button($regrade_link, get_string('regradeanyway', 'grades')); 00699 echo $OUTPUT->single_button($back_link, get_string('cancel')); 00700 echo $OUTPUT->container_end(); 00701 echo $OUTPUT->box_end(); 00702 00703 return GRADE_UPDATE_ITEM_LOCKED; 00704 } 00705 } 00706 } 00707 00708 return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0, $grades, $params); 00709 } 00710 00717 function quiz_grade_item_delete($quiz) { 00718 global $CFG; 00719 require_once($CFG->libdir . '/gradelib.php'); 00720 00721 return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0, 00722 null, array('deleted' => 1)); 00723 } 00724 00733 function quiz_get_participants($quizid) { 00734 global $CFG, $DB; 00735 00736 return $DB->get_records_sql(' 00737 SELECT DISTINCT userid, userid 00738 JOIN {quiz_attempts} qa 00739 WHERE a.quiz = ?', array($quizid)); 00740 } 00741 00752 function quiz_refresh_events($courseid = 0) { 00753 global $DB; 00754 00755 if ($courseid == 0) { 00756 if (!$quizzes = $DB->get_records('quiz')) { 00757 return true; 00758 } 00759 } else { 00760 if (!$quizzes = $DB->get_records('quiz', array('course' => $courseid))) { 00761 return true; 00762 } 00763 } 00764 00765 foreach ($quizzes as $quiz) { 00766 quiz_update_events($quiz); 00767 } 00768 00769 return true; 00770 } 00771 00775 function quiz_get_recent_mod_activity(&$activities, &$index, $timestart, 00776 $courseid, $cmid, $userid = 0, $groupid = 0) { 00777 global $CFG, $COURSE, $USER, $DB; 00778 require_once('locallib.php'); 00779 00780 if ($COURSE->id == $courseid) { 00781 $course = $COURSE; 00782 } else { 00783 $course = $DB->get_record('course', array('id' => $courseid)); 00784 } 00785 00786 $modinfo =& get_fast_modinfo($course); 00787 00788 $cm = $modinfo->cms[$cmid]; 00789 $quiz = $DB->get_record('quiz', array('id' => $cm->instance)); 00790 00791 if ($userid) { 00792 $userselect = "AND u.id = :userid"; 00793 $params['userid'] = $userid; 00794 } else { 00795 $userselect = ''; 00796 } 00797 00798 if ($groupid) { 00799 $groupselect = 'AND gm.groupid = :groupid'; 00800 $groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id'; 00801 $params['groupid'] = $groupid; 00802 } else { 00803 $groupselect = ''; 00804 $groupjoin = ''; 00805 } 00806 00807 $params['timestart'] = $timestart; 00808 $params['quizid'] = $quiz->id; 00809 00810 if (!$attempts = $DB->get_records_sql(" 00811 SELECT qa.*, 00812 u.firstname, u.lastname, u.email, u.picture, u.imagealt 00813 FROM {quiz_attempts} qa 00814 JOIN {user} u ON u.id = qa.userid 00815 $groupjoin 00816 WHERE qa.timefinish > :timestart 00817 AND qa.quiz = :quizid 00818 AND qa.preview = 0 00819 $userselect 00820 $groupselect 00821 ORDER BY qa.timefinish ASC", $params)) { 00822 return; 00823 } 00824 00825 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 00826 $accessallgroups = has_capability('moodle/site:accessallgroups', $context); 00827 $viewfullnames = has_capability('moodle/site:viewfullnames', $context); 00828 $grader = has_capability('mod/quiz:viewreports', $context); 00829 $groupmode = groups_get_activity_groupmode($cm, $course); 00830 00831 if (is_null($modinfo->groups)) { 00832 // load all my groups and cache it in modinfo 00833 $modinfo->groups = groups_get_user_groups($course->id); 00834 } 00835 00836 $usersgroups = null; 00837 $aname = format_string($cm->name, true); 00838 foreach ($attempts as $attempt) { 00839 if ($attempt->userid != $USER->id) { 00840 if (!$grader) { 00841 // Grade permission required 00842 continue; 00843 } 00844 00845 if ($groupmode == SEPARATEGROUPS and !$accessallgroups) { 00846 if (is_null($usersgroups)) { 00847 $usersgroups = groups_get_all_groups($course->id, 00848 $attempt->userid, $cm->groupingid); 00849 if (is_array($usersgroups)) { 00850 $usersgroups = array_keys($usersgroups); 00851 } else { 00852 $usersgroups = array(); 00853 } 00854 } 00855 if (!array_intersect($usersgroups, $modinfo->groups[$cm->id])) { 00856 continue; 00857 } 00858 } 00859 } 00860 00861 $options = quiz_get_review_options($quiz, $attempt, $context); 00862 00863 $tmpactivity = new stdClass(); 00864 00865 $tmpactivity->type = 'quiz'; 00866 $tmpactivity->cmid = $cm->id; 00867 $tmpactivity->name = $aname; 00868 $tmpactivity->sectionnum = $cm->sectionnum; 00869 $tmpactivity->timestamp = $attempt->timefinish; 00870 00871 $tmpactivity->content->attemptid = $attempt->id; 00872 $tmpactivity->content->attempt = $attempt->attempt; 00873 if (quiz_has_grades($quiz) && $options->marks >= question_display_options::MARK_AND_MAX) { 00874 $tmpactivity->content->sumgrades = quiz_format_grade($quiz, $attempt->sumgrades); 00875 $tmpactivity->content->maxgrade = quiz_format_grade($quiz, $quiz->sumgrades); 00876 } else { 00877 $tmpactivity->content->sumgrades = null; 00878 $tmpactivity->content->maxgrade = null; 00879 } 00880 00881 $tmpactivity->user->id = $attempt->userid; 00882 $tmpactivity->user->firstname = $attempt->firstname; 00883 $tmpactivity->user->lastname = $attempt->lastname; 00884 $tmpactivity->user->fullname = fullname($attempt, $viewfullnames); 00885 $tmpactivity->user->picture = $attempt->picture; 00886 $tmpactivity->user->imagealt = $attempt->imagealt; 00887 $tmpactivity->user->email = $attempt->email; 00888 00889 $activities[$index++] = $tmpactivity; 00890 } 00891 } 00892 00893 function quiz_print_recent_mod_activity($activity, $courseid, $detail, $modnames) { 00894 global $CFG, $OUTPUT; 00895 00896 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">'; 00897 00898 echo '<tr><td class="userpicture" valign="top">'; 00899 echo $OUTPUT->user_picture($activity->user, array('courseid' => $courseid)); 00900 echo '</td><td>'; 00901 00902 if ($detail) { 00903 $modname = $modnames[$activity->type]; 00904 echo '<div class="title">'; 00905 echo '<img src="' . $OUTPUT->pix_url('icon', $activity->type) . '" ' . 00906 'class="icon" alt="' . $modname . '" />'; 00907 echo '<a href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' . 00908 $activity->cmid . '">' . $activity->name . '</a>'; 00909 echo '</div>'; 00910 } 00911 00912 echo '<div class="grade">'; 00913 echo get_string('attempt', 'quiz', $activity->content->attempt); 00914 if (isset($activity->content->maxgrade)) { 00915 $grades = $activity->content->sumgrades . ' / ' . $activity->content->maxgrade; 00916 echo ': (<a href="' . $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . 00917 $activity->content->attemptid . '">' . $grades . '</a>)'; 00918 } 00919 echo '</div>'; 00920 00921 echo '<div class="user">'; 00922 echo '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $activity->user->id . 00923 '&course=' . $courseid . '">' . $activity->user->fullname . 00924 '</a> - ' . userdate($activity->timestamp); 00925 echo '</div>'; 00926 00927 echo '</td></tr></table>'; 00928 00929 return; 00930 } 00931 00938 function quiz_process_options($quiz) { 00939 global $CFG; 00940 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 00941 require_once($CFG->libdir . '/questionlib.php'); 00942 00943 $quiz->timemodified = time(); 00944 00945 // Quiz name. 00946 if (!empty($quiz->name)) { 00947 $quiz->name = trim($quiz->name); 00948 } 00949 00950 // Password field - different in form to stop browsers that remember passwords 00951 // getting confused. 00952 $quiz->password = $quiz->quizpassword; 00953 unset($quiz->quizpassword); 00954 00955 // Quiz feedback 00956 if (isset($quiz->feedbacktext)) { 00957 // Clean up the boundary text. 00958 for ($i = 0; $i < count($quiz->feedbacktext); $i += 1) { 00959 if (empty($quiz->feedbacktext[$i]['text'])) { 00960 $quiz->feedbacktext[$i]['text'] = ''; 00961 } else { 00962 $quiz->feedbacktext[$i]['text'] = trim($quiz->feedbacktext[$i]['text']); 00963 } 00964 } 00965 00966 // Check the boundary value is a number or a percentage, and in range. 00967 $i = 0; 00968 while (!empty($quiz->feedbackboundaries[$i])) { 00969 $boundary = trim($quiz->feedbackboundaries[$i]); 00970 if (!is_numeric($boundary)) { 00971 if (strlen($boundary) > 0 && $boundary[strlen($boundary) - 1] == '%') { 00972 $boundary = trim(substr($boundary, 0, -1)); 00973 if (is_numeric($boundary)) { 00974 $boundary = $boundary * $quiz->grade / 100.0; 00975 } else { 00976 return get_string('feedbackerrorboundaryformat', 'quiz', $i + 1); 00977 } 00978 } 00979 } 00980 if ($boundary <= 0 || $boundary >= $quiz->grade) { 00981 return get_string('feedbackerrorboundaryoutofrange', 'quiz', $i + 1); 00982 } 00983 if ($i > 0 && $boundary >= $quiz->feedbackboundaries[$i - 1]) { 00984 return get_string('feedbackerrororder', 'quiz', $i + 1); 00985 } 00986 $quiz->feedbackboundaries[$i] = $boundary; 00987 $i += 1; 00988 } 00989 $numboundaries = $i; 00990 00991 // Check there is nothing in the remaining unused fields. 00992 if (!empty($quiz->feedbackboundaries)) { 00993 for ($i = $numboundaries; $i < count($quiz->feedbackboundaries); $i += 1) { 00994 if (!empty($quiz->feedbackboundaries[$i]) && 00995 trim($quiz->feedbackboundaries[$i]) != '') { 00996 return get_string('feedbackerrorjunkinboundary', 'quiz', $i + 1); 00997 } 00998 } 00999 } 01000 for ($i = $numboundaries + 1; $i < count($quiz->feedbacktext); $i += 1) { 01001 if (!empty($quiz->feedbacktext[$i]['text']) && 01002 trim($quiz->feedbacktext[$i]['text']) != '') { 01003 return get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1); 01004 } 01005 } 01006 // Needs to be bigger than $quiz->grade because of '<' test in quiz_feedback_for_grade(). 01007 $quiz->feedbackboundaries[-1] = $quiz->grade + 1; 01008 $quiz->feedbackboundaries[$numboundaries] = 0; 01009 $quiz->feedbackboundarycount = $numboundaries; 01010 } 01011 01012 // Combing the individual settings into the review columns. 01013 $quiz->reviewattempt = quiz_review_option_form_to_db($quiz, 'attempt'); 01014 $quiz->reviewcorrectness = quiz_review_option_form_to_db($quiz, 'correctness'); 01015 $quiz->reviewmarks = quiz_review_option_form_to_db($quiz, 'marks'); 01016 $quiz->reviewspecificfeedback = quiz_review_option_form_to_db($quiz, 'specificfeedback'); 01017 $quiz->reviewgeneralfeedback = quiz_review_option_form_to_db($quiz, 'generalfeedback'); 01018 $quiz->reviewrightanswer = quiz_review_option_form_to_db($quiz, 'rightanswer'); 01019 $quiz->reviewoverallfeedback = quiz_review_option_form_to_db($quiz, 'overallfeedback'); 01020 $quiz->reviewattempt |= mod_quiz_display_options::DURING; 01021 $quiz->reviewoverallfeedback &= ~mod_quiz_display_options::DURING; 01022 } 01023 01029 function quiz_review_option_form_to_db($fromform, $field) { 01030 static $times = array( 01031 'during' => mod_quiz_display_options::DURING, 01032 'immediately' => mod_quiz_display_options::IMMEDIATELY_AFTER, 01033 'open' => mod_quiz_display_options::LATER_WHILE_OPEN, 01034 'closed' => mod_quiz_display_options::AFTER_CLOSE, 01035 ); 01036 01037 $review = 0; 01038 foreach ($times as $whenname => $when) { 01039 $fieldname = $field . $whenname; 01040 if (isset($fromform->$fieldname)) { 01041 $review |= $when; 01042 unset($fromform->$fieldname); 01043 } 01044 } 01045 01046 return $review; 01047 } 01048 01055 function quiz_after_add_or_update($quiz) { 01056 global $DB; 01057 $cmid = $quiz->coursemodule; 01058 01059 // we need to use context now, so we need to make sure all needed info is already in db 01060 $DB->set_field('course_modules', 'instance', $quiz->id, array('id'=>$cmid)); 01061 $context = get_context_instance(CONTEXT_MODULE, $cmid); 01062 01063 // Save the feedback 01064 $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); 01065 01066 for ($i = 0; $i <= $quiz->feedbackboundarycount; $i++) { 01067 $feedback = new stdClass(); 01068 $feedback->quizid = $quiz->id; 01069 $feedback->feedbacktext = $quiz->feedbacktext[$i]['text']; 01070 $feedback->feedbacktextformat = $quiz->feedbacktext[$i]['format']; 01071 $feedback->mingrade = $quiz->feedbackboundaries[$i]; 01072 $feedback->maxgrade = $quiz->feedbackboundaries[$i - 1]; 01073 $feedback->id = $DB->insert_record('quiz_feedback', $feedback); 01074 $feedbacktext = file_save_draft_area_files((int)$quiz->feedbacktext[$i]['itemid'], 01075 $context->id, 'mod_quiz', 'feedback', $feedback->id, 01076 array('subdirs' => false, 'maxfiles' => -1, 'maxbytes' => 0), 01077 $quiz->feedbacktext[$i]['text']); 01078 $DB->set_field('quiz_feedback', 'feedbacktext', $feedbacktext, 01079 array('id' => $feedback->id)); 01080 } 01081 01082 // Store any settings belonging to the access rules. 01083 quiz_access_manager::save_settings($quiz); 01084 01085 // Update the events relating to this quiz. 01086 quiz_update_events($quiz); 01087 01088 //update related grade item 01089 quiz_grade_item_update($quiz); 01090 } 01091 01101 function quiz_update_events($quiz, $override = null) { 01102 global $DB; 01103 01104 // Load the old events relating to this quiz. 01105 $conds = array('modulename'=>'quiz', 01106 'instance'=>$quiz->id); 01107 if (!empty($override)) { 01108 // only load events for this override 01109 $conds['groupid'] = isset($override->groupid)? $override->groupid : 0; 01110 $conds['userid'] = isset($override->userid)? $override->userid : 0; 01111 } 01112 $oldevents = $DB->get_records('event', $conds); 01113 01114 // Now make a todo list of all that needs to be updated 01115 if (empty($override)) { 01116 // We are updating the primary settings for the quiz, so we 01117 // need to add all the overrides 01118 $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id)); 01119 // as well as the original quiz (empty override) 01120 $overrides[] = new stdClass(); 01121 } else { 01122 // Just do the one override 01123 $overrides = array($override); 01124 } 01125 01126 foreach ($overrides as $current) { 01127 $groupid = isset($current->groupid)? $current->groupid : 0; 01128 $userid = isset($current->userid)? $current->userid : 0; 01129 $timeopen = isset($current->timeopen)? $current->timeopen : $quiz->timeopen; 01130 $timeclose = isset($current->timeclose)? $current->timeclose : $quiz->timeclose; 01131 01132 // only add open/close events for an override if they differ from the quiz default 01133 $addopen = empty($current->id) || !empty($current->timeopen); 01134 $addclose = empty($current->id) || !empty($current->timeclose); 01135 01136 $event = new stdClass(); 01137 $event->description = $quiz->intro; 01138 // Events module won't show user events when the courseid is nonzero 01139 $event->courseid = ($userid) ? 0 : $quiz->course; 01140 $event->groupid = $groupid; 01141 $event->userid = $userid; 01142 $event->modulename = 'quiz'; 01143 $event->instance = $quiz->id; 01144 $event->timestart = $timeopen; 01145 $event->timeduration = max($timeclose - $timeopen, 0); 01146 $event->visible = instance_is_visible('quiz', $quiz); 01147 $event->eventtype = 'open'; 01148 01149 // Determine the event name 01150 if ($groupid) { 01151 $params = new stdClass(); 01152 $params->quiz = $quiz->name; 01153 $params->group = groups_get_group_name($groupid); 01154 if ($params->group === false) { 01155 // group doesn't exist, just skip it 01156 continue; 01157 } 01158 $eventname = get_string('overridegroupeventname', 'quiz', $params); 01159 } else if ($userid) { 01160 $params = new stdClass(); 01161 $params->quiz = $quiz->name; 01162 $eventname = get_string('overrideusereventname', 'quiz', $params); 01163 } else { 01164 $eventname = $quiz->name; 01165 } 01166 if ($addopen or $addclose) { 01167 if ($timeclose and $timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) { 01168 // Single event for the whole quiz. 01169 if ($oldevent = array_shift($oldevents)) { 01170 $event->id = $oldevent->id; 01171 } else { 01172 unset($event->id); 01173 } 01174 $event->name = $eventname; 01175 // calendar_event::create will reuse a db record if the id field is set 01176 calendar_event::create($event); 01177 } else { 01178 // Separate start and end events. 01179 $event->timeduration = 0; 01180 if ($timeopen && $addopen) { 01181 if ($oldevent = array_shift($oldevents)) { 01182 $event->id = $oldevent->id; 01183 } else { 01184 unset($event->id); 01185 } 01186 $event->name = $eventname.' ('.get_string('quizopens', 'quiz').')'; 01187 // calendar_event::create will reuse a db record if the id field is set 01188 calendar_event::create($event); 01189 } 01190 if ($timeclose && $addclose) { 01191 if ($oldevent = array_shift($oldevents)) { 01192 $event->id = $oldevent->id; 01193 } else { 01194 unset($event->id); 01195 } 01196 $event->name = $eventname.' ('.get_string('quizcloses', 'quiz').')'; 01197 $event->timestart = $timeclose; 01198 $event->eventtype = 'close'; 01199 calendar_event::create($event); 01200 } 01201 } 01202 } 01203 } 01204 01205 // Delete any leftover events 01206 foreach ($oldevents as $badevent) { 01207 $badevent = calendar_event::load($badevent); 01208 $badevent->delete(); 01209 } 01210 } 01211 01215 function quiz_get_view_actions() { 01216 return array('view', 'view all', 'report', 'review'); 01217 } 01218 01222 function quiz_get_post_actions() { 01223 return array('attempt', 'close attempt', 'preview', 'editquestions', 01224 'delete attempt', 'manualgrade'); 01225 } 01226 01231 function quiz_questions_in_use($questionids) { 01232 global $DB, $CFG; 01233 require_once($CFG->libdir . '/questionlib.php'); 01234 list($test, $params) = $DB->get_in_or_equal($questionids); 01235 return $DB->record_exists_select('quiz_question_instances', 01236 'question ' . $test, $params) || question_engine::questions_in_use( 01237 $questionids, new qubaid_join('{quiz_attempts} quiza', 01238 'quiza.uniqueid', 'quiza.preview = 0')); 01239 } 01240 01247 function quiz_reset_course_form_definition($mform) { 01248 $mform->addElement('header', 'quizheader', get_string('modulenameplural', 'quiz')); 01249 $mform->addElement('advcheckbox', 'reset_quiz_attempts', 01250 get_string('removeallquizattempts', 'quiz')); 01251 } 01252 01257 function quiz_reset_course_form_defaults($course) { 01258 return array('reset_quiz_attempts' => 1); 01259 } 01260 01267 function quiz_reset_gradebook($courseid, $type='') { 01268 global $CFG, $DB; 01269 01270 $quizzes = $DB->get_records_sql(" 01271 SELECT q.*, cm.idnumber as cmidnumber, q.course as courseid 01272 FROM {modules} m 01273 JOIN {course_modules} cm ON m.id = cm.module 01274 JOIN {quiz} q ON cm.instance = q.id 01275 WHERE m.name = 'quiz' AND cm.course = ?", array($courseid)); 01276 01277 foreach ($quizzes as $quiz) { 01278 quiz_grade_item_update($quiz, 'reset'); 01279 } 01280 } 01281 01292 function quiz_reset_userdata($data) { 01293 global $CFG, $DB; 01294 require_once($CFG->libdir.'/questionlib.php'); 01295 01296 $componentstr = get_string('modulenameplural', 'quiz'); 01297 $status = array(); 01298 01299 // Delete attempts. 01300 if (!empty($data->reset_quiz_attempts)) { 01301 require_once($CFG->libdir . '/questionlib.php'); 01302 01303 question_engine::delete_questions_usage_by_activities(new qubaid_join( 01304 '{quiz_attempts} quiza JOIN {quiz} quiz ON quiza.quiz = quiz.id', 01305 'quiza.uniqueid', 'quiz.course = :quizcourseid', 01306 array('quizcourseid' => $data->courseid))); 01307 01308 $DB->delete_records_select('quiz_attempts', 01309 'quiz IN (SELECT id FROM {quiz} WHERE course = ?)', array($data->courseid)); 01310 $status[] = array( 01311 'component' => $componentstr, 01312 'item' => get_string('attemptsdeleted', 'quiz'), 01313 'error' => false); 01314 01315 // Remove all grades from gradebook 01316 $DB->delete_records_select('quiz_grades', 01317 'quiz IN (SELECT id FROM {quiz} WHERE course = ?)', array($data->courseid)); 01318 if (empty($data->reset_gradebook_grades)) { 01319 quiz_reset_gradebook($data->courseid); 01320 } 01321 $status[] = array( 01322 'component' => $componentstr, 01323 'item' => get_string('gradesdeleted', 'quiz'), 01324 'error' => false); 01325 } 01326 01327 // Updating dates - shift may be negative too 01328 if ($data->timeshift) { 01329 shift_course_mod_dates('quiz', array('timeopen', 'timeclose'), 01330 $data->timeshift, $data->courseid); 01331 $status[] = array( 01332 'component' => $componentstr, 01333 'item' => get_string('openclosedatesupdated', 'quiz'), 01334 'error' => false); 01335 } 01336 01337 return $status; 01338 } 01339 01348 function quiz_check_file_access($attemptuniqueid, $questionid, $context = null) { 01349 global $USER, $DB, $CFG; 01350 require_once(dirname(__FILE__).'/attemptlib.php'); 01351 require_once(dirname(__FILE__).'/locallib.php'); 01352 01353 $attempt = $DB->get_record('quiz_attempts', array('uniqueid' => $attemptuniqueid)); 01354 $attemptobj = quiz_attempt::create($attempt->id); 01355 01356 // does question exist? 01357 if (!$question = $DB->get_record('question', array('id' => $questionid))) { 01358 return false; 01359 } 01360 01361 if ($context === null) { 01362 $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz)); 01363 $cm = get_coursemodule_from_id('quiz', $quiz->id); 01364 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 01365 } 01366 01367 // Load those questions and the associated states. 01368 $attemptobj->load_questions(array($questionid)); 01369 $attemptobj->load_question_states(array($questionid)); 01370 01371 // obtain state 01372 $state = $attemptobj->get_question_state($questionid); 01373 // obtain questoin 01374 $question = $attemptobj->get_question($questionid); 01375 01376 // access granted if the current user submitted this file 01377 if ($attempt->userid != $USER->id) { 01378 return false; 01379 } 01380 // access granted if the current user has permission to grade quizzes in this course 01381 if (!(has_capability('mod/quiz:viewreports', $context) || 01382 has_capability('mod/quiz:grade', $context))) { 01383 return false; 01384 } 01385 01386 return array($question, $state, array()); 01387 } 01388 01394 function quiz_print_overview($courses, &$htmlarray) { 01395 global $USER, $CFG; 01396 // These next 6 Lines are constant in all modules (just change module name) 01397 if (empty($courses) || !is_array($courses) || count($courses) == 0) { 01398 return array(); 01399 } 01400 01401 if (!$quizzes = get_all_instances_in_courses('quiz', $courses)) { 01402 return; 01403 } 01404 01405 // Fetch some language strings outside the main loop. 01406 $strquiz = get_string('modulename', 'quiz'); 01407 $strnoattempts = get_string('noattempts', 'quiz'); 01408 01409 // We want to list quizzes that are currently available, and which have a close date. 01410 // This is the same as what the lesson does, and the dabate is in MDL-10568. 01411 $now = time(); 01412 foreach ($quizzes as $quiz) { 01413 if ($quiz->timeclose >= $now && $quiz->timeopen < $now) { 01414 // Give a link to the quiz, and the deadline. 01415 $str = '<div class="quiz overview">' . 01416 '<div class="name">' . $strquiz . ': <a ' . 01417 ($quiz->visible ? '' : ' class="dimmed"') . 01418 ' href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' . 01419 $quiz->coursemodule . '">' . 01420 $quiz->name . '</a></div>'; 01421 $str .= '<div class="info">' . get_string('quizcloseson', 'quiz', 01422 userdate($quiz->timeclose)) . '</div>'; 01423 01424 // Now provide more information depending on the uers's role. 01425 $context = get_context_instance(CONTEXT_MODULE, $quiz->coursemodule); 01426 if (has_capability('mod/quiz:viewreports', $context)) { 01427 // For teacher-like people, show a summary of the number of student attempts. 01428 // The $quiz objects returned by get_all_instances_in_course have the necessary $cm 01429 // fields set to make the following call work. 01430 $str .= '<div class="info">' . 01431 quiz_num_attempt_summary($quiz, $quiz, true) . '</div>'; 01432 } else if (has_any_capability(array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), 01433 $context)) { // Student 01434 // For student-like people, tell them how many attempts they have made. 01435 if (isset($USER->id) && 01436 ($attempts = quiz_get_user_attempts($quiz->id, $USER->id))) { 01437 $numattempts = count($attempts); 01438 $str .= '<div class="info">' . 01439 get_string('numattemptsmade', 'quiz', $numattempts) . '</div>'; 01440 } else { 01441 $str .= '<div class="info">' . $strnoattempts . '</div>'; 01442 } 01443 } else { 01444 // For ayone else, there is no point listing this quiz, so stop processing. 01445 continue; 01446 } 01447 01448 // Add the output for this quiz to the rest. 01449 $str .= '</div>'; 01450 if (empty($htmlarray[$quiz->course]['quiz'])) { 01451 $htmlarray[$quiz->course]['quiz'] = $str; 01452 } else { 01453 $htmlarray[$quiz->course]['quiz'] .= $str; 01454 } 01455 } 01456 } 01457 } 01458 01473 function quiz_num_attempt_summary($quiz, $cm, $returnzero = false, $currentgroup = 0) { 01474 global $DB, $USER; 01475 $numattempts = $DB->count_records('quiz_attempts', array('quiz'=> $quiz->id, 'preview'=>0)); 01476 if ($numattempts || $returnzero) { 01477 if (groups_get_activity_groupmode($cm)) { 01478 $a = new stdClass(); 01479 $a->total = $numattempts; 01480 if ($currentgroup) { 01481 $a->group = $DB->count_records_sql('SELECT COUNT(DISTINCT qa.id) FROM ' . 01482 '{quiz_attempts} qa JOIN ' . 01483 '{groups_members} gm ON qa.userid = gm.userid ' . 01484 'WHERE quiz = ? AND preview = 0 AND groupid = ?', 01485 array($quiz->id, $currentgroup)); 01486 return get_string('attemptsnumthisgroup', 'quiz', $a); 01487 } else if ($groups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid)) { 01488 list($usql, $params) = $DB->get_in_or_equal(array_keys($groups)); 01489 $a->group = $DB->count_records_sql('SELECT COUNT(DISTINCT qa.id) FROM ' . 01490 '{quiz_attempts} qa JOIN ' . 01491 '{groups_members} gm ON qa.userid = gm.userid ' . 01492 'WHERE quiz = ? AND preview = 0 AND ' . 01493 "groupid $usql", array_merge(array($quiz->id), $params)); 01494 return get_string('attemptsnumyourgroups', 'quiz', $a); 01495 } 01496 } 01497 return get_string('attemptsnum', 'quiz', $numattempts); 01498 } 01499 return ''; 01500 } 01501 01516 function quiz_attempt_summary_link_to_reports($quiz, $cm, $context, $returnzero = false, 01517 $currentgroup = 0) { 01518 global $CFG; 01519 $summary = quiz_num_attempt_summary($quiz, $cm, $returnzero, $currentgroup); 01520 if (!$summary) { 01521 return ''; 01522 } 01523 01524 require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php'); 01525 $url = new moodle_url('/mod/quiz/report.php', array( 01526 'id' => $cm->id, 'mode' => quiz_report_default_report($context))); 01527 return html_writer::link($url, $summary); 01528 } 01529 01534 function quiz_supports($feature) { 01535 switch($feature) { 01536 case FEATURE_GROUPS: return true; 01537 case FEATURE_GROUPINGS: return true; 01538 case FEATURE_GROUPMEMBERSONLY: return true; 01539 case FEATURE_MOD_INTRO: return true; 01540 case FEATURE_COMPLETION_TRACKS_VIEWS: return true; 01541 case FEATURE_GRADE_HAS_GRADE: return true; 01542 case FEATURE_GRADE_OUTCOMES: return false; 01543 case FEATURE_BACKUP_MOODLE2: return true; 01544 case FEATURE_SHOW_DESCRIPTION: return true; 01545 01546 default: return null; 01547 } 01548 } 01549 01553 function quiz_get_extra_capabilities() { 01554 global $CFG; 01555 require_once($CFG->libdir.'/questionlib.php'); 01556 $caps = question_get_all_capabilities(); 01557 $caps[] = 'moodle/site:accessallgroups'; 01558 return $caps; 01559 } 01560 01572 function quiz_extend_navigation($quiznode, $course, $module, $cm) { 01573 global $CFG; 01574 01575 $context = get_context_instance(CONTEXT_MODULE, $cm->id); 01576 01577 if (has_capability('mod/quiz:view', $context)) { 01578 $url = new moodle_url('/mod/quiz/view.php', array('id'=>$cm->id)); 01579 $quiznode->add(get_string('info', 'quiz'), $url, navigation_node::TYPE_SETTING, 01580 null, null, new pix_icon('i/info', '')); 01581 } 01582 01583 if (has_any_capability(array('mod/quiz:viewreports', 'mod/quiz:grade'), $context)) { 01584 require_once($CFG->dirroot.'/mod/quiz/report/reportlib.php'); 01585 $reportlist = quiz_report_list($context); 01586 01587 $url = new moodle_url('/mod/quiz/report.php', 01588 array('id' => $cm->id, 'mode' => reset($reportlist))); 01589 $reportnode = $quiznode->add(get_string('results', 'quiz'), $url, 01590 navigation_node::TYPE_SETTING, 01591 null, null, new pix_icon('i/report', '')); 01592 01593 foreach ($reportlist as $report) { 01594 $url = new moodle_url('/mod/quiz/report.php', 01595 array('id' => $cm->id, 'mode' => $report)); 01596 $reportnode->add(get_string($report, 'quiz_'.$report), $url, 01597 navigation_node::TYPE_SETTING, 01598 null, 'quiz_report_' . $report, new pix_icon('i/item', '')); 01599 } 01600 } 01601 } 01602 01612 function quiz_extend_settings_navigation($settings, $quiznode) { 01613 global $PAGE, $CFG; 01614 01619 require_once($CFG->libdir . '/questionlib.php'); 01620 01621 // We want to add these new nodes after the Edit settings node, and before the 01622 // Locally assigned roles node. Of course, both of those are controlled by capabilities. 01623 $keys = $quiznode->get_children_key_list(); 01624 $beforekey = null; 01625 $i = array_search('modedit', $keys); 01626 if ($i === false and array_key_exists(0, $keys)) { 01627 $beforekey = $keys[0]; 01628 } else if (array_key_exists($i + 1, $keys)) { 01629 $beforekey = $keys[$i + 1]; 01630 } 01631 01632 if (has_capability('mod/quiz:manageoverrides', $PAGE->cm->context)) { 01633 $url = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$PAGE->cm->id)); 01634 $node = navigation_node::create(get_string('groupoverrides', 'quiz'), 01635 new moodle_url($url, array('mode'=>'group')), 01636 navigation_node::TYPE_SETTING, null, 'mod_quiz_groupoverrides'); 01637 $quiznode->add_node($node, $beforekey); 01638 01639 $node = navigation_node::create(get_string('useroverrides', 'quiz'), 01640 new moodle_url($url, array('mode'=>'user')), 01641 navigation_node::TYPE_SETTING, null, 'mod_quiz_useroverrides'); 01642 $quiznode->add_node($node, $beforekey); 01643 } 01644 01645 if (has_capability('mod/quiz:manage', $PAGE->cm->context)) { 01646 $node = navigation_node::create(get_string('editquiz', 'quiz'), 01647 new moodle_url('/mod/quiz/edit.php', array('cmid'=>$PAGE->cm->id)), 01648 navigation_node::TYPE_SETTING, null, 'mod_quiz_edit', 01649 new pix_icon('t/edit', '')); 01650 $quiznode->add_node($node, $beforekey); 01651 } 01652 01653 if (has_capability('mod/quiz:preview', $PAGE->cm->context)) { 01654 $url = new moodle_url('/mod/quiz/startattempt.php', 01655 array('cmid'=>$PAGE->cm->id, 'sesskey'=>sesskey())); 01656 $node = navigation_node::create(get_string('preview', 'quiz'), $url, 01657 navigation_node::TYPE_SETTING, null, 'mod_quiz_preview', 01658 new pix_icon('t/preview', '')); 01659 $quiznode->add_node($node, $beforekey); 01660 } 01661 01662 question_extend_settings_navigation($quiznode, $PAGE->cm->context)->trim_if_empty(); 01663 } 01664 01676 function quiz_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) { 01677 global $CFG, $DB; 01678 01679 if ($context->contextlevel != CONTEXT_MODULE) { 01680 return false; 01681 } 01682 01683 require_login($course, false, $cm); 01684 01685 if (!$quiz = $DB->get_record('quiz', array('id'=>$cm->instance))) { 01686 return false; 01687 } 01688 01689 // 'intro' area is served by pluginfile.php 01690 $fileareas = array('feedback'); 01691 if (!in_array($filearea, $fileareas)) { 01692 return false; 01693 } 01694 01695 $feedbackid = (int)array_shift($args); 01696 if (!$feedback = $DB->get_record('quiz_feedback', array('id'=>$feedbackid))) { 01697 return false; 01698 } 01699 01700 $fs = get_file_storage(); 01701 $relativepath = implode('/', $args); 01702 $fullpath = "/$context->id/mod_quiz/$filearea/$feedbackid/$relativepath"; 01703 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 01704 return false; 01705 } 01706 send_stored_file($file, 0, 0, true); 01707 } 01708 01721 function mod_quiz_question_pluginfile($course, $context, $component, 01722 $filearea, $qubaid, $slot, $args, $forcedownload) { 01723 global $CFG; 01724 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 01725 01726 $attemptobj = quiz_attempt::create_from_usage_id($qubaid); 01727 require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); 01728 01729 if ($attemptobj->is_own_attempt() && !$attemptobj->is_finished()) { 01730 // In the middle of an attempt. 01731 if (!$attemptobj->is_preview_user()) { 01732 $attemptobj->require_capability('mod/quiz:attempt'); 01733 } 01734 $isreviewing = false; 01735 01736 } else { 01737 // Reviewing an attempt. 01738 $attemptobj->check_review_capability(); 01739 $isreviewing = true; 01740 } 01741 01742 if (!$attemptobj->check_file_access($slot, $isreviewing, $context->id, 01743 $component, $filearea, $args, $forcedownload)) { 01744 send_file_not_found(); 01745 } 01746 01747 $fs = get_file_storage(); 01748 $relativepath = implode('/', $args); 01749 $fullpath = "/$context->id/$component/$filearea/$relativepath"; 01750 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 01751 send_file_not_found(); 01752 } 01753 01754 send_stored_file($file, 0, 0, $forcedownload); 01755 } 01756 01763 function quiz_page_type_list($pagetype, $parentcontext, $currentcontext) { 01764 $module_pagetype = array( 01765 'mod-quiz-*'=>get_string('page-mod-quiz-x', 'quiz'), 01766 'mod-quiz-edit'=>get_string('page-mod-quiz-edit', 'quiz')); 01767 return $module_pagetype; 01768 }