See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This file defines the quiz grades table. 19 * 20 * @package quiz_overview 21 * @copyright 2008 Jamie Pratt 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 require_once($CFG->dirroot . '/mod/quiz/report/attemptsreport_table.php'); 29 30 31 /** 32 * This is a table subclass for displaying the quiz grades report. 33 * 34 * @copyright 2008 Jamie Pratt 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class quiz_overview_table extends quiz_attempts_report_table { 38 39 protected $regradedqs = array(); 40 41 /** 42 * Constructor 43 * @param object $quiz 44 * @param context $context 45 * @param string $qmsubselect 46 * @param quiz_overview_options $options 47 * @param \core\dml\sql_join $groupstudentsjoins 48 * @param \core\dml\sql_join $studentsjoins 49 * @param array $questions 50 * @param moodle_url $reporturl 51 */ 52 public function __construct($quiz, $context, $qmsubselect, 53 quiz_overview_options $options, \core\dml\sql_join $groupstudentsjoins, 54 \core\dml\sql_join $studentsjoins, $questions, $reporturl) { 55 parent::__construct('mod-quiz-report-overview-report', $quiz , $context, 56 $qmsubselect, $options, $groupstudentsjoins, $studentsjoins, $questions, $reporturl); 57 } 58 59 public function build_table() { 60 global $DB; 61 62 if (!$this->rawdata) { 63 return; 64 } 65 66 $this->strtimeformat = str_replace(',', ' ', get_string('strftimedatetime')); 67 parent::build_table(); 68 69 // End of adding the data from attempts. Now add averages at bottom. 70 $this->add_separator(); 71 72 if (!empty($this->groupstudentsjoins->joins)) { 73 $sql = "SELECT DISTINCT u.id 74 FROM {user} u 75 {$this->groupstudentsjoins->joins} 76 WHERE {$this->groupstudentsjoins->wheres}"; 77 $groupstudents = $DB->get_records_sql($sql, $this->groupstudentsjoins->params); 78 if ($groupstudents) { 79 $this->add_average_row(get_string('groupavg', 'grades'), $this->groupstudentsjoins); 80 } 81 } 82 83 if (!empty($this->studentsjoins->joins)) { 84 $sql = "SELECT DISTINCT u.id 85 FROM {user} u 86 {$this->studentsjoins->joins} 87 WHERE {$this->studentsjoins->wheres}"; 88 $students = $DB->get_records_sql($sql, $this->studentsjoins->params); 89 if ($students) { 90 $this->add_average_row(get_string('overallaverage', 'grades'), $this->studentsjoins); 91 } 92 } 93 } 94 95 /** 96 * Calculate the average overall and question scores for a set of attempts at the quiz. 97 * 98 * @param string $label the title ot use for this row. 99 * @param \core\dml\sql_join $usersjoins to indicate a set of users. 100 * @return array of table cells that make up the average row. 101 */ 102 public function compute_average_row($label, \core\dml\sql_join $usersjoins) { 103 global $DB; 104 105 list($fields, $from, $where, $params) = $this->base_sql($usersjoins); 106 $record = $DB->get_record_sql(" 107 SELECT AVG(quizaouter.sumgrades) AS grade, COUNT(quizaouter.sumgrades) AS numaveraged 108 FROM {quiz_attempts} quizaouter 109 JOIN ( 110 SELECT DISTINCT quiza.id 111 FROM $from 112 WHERE $where 113 ) relevant_attempt_ids ON quizaouter.id = relevant_attempt_ids.id 114 ", $params); 115 $record->grade = quiz_rescale_grade($record->grade, $this->quiz, false); 116 if ($this->is_downloading()) { 117 $namekey = 'lastname'; 118 } else { 119 $namekey = 'fullname'; 120 } 121 $averagerow = array( 122 $namekey => $label, 123 'sumgrades' => $this->format_average($record), 124 'feedbacktext' => strip_tags(quiz_report_feedback_for_grade( 125 $record->grade, $this->quiz->id, $this->context)) 126 ); 127 128 if ($this->options->slotmarks) { 129 $dm = new question_engine_data_mapper(); 130 $qubaids = new qubaid_join("{quiz_attempts} quizaouter 131 JOIN ( 132 SELECT DISTINCT quiza.id 133 FROM $from 134 WHERE $where 135 ) relevant_attempt_ids ON quizaouter.id = relevant_attempt_ids.id", 136 'quizaouter.uniqueid', '1 = 1', $params); 137 $avggradebyq = $dm->load_average_marks($qubaids, array_keys($this->questions)); 138 139 $averagerow += $this->format_average_grade_for_questions($avggradebyq); 140 } 141 142 return $averagerow; 143 } 144 145 /** 146 * Add an average grade row for a set of users. 147 * 148 * @param string $label the title ot use for this row. 149 * @param \core\dml\sql_join $usersjoins (joins, wheres, params) for the users to average over. 150 */ 151 protected function add_average_row($label, \core\dml\sql_join $usersjoins) { 152 $averagerow = $this->compute_average_row($label, $usersjoins); 153 $this->add_data_keyed($averagerow); 154 } 155 156 /** 157 * Helper userd by {@link add_average_row()}. 158 * @param array $gradeaverages the raw grades. 159 * @return array the (partial) row of data. 160 */ 161 protected function format_average_grade_for_questions($gradeaverages) { 162 $row = array(); 163 164 if (!$gradeaverages) { 165 $gradeaverages = array(); 166 } 167 168 foreach ($this->questions as $question) { 169 if (isset($gradeaverages[$question->slot]) && $question->maxmark > 0) { 170 $record = $gradeaverages[$question->slot]; 171 $record->grade = quiz_rescale_grade( 172 $record->averagefraction * $question->maxmark, $this->quiz, false); 173 174 } else { 175 $record = new stdClass(); 176 $record->grade = null; 177 $record->numaveraged = 0; 178 } 179 180 $row['qsgrade' . $question->slot] = $this->format_average($record, true); 181 } 182 183 return $row; 184 } 185 186 /** 187 * Format an entry in an average row. 188 * @param object $record with fields grade and numaveraged. 189 * @param bool $question true if this is a question score, false if it is an overall score. 190 * @return string HTML fragment for an average score (with number of things included in the average). 191 */ 192 protected function format_average($record, $question = false) { 193 if (is_null($record->grade)) { 194 $average = '-'; 195 } else if ($question) { 196 $average = quiz_format_question_grade($this->quiz, $record->grade); 197 } else { 198 $average = quiz_format_grade($this->quiz, $record->grade); 199 } 200 201 if ($this->download) { 202 return $average; 203 } else if (is_null($record->numaveraged) || $record->numaveraged == 0) { 204 return html_writer::tag('span', html_writer::tag('span', 205 $average, array('class' => 'average')), array('class' => 'avgcell')); 206 } else { 207 return html_writer::tag('span', html_writer::tag('span', 208 $average, array('class' => 'average')) . ' ' . html_writer::tag('span', 209 '(' . $record->numaveraged . ')', array('class' => 'count')), 210 array('class' => 'avgcell')); 211 } 212 } 213 214 protected function submit_buttons() { 215 if (has_capability('mod/quiz:regrade', $this->context)) { 216 $regradebuttonparams = [ 217 'type' => 'submit', 218 'class' => 'btn btn-secondary mr-1', 219 'name' => 'regrade', 220 'value' => get_string('regradeselected', 'quiz_overview'), 221 'data-action' => 'toggle', 222 'data-togglegroup' => $this->togglegroup, 223 'data-toggle' => 'action', 224 'disabled' => true 225 ]; 226 echo html_writer::empty_tag('input', $regradebuttonparams); 227 } 228 parent::submit_buttons(); 229 } 230 231 public function col_sumgrades($attempt) { 232 if ($attempt->state != quiz_attempt::FINISHED) { 233 return '-'; 234 } 235 236 $grade = quiz_rescale_grade($attempt->sumgrades, $this->quiz); 237 if ($this->is_downloading()) { 238 return $grade; 239 } 240 241 if (isset($this->regradedqs[$attempt->usageid])) { 242 $newsumgrade = 0; 243 $oldsumgrade = 0; 244 foreach ($this->questions as $question) { 245 if (isset($this->regradedqs[$attempt->usageid][$question->slot])) { 246 $newsumgrade += $this->regradedqs[$attempt->usageid] 247 [$question->slot]->newfraction * $question->maxmark; 248 $oldsumgrade += $this->regradedqs[$attempt->usageid] 249 [$question->slot]->oldfraction * $question->maxmark; 250 } else { 251 $newsumgrade += $this->lateststeps[$attempt->usageid] 252 [$question->slot]->fraction * $question->maxmark; 253 $oldsumgrade += $this->lateststeps[$attempt->usageid] 254 [$question->slot]->fraction * $question->maxmark; 255 } 256 } 257 $newsumgrade = quiz_rescale_grade($newsumgrade, $this->quiz); 258 $oldsumgrade = quiz_rescale_grade($oldsumgrade, $this->quiz); 259 $grade = html_writer::tag('del', $oldsumgrade) . '/' . 260 html_writer::empty_tag('br') . $newsumgrade; 261 } 262 return html_writer::link(new moodle_url('/mod/quiz/review.php', 263 array('attempt' => $attempt->attempt)), $grade, 264 array('title' => get_string('reviewattempt', 'quiz'))); 265 } 266 267 /** 268 * @param string $colname the name of the column. 269 * @param object $attempt the row of data - see the SQL in display() in 270 * mod/quiz/report/overview/report.php to see what fields are present, 271 * and what they are called. 272 * @return string the contents of the cell. 273 */ 274 public function other_cols($colname, $attempt) { 275 if (!preg_match('/^qsgrade(\d+)$/', $colname, $matches)) { 276 return parent::other_cols($colname, $attempt); 277 } 278 $slot = $matches[1]; 279 280 $question = $this->questions[$slot]; 281 if (!isset($this->lateststeps[$attempt->usageid][$slot])) { 282 return '-'; 283 } 284 285 $stepdata = $this->lateststeps[$attempt->usageid][$slot]; 286 $state = question_state::get($stepdata->state); 287 288 if ($question->maxmark == 0) { 289 $grade = '-'; 290 } else if (is_null($stepdata->fraction)) { 291 if ($state == question_state::$needsgrading) { 292 $grade = get_string('requiresgrading', 'question'); 293 } else { 294 $grade = '-'; 295 } 296 } else { 297 $grade = quiz_rescale_grade( 298 $stepdata->fraction * $question->maxmark, $this->quiz, 'question'); 299 } 300 301 if ($this->is_downloading()) { 302 return $grade; 303 } 304 305 if (isset($this->regradedqs[$attempt->usageid][$slot])) { 306 $gradefromdb = $grade; 307 $newgrade = quiz_rescale_grade( 308 $this->regradedqs[$attempt->usageid][$slot]->newfraction * $question->maxmark, 309 $this->quiz, 'question'); 310 $oldgrade = quiz_rescale_grade( 311 $this->regradedqs[$attempt->usageid][$slot]->oldfraction * $question->maxmark, 312 $this->quiz, 'question'); 313 314 $grade = html_writer::tag('del', $oldgrade) . '/' . 315 html_writer::empty_tag('br') . $newgrade; 316 } 317 318 return $this->make_review_link($grade, $attempt, $slot); 319 } 320 321 public function col_regraded($attempt) { 322 if ($attempt->regraded == '') { 323 return ''; 324 } else if ($attempt->regraded == 0) { 325 return get_string('needed', 'quiz_overview'); 326 } else if ($attempt->regraded == 1) { 327 return get_string('done', 'quiz_overview'); 328 } 329 } 330 331 protected function update_sql_after_count($fields, $from, $where, $params) { 332 $fields .= ", COALESCE(( 333 SELECT MAX(qqr.regraded) 334 FROM {quiz_overview_regrades} qqr 335 WHERE qqr.questionusageid = quiza.uniqueid 336 ), -1) AS regraded"; 337 if ($this->options->onlyregraded) { 338 $where .= " AND COALESCE(( 339 SELECT MAX(qqr.regraded) 340 FROM {quiz_overview_regrades} qqr 341 WHERE qqr.questionusageid = quiza.uniqueid 342 ), -1) <> -1"; 343 } 344 return [$fields, $from, $where, $params]; 345 } 346 347 protected function requires_latest_steps_loaded() { 348 return $this->options->slotmarks; 349 } 350 351 protected function is_latest_step_column($column) { 352 if (preg_match('/^qsgrade([0-9]+)/', $column, $matches)) { 353 return $matches[1]; 354 } 355 return false; 356 } 357 358 protected function get_required_latest_state_fields($slot, $alias) { 359 return "$alias.fraction * $alias.maxmark AS qsgrade$slot"; 360 } 361 362 public function query_db($pagesize, $useinitialsbar = true) { 363 parent::query_db($pagesize, $useinitialsbar); 364 365 if ($this->options->slotmarks && has_capability('mod/quiz:regrade', $this->context)) { 366 $this->regradedqs = $this->get_regraded_questions(); 367 } 368 } 369 370 /** 371 * Get all the questions in all the attempts being displayed that need regrading. 372 * @return array A two dimensional array $questionusageid => $slot => $regradeinfo. 373 */ 374 protected function get_regraded_questions() { 375 global $DB; 376 377 $qubaids = $this->get_qubaids_condition(); 378 $regradedqs = $DB->get_records_select('quiz_overview_regrades', 379 'questionusageid ' . $qubaids->usage_id_in(), $qubaids->usage_id_in_params()); 380 return quiz_report_index_by_keys($regradedqs, array('questionusageid', 'slot')); 381 } 382 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body