|
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 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 require_once($CFG->dirroot.'/mod/quiz/report/attemptsreport.php'); 00030 require_once($CFG->dirroot.'/mod/quiz/report/overview/overviewsettings_form.php'); 00031 require_once($CFG->dirroot.'/mod/quiz/report/overview/overview_table.php'); 00032 00033 00040 class quiz_overview_report extends quiz_attempt_report { 00041 00042 public function display($quiz, $cm, $course) { 00043 global $CFG, $COURSE, $DB, $OUTPUT; 00044 00045 $this->context = get_context_instance(CONTEXT_MODULE, $cm->id); 00046 00047 $download = optional_param('download', '', PARAM_ALPHA); 00048 00049 list($currentgroup, $students, $groupstudents, $allowed) = 00050 $this->load_relevant_students($cm, $course); 00051 00052 $pageoptions = array(); 00053 $pageoptions['id'] = $cm->id; 00054 $pageoptions['mode'] = 'overview'; 00055 00056 $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions); 00057 $qmsubselect = quiz_report_qm_filter_select($quiz); 00058 00059 $mform = new mod_quiz_report_overview_settings($reporturl, 00060 array('qmsubselect' => $qmsubselect, 'quiz' => $quiz, 00061 'currentgroup' => $currentgroup, 'context' => $this->context)); 00062 00063 if ($fromform = $mform->get_data()) { 00064 $regradeall = false; 00065 $regradealldry = false; 00066 $regradealldrydo = false; 00067 $attemptsmode = $fromform->attemptsmode; 00068 if ($qmsubselect) { 00069 $qmfilter = $fromform->qmfilter; 00070 } else { 00071 $qmfilter = 0; 00072 } 00073 $regradefilter = !empty($fromform->regradefilter); 00074 set_user_preference('quiz_report_overview_detailedmarks', $fromform->detailedmarks); 00075 set_user_preference('quiz_report_pagesize', $fromform->pagesize); 00076 $detailedmarks = $fromform->detailedmarks; 00077 $pagesize = $fromform->pagesize; 00078 00079 } else { 00080 $regradeall = optional_param('regradeall', 0, PARAM_BOOL); 00081 $regradealldry = optional_param('regradealldry', 0, PARAM_BOOL); 00082 $regradealldrydo = optional_param('regradealldrydo', 0, PARAM_BOOL); 00083 $attemptsmode = optional_param('attemptsmode', null, PARAM_INT); 00084 if ($qmsubselect) { 00085 $qmfilter = optional_param('qmfilter', 0, PARAM_INT); 00086 } else { 00087 $qmfilter = 0; 00088 } 00089 $regradefilter = optional_param('regradefilter', 0, PARAM_INT); 00090 $detailedmarks = get_user_preferences('quiz_report_overview_detailedmarks', 1); 00091 $pagesize = get_user_preferences('quiz_report_pagesize', 0); 00092 } 00093 00094 $this->validate_common_options($attemptsmode, $pagesize, $course, $currentgroup); 00095 $displayoptions = array(); 00096 $displayoptions['attemptsmode'] = $attemptsmode; 00097 $displayoptions['qmfilter'] = $qmfilter; 00098 $displayoptions['regradefilter'] = $regradefilter; 00099 00100 $mform->set_data($displayoptions + 00101 array('detailedmarks' => $detailedmarks, 'pagesize' => $pagesize)); 00102 00103 if (!$this->should_show_grades($quiz)) { 00104 $detailedmarks = 0; 00105 } 00106 00107 // We only want to show the checkbox to delete attempts 00108 // if the user has permissions and if the report mode is showing attempts. 00109 $includecheckboxes = has_any_capability( 00110 array('mod/quiz:regrade', 'mod/quiz:deleteattempts'), $this->context) 00111 && ($attemptsmode != QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO); 00112 00113 if ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) { 00114 // This option is only available to users who can access all groups in 00115 // groups mode, so setting allowed to empty (which means all quiz attempts 00116 // are accessible, is not a security porblem. 00117 $allowed = array(); 00118 } 00119 00120 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); 00121 $courseshortname = format_string($course->shortname, true, 00122 array('context' => $coursecontext)); 00123 00124 $displaycoursecontext = get_context_instance(CONTEXT_COURSE, $COURSE->id); 00125 $displaycourseshortname = format_string($COURSE->shortname, true, 00126 array('context' => $displaycoursecontext)); 00127 00128 // Load the required questions. 00129 $questions = quiz_report_get_significant_questions($quiz); 00130 00131 $table = new quiz_report_overview_table($quiz, $this->context, $qmsubselect, 00132 $qmfilter, $attemptsmode, $groupstudents, $students, $detailedmarks, 00133 $questions, $includecheckboxes, $reporturl, $displayoptions); 00134 $filename = quiz_report_download_filename(get_string('overviewfilename', 'quiz_overview'), 00135 $courseshortname, $quiz->name); 00136 $table->is_downloading($download, $filename, 00137 $displaycourseshortname . ' ' . format_string($quiz->name, true)); 00138 if ($table->is_downloading()) { 00139 raise_memory_limit(MEMORY_EXTRA); 00140 } 00141 00142 // Process actions. 00143 if (empty($currentgroup) || $groupstudents) { 00144 if (optional_param('delete', 0, PARAM_BOOL) && confirm_sesskey()) { 00145 if ($attemptids = optional_param_array('attemptid', array(), PARAM_INT)) { 00146 require_capability('mod/quiz:deleteattempts', $this->context); 00147 $this->delete_selected_attempts($quiz, $cm, $attemptids, $allowed); 00148 redirect($reporturl->out(false, $displayoptions)); 00149 } 00150 00151 } else if (optional_param('regrade', 0, PARAM_BOOL) && confirm_sesskey()) { 00152 if ($attemptids = optional_param_array('attemptid', array(), PARAM_INT)) { 00153 require_capability('mod/quiz:regrade', $this->context); 00154 $this->regrade_attempts($quiz, false, $groupstudents, $attemptids); 00155 redirect($reporturl->out(false, $displayoptions)); 00156 } 00157 } 00158 } 00159 00160 if ($regradeall && confirm_sesskey()) { 00161 require_capability('mod/quiz:regrade', $this->context); 00162 $this->regrade_attempts($quiz, false, $groupstudents); 00163 redirect($reporturl->out(false, $displayoptions), '', 5); 00164 00165 } else if ($regradealldry && confirm_sesskey()) { 00166 require_capability('mod/quiz:regrade', $this->context); 00167 $this->regrade_attempts($quiz, true, $groupstudents); 00168 redirect($reporturl->out(false, $displayoptions), '', 5); 00169 00170 } else if ($regradealldrydo && confirm_sesskey()) { 00171 require_capability('mod/quiz:regrade', $this->context); 00172 $this->regrade_attempts_needing_it($quiz, $groupstudents); 00173 redirect($reporturl->out(false, $displayoptions), '', 5); 00174 } 00175 00176 // Start output. 00177 if (!$table->is_downloading()) { 00178 // Only print headers if not asked to download data 00179 $this->print_header_and_tabs($cm, $course, $quiz, 'overview'); 00180 } 00181 00182 if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used 00183 if (!$table->is_downloading()) { 00184 groups_print_activity_menu($cm, $reporturl->out(true, $displayoptions)); 00185 } 00186 } 00187 00188 // Print information on the number of existing attempts 00189 if (!$table->is_downloading()) { //do not print notices when downloading 00190 if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm, true, $currentgroup)) { 00191 echo '<div class="quizattemptcounts">' . $strattemptnum . '</div>'; 00192 } 00193 } 00194 00195 $hasquestions = quiz_questions_in_quiz($quiz->questions); 00196 if (!$table->is_downloading()) { 00197 if (!$hasquestions) { 00198 echo quiz_no_questions_message($quiz, $cm, $this->context); 00199 } else if (!$students) { 00200 echo $OUTPUT->notification(get_string('nostudentsyet')); 00201 } else if ($currentgroup && !$groupstudents) { 00202 echo $OUTPUT->notification(get_string('nostudentsingroup')); 00203 } 00204 00205 // Print display options 00206 $mform->display(); 00207 } 00208 00209 $hasstudents = $students && (!$currentgroup || $groupstudents); 00210 if ($hasquestions && ($hasstudents || ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL))) { 00211 // Construct the SQL 00212 $fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') . 00213 ' AS uniqueid, '; 00214 if ($qmsubselect) { 00215 $fields .= 00216 "(CASE " . 00217 " WHEN $qmsubselect THEN 1" . 00218 " ELSE 0 " . 00219 "END) AS gradedattempt, "; 00220 } 00221 00222 list($fields, $from, $where, $params) = $table->base_sql($allowed); 00223 00224 $table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params); 00225 00226 // Test to see if there are any regraded attempts to be listed. 00227 $fields .= ", COALESCE(( 00228 SELECT MAX(qqr.regraded) 00229 FROM {quiz_overview_regrades} qqr 00230 WHERE qqr.questionusageid = quiza.uniqueid 00231 ), -1) AS regraded"; 00232 if ($regradefilter) { 00233 $where .= " AND COALESCE(( 00234 SELECT MAX(qqr.regraded) 00235 FROM {quiz_overview_regrades} qqr 00236 WHERE qqr.questionusageid = quiza.uniqueid 00237 ), -1) <> -1"; 00238 } 00239 $table->set_sql($fields, $from, $where, $params); 00240 00241 if (!$table->is_downloading()) { 00242 // Regrade buttons 00243 if (has_capability('mod/quiz:regrade', $this->context)) { 00244 $regradesneeded = $this->count_question_attempts_needing_regrade( 00245 $quiz, $groupstudents); 00246 if ($currentgroup) { 00247 $a= new stdClass(); 00248 $a->groupname = groups_get_group_name($currentgroup); 00249 $a->coursestudents = get_string('participants'); 00250 $a->countregradeneeded = $regradesneeded; 00251 $regradealldrydolabel = 00252 get_string('regradealldrydogroup', 'quiz_overview', $a); 00253 $regradealldrylabel = 00254 get_string('regradealldrygroup', 'quiz_overview', $a); 00255 $regradealllabel = 00256 get_string('regradeallgroup', 'quiz_overview', $a); 00257 } else { 00258 $regradealldrydolabel = 00259 get_string('regradealldrydo', 'quiz_overview', $regradesneeded); 00260 $regradealldrylabel = 00261 get_string('regradealldry', 'quiz_overview'); 00262 $regradealllabel = 00263 get_string('regradeall', 'quiz_overview'); 00264 } 00265 $displayurl = new moodle_url($reporturl, 00266 $displayoptions + array('sesskey' => sesskey())); 00267 echo '<div class="mdl-align">'; 00268 echo '<form action="'.$displayurl->out_omit_querystring().'">'; 00269 echo '<div>'; 00270 echo html_writer::input_hidden_params($displayurl); 00271 echo '<input type="submit" name="regradeall" value="'.$regradealllabel.'"/>'; 00272 echo '<input type="submit" name="regradealldry" value="' . 00273 $regradealldrylabel . '"/>'; 00274 if ($regradesneeded) { 00275 echo '<input type="submit" name="regradealldrydo" value="' . 00276 $regradealldrydolabel . '"/>'; 00277 } 00278 echo '</div>'; 00279 echo '</form>'; 00280 echo '</div>'; 00281 } 00282 // Print information on the grading method 00283 if ($strattempthighlight = quiz_report_highlighting_grading_method( 00284 $quiz, $qmsubselect, $qmfilter)) { 00285 echo '<div class="quizattemptcounts">' . $strattempthighlight . '</div>'; 00286 } 00287 } 00288 00289 // Define table columns 00290 $columns = array(); 00291 $headers = array(); 00292 00293 if (!$table->is_downloading() && $includecheckboxes) { 00294 $columns[] = 'checkbox'; 00295 $headers[] = null; 00296 } 00297 00298 $this->add_user_columns($table, $columns, $headers); 00299 00300 $this->add_time_columns($columns, $headers); 00301 00302 if ($detailedmarks) { 00303 foreach ($questions as $slot => $question) { 00304 // Ignore questions of zero length 00305 $columns[] = 'qsgrade' . $slot; 00306 $header = get_string('qbrief', 'quiz', $question->number); 00307 if (!$table->is_downloading()) { 00308 $header .= '<br />'; 00309 } else { 00310 $header .= ' '; 00311 } 00312 $header .= '/' . quiz_rescale_grade($question->maxmark, $quiz, 'question'); 00313 $headers[] = $header; 00314 } 00315 } 00316 00317 if (!$table->is_downloading() && has_capability('mod/quiz:regrade', $this->context) && 00318 $this->has_regraded_questions($from, $where, $params)) { 00319 $columns[] = 'regraded'; 00320 $headers[] = get_string('regrade', 'quiz_overview'); 00321 } 00322 00323 $this->add_grade_columns($quiz, $columns, $headers, false); 00324 00325 $this->set_up_table_columns( 00326 $table, $columns, $headers, $reporturl, $displayoptions, false); 00327 $table->set_attribute('class', 'generaltable generalbox grades'); 00328 00329 $table->out($pagesize, true); 00330 } 00331 00332 if (!$table->is_downloading() && $this->should_show_grades($quiz)) { 00333 if ($currentgroup && $groupstudents) { 00334 list($usql, $params) = $DB->get_in_or_equal($groupstudents); 00335 $params[] = $quiz->id; 00336 if ($DB->record_exists_select('quiz_grades', "userid $usql AND quiz = ?", 00337 $params)) { 00338 $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php', 00339 array('id' => $quiz->id, 'groupid' => $currentgroup)); 00340 $graphname = get_string('overviewreportgraphgroup', 'quiz_overview', 00341 groups_get_group_name($currentgroup)); 00342 echo $OUTPUT->heading($graphname); 00343 echo html_writer::tag('div', html_writer::empty_tag('img', 00344 array('src' => $imageurl, 'alt' => $graphname)), 00345 array('class' => 'graph')); 00346 } 00347 } 00348 00349 if ($DB->record_exists('quiz_grades', array('quiz'=> $quiz->id))) { 00350 $graphname = get_string('overviewreportgraph', 'quiz_overview'); 00351 $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php', 00352 array('id' => $quiz->id)); 00353 echo $OUTPUT->heading($graphname); 00354 echo html_writer::tag('div', html_writer::empty_tag('img', 00355 array('src' => $imageurl, 'alt' => $graphname)), 00356 array('class' => 'graph')); 00357 } 00358 } 00359 return true; 00360 } 00361 00375 protected function regrade_attempt($attempt, $dryrun = false, $slots = null) { 00376 global $DB; 00377 00378 $transaction = $DB->start_delegated_transaction(); 00379 00380 $quba = question_engine::load_questions_usage_by_activity($attempt->uniqueid); 00381 00382 if (is_null($slots)) { 00383 $slots = $quba->get_slots(); 00384 } 00385 00386 $finished = $attempt->timefinish > 0; 00387 foreach ($slots as $slot) { 00388 $qqr = new stdClass(); 00389 $qqr->oldfraction = $quba->get_question_fraction($slot); 00390 00391 $quba->regrade_question($slot, $finished); 00392 00393 $qqr->newfraction = $quba->get_question_fraction($slot); 00394 00395 if (abs($qqr->oldfraction - $qqr->newfraction) > 1e-7) { 00396 $qqr->questionusageid = $quba->get_id(); 00397 $qqr->slot = $slot; 00398 $qqr->regraded = empty($dryrun); 00399 $qqr->timemodified = time(); 00400 $DB->insert_record('quiz_overview_regrades', $qqr, false); 00401 } 00402 } 00403 00404 if (!$dryrun) { 00405 question_engine::save_questions_usage_by_activity($quba); 00406 } 00407 00408 $transaction->allow_commit(); 00409 } 00410 00421 protected function regrade_attempts($quiz, $dryrun = false, 00422 $groupstudents = array(), $attemptids = array()) { 00423 global $DB; 00424 00425 $where = "quiz = ? AND preview = 0"; 00426 $params = array($quiz->id); 00427 00428 if ($groupstudents) { 00429 list($usql, $uparams) = $DB->get_in_or_equal($groupstudents); 00430 $where .= " AND userid $usql"; 00431 $params = array_merge($params, $uparams); 00432 } 00433 00434 if ($attemptids) { 00435 list($asql, $aparams) = $DB->get_in_or_equal($attemptids); 00436 $where .= " AND id $asql"; 00437 $params = array_merge($params, $aparams); 00438 } 00439 00440 $attempts = $DB->get_records_select('quiz_attempts', $where, $params); 00441 if (!$attempts) { 00442 return; 00443 } 00444 00445 $this->clear_regrade_table($quiz, $groupstudents); 00446 00447 foreach ($attempts as $attempt) { 00448 set_time_limit(30); 00449 $this->regrade_attempt($attempt, $dryrun); 00450 } 00451 00452 if (!$dryrun) { 00453 $this->update_overall_grades($quiz); 00454 } 00455 } 00456 00464 protected function regrade_attempts_needing_it($quiz, $groupstudents) { 00465 global $DB; 00466 00467 $where = "quiza.quiz = ? AND quiza.preview = 0 AND qqr.regraded = 0"; 00468 $params = array($quiz->id); 00469 00470 // Fetch all attempts that need regrading 00471 if ($groupstudents) { 00472 list($usql, $uparams) = $DB->get_in_or_equal($groupstudents); 00473 $where .= " AND quiza.userid $usql"; 00474 $params += $uparams; 00475 } 00476 00477 $toregrade = $DB->get_records_sql(" 00478 SELECT quiza.uniqueid, qqr.slot 00479 FROM {quiz_attempts} quiza 00480 JOIN {quiz_overview_regrades} qqr ON qqr.questionusageid = quiza.uniqueid 00481 WHERE $where", $params); 00482 00483 if (!$toregrade) { 00484 return; 00485 } 00486 00487 $attemptquestions = array(); 00488 foreach ($toregrade as $row) { 00489 $attemptquestions[$row->uniqueid][] = $row->slot; 00490 } 00491 $attempts = $DB->get_records_list('quiz_attempts', 'uniqueid', 00492 array_keys($attemptquestions)); 00493 00494 $this->clear_regrade_table($quiz, $groupstudents); 00495 00496 foreach ($attempts as $attempt) { 00497 set_time_limit(30); 00498 $this->regrade_attempt($attempt, false, $attemptquestions[$attempt->uniqueid]); 00499 } 00500 00501 $this->update_overall_grades($quiz); 00502 } 00503 00510 protected function count_question_attempts_needing_regrade($quiz, $groupstudents) { 00511 global $DB; 00512 00513 $usertest = ''; 00514 $params = array(); 00515 if ($groupstudents) { 00516 list($usql, $params) = $DB->get_in_or_equal($groupstudents); 00517 $usertest = "quiza.userid $usql AND "; 00518 } 00519 00520 $params[] = $quiz->id; 00521 $sql = "SELECT COUNT(DISTINCT quiza.id) 00522 FROM {quiz_attempts} quiza 00523 JOIN {quiz_overview_regrades} qqr ON quiza.uniqueid = qqr.questionusageid 00524 WHERE 00525 $usertest 00526 quiza.quiz = ? AND 00527 quiza.preview = 0 AND 00528 qqr.regraded = 0"; 00529 return $DB->count_records_sql($sql, $params); 00530 } 00531 00539 protected function has_regraded_questions($from, $where, $params) { 00540 global $DB; 00541 $qubaids = new qubaid_join($from, 'uniqueid', $where, $params); 00542 return $DB->record_exists_select('quiz_overview_regrades', 00543 'questionusageid ' . $qubaids->usage_id_in(), 00544 $qubaids->usage_id_in_params()); 00545 } 00546 00553 protected function clear_regrade_table($quiz, $groupstudents) { 00554 global $DB; 00555 00556 // Fetch all attempts that need regrading 00557 $where = ''; 00558 $params = array(); 00559 if ($groupstudents) { 00560 list($usql, $params) = $DB->get_in_or_equal($groupstudents); 00561 $where = "userid $usql AND "; 00562 } 00563 00564 $params[] = $quiz->id; 00565 $DB->delete_records_select('quiz_overview_regrades', 00566 "questionusageid IN ( 00567 SELECT uniqueid 00568 FROM {quiz_attempts} 00569 WHERE $where quiz = ? 00570 )", $params); 00571 } 00572 00580 protected function update_overall_grades($quiz) { 00581 quiz_update_all_attempt_sumgrades($quiz); 00582 quiz_update_all_final_grades($quiz); 00583 quiz_update_grades($quiz); 00584 } 00585 }