|
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/grading/gradingsettings_form.php'); 00030 00031 00042 class quiz_grading_report extends quiz_default_report { 00043 const DEFAULT_PAGE_SIZE = 5; 00044 const DEFAULT_ORDER = 'random'; 00045 00046 protected $viewoptions = array(); 00047 protected $questions; 00048 protected $currentgroup; 00049 protected $users; 00050 protected $cm; 00051 protected $quiz; 00052 protected $context; 00053 00054 public function display($quiz, $cm, $course) { 00055 global $CFG, $DB, $PAGE; 00056 00057 $this->quiz = $quiz; 00058 $this->cm = $cm; 00059 $this->course = $course; 00060 00061 // Get the URL options. 00062 $slot = optional_param('slot', null, PARAM_INT); 00063 $questionid = optional_param('qid', null, PARAM_INT); 00064 $grade = optional_param('grade', null, PARAM_ALPHA); 00065 00066 $includeauto = optional_param('includeauto', false, PARAM_BOOL); 00067 if (!in_array($grade, array('all', 'needsgrading', 'autograded', 'manuallygraded'))) { 00068 $grade = null; 00069 } 00070 $pagesize = optional_param('pagesize', self::DEFAULT_PAGE_SIZE, PARAM_INT); 00071 $page = optional_param('page', 0, PARAM_INT); 00072 $order = optional_param('order', self::DEFAULT_ORDER, PARAM_ALPHA); 00073 00074 // Assemble the options requried to reload this page. 00075 $optparams = array('includeauto', 'page'); 00076 foreach ($optparams as $param) { 00077 if ($$param) { 00078 $this->viewoptions[$param] = $$param; 00079 } 00080 } 00081 if ($pagesize != self::DEFAULT_PAGE_SIZE) { 00082 $this->viewoptions['pagesize'] = $pagesize; 00083 } 00084 if ($order != self::DEFAULT_ORDER) { 00085 $this->viewoptions['order'] = $order; 00086 } 00087 00088 // Check permissions 00089 $this->context = get_context_instance(CONTEXT_MODULE, $cm->id); 00090 require_capability('mod/quiz:grade', $this->context); 00091 $shownames = has_capability('quiz/grading:viewstudentnames', $this->context); 00092 $showidnumbers = has_capability('quiz/grading:viewidnumber', $this->context); 00093 00094 // Validate order. 00095 if (!in_array($order, array('random', 'date', 'student', 'idnumber'))) { 00096 $order = self::DEFAULT_ORDER; 00097 } else if (!$shownames && $order == 'student') { 00098 $order = self::DEFAULT_ORDER; 00099 } else if (!$showidnumbers && $order == 'idnumber') { 00100 $order = self::DEFAULT_ORDER; 00101 } 00102 if ($order == 'random') { 00103 $page = 0; 00104 } 00105 00106 // Get the list of questions in this quiz. 00107 $this->questions = quiz_report_get_significant_questions($quiz); 00108 if ($slot && !array_key_exists($slot, $this->questions)) { 00109 throw new moodle_exception('unknownquestion', 'quiz_grading'); 00110 } 00111 00112 // Process any submitted data. 00113 if ($data = data_submitted() && confirm_sesskey() && $this->validate_submitted_marks()) { 00114 $this->process_submitted_data(); 00115 00116 redirect($this->grade_question_url($slot, $questionid, $grade, $page + 1)); 00117 } 00118 00119 // Get the group, and the list of significant users. 00120 $this->currentgroup = $this->get_current_group($cm, $course, $this->context); 00121 if ($this->currentgroup == self::NO_GROUPS_ALLOWED) { 00122 $this->users = array(); 00123 } else { 00124 $this->users = get_users_by_capability($this->context, 00125 array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '', 00126 $this->currentgroup, '', false); 00127 } 00128 00129 // Start output. 00130 $this->print_header_and_tabs($cm, $course, $quiz, 'grading'); 00131 00132 // What sort of page to display? 00133 if (!quiz_questions_in_quiz($quiz->questions)) { 00134 echo quiz_no_questions_message($quiz, $cm, $this->context); 00135 00136 } else if (!$slot) { 00137 $this->display_index($includeauto); 00138 00139 } else { 00140 $this->display_grading_interface($slot, $questionid, $grade, 00141 $pagesize, $page, $shownames, $showidnumbers, $order); 00142 } 00143 return true; 00144 } 00145 00146 protected function get_qubaids_condition() { 00147 global $DB; 00148 00149 $where = "quiza.quiz = :mangrquizid AND 00150 quiza.preview = 0 AND 00151 quiza.timefinish <> 0"; 00152 $params = array('mangrquizid' => $this->cm->instance); 00153 00154 if ($this->currentgroup) { 00155 list($usql, $uparam) = $DB->get_in_or_equal(array_keys($this->users), 00156 SQL_PARAMS_NAMED, 'mangru'); 00157 $where .= ' AND quiza.userid ' . $usql; 00158 $params += $uparam; 00159 } 00160 00161 return new qubaid_join('{quiz_attempts} quiza', 'quiza.uniqueid', $where, $params); 00162 } 00163 00164 protected function load_attempts_by_usage_ids($qubaids) { 00165 global $DB; 00166 00167 list($asql, $params) = $DB->get_in_or_equal($qubaids); 00168 $params[] = $this->quiz->id; 00169 00170 $attemptsbyid = $DB->get_records_sql(" 00171 SELECT quiza.*, u.firstname, u.lastname, u.idnumber 00172 FROM {quiz_attempts} quiza 00173 JOIN {user} u ON u.id = quiza.userid 00174 WHERE quiza.uniqueid $asql AND quiza.timefinish <> 0 AND quiza.quiz = ?", 00175 $params); 00176 00177 $attempts = array(); 00178 foreach ($attemptsbyid as $attempt) { 00179 $attempts[$attempt->uniqueid] = $attempt; 00180 } 00181 return $attempts; 00182 } 00183 00190 protected function base_url() { 00191 return new moodle_url('/mod/quiz/report.php', 00192 array('id' => $this->cm->id, 'mode' => 'grading')); 00193 } 00194 00201 protected function list_questions_url($includeauto = null) { 00202 $url = $this->base_url(); 00203 00204 $url->params($this->viewoptions); 00205 00206 if (!is_null($includeauto)) { 00207 $url->param('includeauto', $includeauto); 00208 } 00209 00210 return $url; 00211 } 00212 00220 protected function grade_question_url($slot, $questionid, $grade, $page = true) { 00221 $url = $this->base_url(); 00222 $url->params(array('slot' => $slot, 'qid' => $questionid, 'grade' => $grade)); 00223 $url->params($this->viewoptions); 00224 00225 $options = $this->viewoptions; 00226 if (!$page) { 00227 $url->remove_params('page'); 00228 } else if (is_integer($page)) { 00229 $url->param('page', $page); 00230 } 00231 00232 return $url; 00233 } 00234 00235 protected function format_count_for_table($counts, $type, $gradestring) { 00236 $result = $counts->$type; 00237 if ($counts->$type > 0) { 00238 $result .= ' ' . html_writer::link($this->grade_question_url( 00239 $counts->slot, $counts->questionid, $type), 00240 get_string($gradestring, 'quiz_grading'), 00241 array('class' => 'gradetheselink')); 00242 } 00243 return $result; 00244 } 00245 00246 protected function display_index($includeauto) { 00247 global $OUTPUT; 00248 00249 if ($groupmode = groups_get_activity_groupmode($this->cm)) { 00250 // Groups are being used 00251 groups_print_activity_menu($this->cm, $this->list_questions_url()); 00252 } 00253 00254 echo $OUTPUT->heading(get_string('questionsthatneedgrading', 'quiz_grading')); 00255 if ($includeauto) { 00256 $linktext = get_string('hideautomaticallygraded', 'quiz_grading'); 00257 } else { 00258 $linktext = get_string('alsoshowautomaticallygraded', 'quiz_grading'); 00259 } 00260 echo html_writer::tag('p', html_writer::link($this->list_questions_url(!$includeauto), 00261 $linktext), array('class' => 'toggleincludeauto')); 00262 00263 $statecounts = $this->get_question_state_summary(array_keys($this->questions)); 00264 00265 $data = array(); 00266 foreach ($statecounts as $counts) { 00267 if ($counts->all == 0) { 00268 continue; 00269 } 00270 if (!$includeauto && $counts->needsgrading == 0 && $counts->manuallygraded == 0) { 00271 continue; 00272 } 00273 00274 $row = array(); 00275 00276 $row[] = $this->questions[$counts->slot]->number; 00277 00278 $row[] = format_string($counts->name); 00279 00280 $row[] = $this->format_count_for_table($counts, 'needsgrading', 'grade'); 00281 00282 $row[] = $this->format_count_for_table($counts, 'manuallygraded', 'updategrade'); 00283 00284 if ($includeauto) { 00285 $row[] = $this->format_count_for_table($counts, 'autograded', 'updategrade'); 00286 } 00287 00288 $row[] = $this->format_count_for_table($counts, 'all', 'gradeall'); 00289 00290 $data[] = $row; 00291 } 00292 00293 if (empty($data)) { 00294 echo $OUTPUT->heading(get_string('noquestionsfound', 'quiz_grading')); 00295 return; 00296 } 00297 00298 $table = new html_table(); 00299 $table->class = 'generaltable'; 00300 $table->id = 'questionstograde'; 00301 00302 $table->head[] = get_string('qno', 'quiz_grading'); 00303 $table->head[] = get_string('questionname', 'quiz_grading'); 00304 $table->head[] = get_string('tograde', 'quiz_grading'); 00305 $table->head[] = get_string('alreadygraded', 'quiz_grading'); 00306 if ($includeauto) { 00307 $table->head[] = get_string('automaticallygraded', 'quiz_grading'); 00308 } 00309 $table->head[] = get_string('total', 'quiz_grading'); 00310 00311 $table->data = $data; 00312 echo html_writer::table($table); 00313 } 00314 00315 protected function display_grading_interface($slot, $questionid, $grade, 00316 $pagesize, $page, $shownames, $showidnumbers, $order) { 00317 global $OUTPUT; 00318 00319 // Make sure there is something to do. 00320 $statecounts = $this->get_question_state_summary(array($slot)); 00321 00322 $counts = null; 00323 foreach ($statecounts as $record) { 00324 if ($record->questionid == $questionid) { 00325 $counts = $record; 00326 break; 00327 } 00328 } 00329 00330 // If not, redirect back to the list. 00331 if (!$counts || $counts->$grade == 0) { 00332 redirect($this->list_questions_url(), get_string('alldoneredirecting', 'quiz_grading')); 00333 } 00334 00335 if ($pagesize * $page >= $counts->$grade) { 00336 $page = 0; 00337 } 00338 00339 list($qubaids, $count) = $this->get_usage_ids_where_question_in_state( 00340 $grade, $slot, $questionid, $order, $page, $pagesize); 00341 $attempts = $this->load_attempts_by_usage_ids($qubaids); 00342 00343 // Prepare the form. 00344 $hidden = array( 00345 'id' => $this->cm->id, 00346 'mode' => 'grading', 00347 'slot' => $slot, 00348 'qid' => $questionid, 00349 'page' => $page, 00350 ); 00351 if (array_key_exists('includeauto', $this->viewoptions)) { 00352 $hidden['includeauto'] = $this->viewoptions['includeauto']; 00353 } 00354 $mform = new quiz_grading_settings($hidden, $counts, $shownames, $showidnumbers); 00355 00356 // Tell the form the current settings. 00357 $settings = new stdClass(); 00358 $settings->grade = $grade; 00359 $settings->pagesize = $pagesize; 00360 $settings->order = $order; 00361 $mform->set_data($settings); 00362 00363 // Print the heading and form. 00364 echo question_engine::initialise_js(); 00365 00366 $a = new stdClass(); 00367 $a->number = $this->questions[$slot]->number; 00368 $a->questionname = format_string($counts->name); 00369 echo $OUTPUT->heading(get_string('gradingquestionx', 'quiz_grading', $a)); 00370 echo html_writer::tag('p', html_writer::link($this->list_questions_url(), 00371 get_string('backtothelistofquestions', 'quiz_grading')), 00372 array('class' => 'mdl-align')); 00373 00374 $mform->display(); 00375 00376 // Paging info. 00377 $a = new stdClass(); 00378 $a->from = $page * $pagesize + 1; 00379 $a->to = min(($page + 1) * $pagesize, $count); 00380 $a->of = $count; 00381 echo $OUTPUT->heading(get_string('gradingattemptsxtoyofz', 'quiz_grading', $a), 3); 00382 00383 if ($count > $pagesize && $order != 'random') { 00384 echo $OUTPUT->paging_bar($count, $page, $pagesize, 00385 $this->grade_question_url($slot, $questionid, $grade, false)); 00386 } 00387 00388 // Display the form with one section for each attempt. 00389 $usehtmleditor = can_use_html_editor(); 00390 $sesskey = sesskey(); 00391 $qubaidlist = implode(',', $qubaids); 00392 echo html_writer::start_tag('form', array('method' => 'post', 00393 'action' => $this->grade_question_url($slot, $questionid, $grade, $page), 00394 'class' => 'mform', 'id' => 'manualgradingform')) . 00395 html_writer::start_tag('div') . 00396 html_writer::input_hidden_params(new moodle_url('', array( 00397 'qubaids' => $qubaidlist, 'slots' => $slot, 'sesskey' => $sesskey))); 00398 00399 foreach ($qubaids as $qubaid) { 00400 $attempt = $attempts[$qubaid]; 00401 $quba = question_engine::load_questions_usage_by_activity($qubaid); 00402 $displayoptions = quiz_get_review_options($this->quiz, $attempt, $this->context); 00403 $displayoptions->hide_all_feedback(); 00404 $displayoptions->history = question_display_options::HIDDEN; 00405 $displayoptions->manualcomment = question_display_options::EDITABLE; 00406 00407 $heading = $this->get_question_heading($attempt, $shownames, $showidnumbers); 00408 if ($heading) { 00409 echo $OUTPUT->heading($heading, 4); 00410 } 00411 echo $quba->render_question($slot, $displayoptions, $this->questions[$slot]->number); 00412 } 00413 00414 echo html_writer::tag('div', html_writer::empty_tag('input', array( 00415 'type' => 'submit', 'value' => get_string('saveandnext', 'quiz_grading'))), 00416 array('class' => 'mdl-align')) . 00417 html_writer::end_tag('div') . html_writer::end_tag('form'); 00418 } 00419 00420 protected function get_question_heading($attempt, $shownames, $showidnumbers) { 00421 $a = new stdClass(); 00422 $a->attempt = $attempt->attempt; 00423 $a->fullname = fullname($attempt); 00424 $a->idnumber = $attempt->idnumber; 00425 00426 $showidnumbers &= !empty($attempt->idnumber); 00427 00428 if ($shownames && $showidnumbers) { 00429 return get_string('gradingattemptwithidnumber', 'quiz_grading', $a); 00430 } else if ($shownames) { 00431 return get_string('gradingattempt', 'quiz_grading', $a); 00432 } else if ($showidnumbers) { 00433 $a->fullname = $attempt->idnumber; 00434 return get_string('gradingattempt', 'quiz_grading', $a); 00435 } else { 00436 return ''; 00437 } 00438 } 00439 00440 protected function validate_submitted_marks() { 00441 00442 $qubaids = optional_param('qubaids', null, PARAM_SEQUENCE); 00443 if (!$qubaids) { 00444 return false; 00445 } 00446 $qubaids = clean_param_array(explode(',', $qubaids), PARAM_INT); 00447 00448 $slots = optional_param('slots', '', PARAM_SEQUENCE); 00449 if (!$slots) { 00450 $slots = array(); 00451 } else { 00452 $slots = explode(',', $slots); 00453 } 00454 00455 foreach ($qubaids as $qubaid) { 00456 foreach ($slots as $slot) { 00457 $prefix = 'q' . $qubaid . ':' . $slot . '_'; 00458 $mark = optional_param($prefix . '-mark', null, PARAM_NUMBER); 00459 $maxmark = optional_param($prefix . '-maxmark', null, PARAM_NUMBER); 00460 $minfraction = optional_param($prefix . ':minfraction', null, PARAM_NUMBER); 00461 if (!is_null($mark) && ($mark < $minfraction * $maxmark || $mark > $maxmark)) { 00462 return false; 00463 } 00464 } 00465 } 00466 00467 return true; 00468 } 00469 00470 protected function process_submitted_data() { 00471 global $DB; 00472 00473 $qubaids = optional_param('qubaids', null, PARAM_SEQUENCE); 00474 if (!$qubaids) { 00475 return; 00476 } 00477 00478 $qubaids = clean_param_array(explode(',', $qubaids), PARAM_INT); 00479 $attempts = $this->load_attempts_by_usage_ids($qubaids); 00480 00481 $transaction = $DB->start_delegated_transaction(); 00482 foreach ($qubaids as $qubaid) { 00483 $attempt = $attempts[$qubaid]; 00484 $quba = question_engine::load_questions_usage_by_activity($qubaid); 00485 $attemptobj = new quiz_attempt($attempt, $this->quiz, $this->cm, $this->course); 00486 $attemptobj->process_all_actions(time()); 00487 } 00488 $transaction->allow_commit(); 00489 } 00490 00502 protected function get_question_state_summary($slots) { 00503 $dm = new question_engine_data_mapper(); 00504 return $dm->load_questions_usages_question_state_summary( 00505 $this->get_qubaids_condition(), $slots); 00506 } 00507 00525 protected function get_usage_ids_where_question_in_state($summarystate, $slot, 00526 $questionid = null, $orderby = 'random', $page = 0, $pagesize = null) { 00527 global $CFG, $DB; 00528 $dm = new question_engine_data_mapper(); 00529 00530 if ($pagesize && $orderby != 'random') { 00531 $limitfrom = $page * $pagesize; 00532 } else { 00533 $limitfrom = 0; 00534 } 00535 00536 $qubaids = $this->get_qubaids_condition(); 00537 00538 $params = array(); 00539 if ($orderby == 'date') { 00540 list($statetest, $params) = $dm->in_summary_state_test( 00541 'manuallygraded', false, 'mangrstate'); 00542 $orderby = "( 00543 SELECT MAX(sortqas.timecreated) 00544 FROM {question_attempt_steps} sortqas 00545 WHERE sortqas.questionattemptid = qa.id 00546 AND sortqas.state $statetest 00547 )"; 00548 } else if ($orderby == 'student' || $orderby == 'idnumber') { 00549 $qubaids->from .= " JOIN {user} u ON quiza.userid = u.id "; 00550 if ($orderby == 'student') { 00551 $orderby = $DB->sql_fullname('u.firstname', 'u.lastname'); 00552 } 00553 } 00554 00555 return $dm->load_questions_usages_where_question_in_state($qubaids, $summarystate, 00556 $slot, $questionid, $orderby, $params, $limitfrom, $pagesize); 00557 } 00558 }