See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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 $hasgroupstudents = $DB->record_exists_sql(" 74 SELECT 1 75 FROM {user} u 76 {$this->groupstudentsjoins->joins} 77 WHERE {$this->groupstudentsjoins->wheres} 78 ", $this->groupstudentsjoins->params); 79 if ($hasgroupstudents) { 80 $this->add_average_row(get_string('groupavg', 'grades'), $this->groupstudentsjoins); 81 } 82 } 83 84 if (!empty($this->studentsjoins->joins)) { 85 $hasstudents = $DB->record_exists_sql(" 86 SELECT 1 87 FROM {user} u 88 {$this->studentsjoins->joins} 89 WHERE {$this->studentsjoins->wheres} 90 " , $this->studentsjoins->params); 91 if ($hasstudents) { 92 $this->add_average_row(get_string('overallaverage', 'grades'), $this->studentsjoins); 93 } 94 } 95 } 96 97 /** 98 * Calculate the average overall and question scores for a set of attempts at the quiz. 99 * 100 * @param string $label the title ot use for this row. 101 * @param \core\dml\sql_join $usersjoins to indicate a set of users. 102 * @return array of table cells that make up the average row. 103 */ 104 public function compute_average_row($label, \core\dml\sql_join $usersjoins) { 105 global $DB; 106 107 list($fields, $from, $where, $params) = $this->base_sql($usersjoins); 108 $record = $DB->get_record_sql(" 109 SELECT AVG(quizaouter.sumgrades) AS grade, COUNT(quizaouter.sumgrades) AS numaveraged 110 FROM {quiz_attempts} quizaouter 111 JOIN ( 112 SELECT DISTINCT quiza.id 113 FROM $from 114 WHERE $where 115 ) relevant_attempt_ids ON quizaouter.id = relevant_attempt_ids.id 116 ", $params); 117 $record->grade = quiz_rescale_grade($record->grade, $this->quiz, false); 118 if ($this->is_downloading()) { 119 $namekey = 'lastname'; 120 } else { 121 $namekey = 'fullname'; 122 } 123 $averagerow = array( 124 $namekey => $label, 125 'sumgrades' => $this->format_average($record), 126 'feedbacktext' => strip_tags(quiz_report_feedback_for_grade( 127 $record->grade, $this->quiz->id, $this->context)) 128 ); 129 130 if ($this->options->slotmarks) { 131 $dm = new question_engine_data_mapper(); 132 $qubaids = new qubaid_join("{quiz_attempts} quizaouter 133 JOIN ( 134 SELECT DISTINCT quiza.id 135 FROM $from 136 WHERE $where 137 ) relevant_attempt_ids ON quizaouter.id = relevant_attempt_ids.id", 138 'quizaouter.uniqueid', '1 = 1', $params); 139 $avggradebyq = $dm->load_average_marks($qubaids, array_keys($this->questions)); 140 141 $averagerow += $this->format_average_grade_for_questions($avggradebyq); 142 } 143 144 return $averagerow; 145 } 146 147 /** 148 * Add an average grade row for a set of users. 149 * 150 * @param string $label the title ot use for this row. 151 * @param \core\dml\sql_join $usersjoins (joins, wheres, params) for the users to average over. 152 */ 153 protected function add_average_row($label, \core\dml\sql_join $usersjoins) { 154 $averagerow = $this->compute_average_row($label, $usersjoins); 155 $this->add_data_keyed($averagerow); 156 } 157 158 /** 159 * Helper userd by {@link add_average_row()}. 160 * @param array $gradeaverages the raw grades. 161 * @return array the (partial) row of data. 162 */ 163 protected function format_average_grade_for_questions($gradeaverages) { 164 $row = array(); 165 166 if (!$gradeaverages) { 167 $gradeaverages = array(); 168 } 169 170 foreach ($this->questions as $question) { 171 if (isset($gradeaverages[$question->slot]) && $question->maxmark > 0) { 172 $record = $gradeaverages[$question->slot]; 173 $record->grade = quiz_rescale_grade( 174 $record->averagefraction * $question->maxmark, $this->quiz, false); 175 176 } else { 177 $record = new stdClass(); 178 $record->grade = null; 179 $record->numaveraged = 0; 180 } 181 182 $row['qsgrade' . $question->slot] = $this->format_average($record, true); 183 } 184 185 return $row; 186 } 187 188 /** 189 * Format an entry in an average row. 190 * @param object $record with fields grade and numaveraged. 191 * @param bool $question true if this is a question score, false if it is an overall score. 192 * @return string HTML fragment for an average score (with number of things included in the average). 193 */ 194 protected function format_average($record, $question = false) { 195 if (is_null($record->grade)) { 196 $average = '-'; 197 } else if ($question) { 198 $average = quiz_format_question_grade($this->quiz, $record->grade); 199 } else { 200 $average = quiz_format_grade($this->quiz, $record->grade); 201 } 202 203 if ($this->download) { 204 return $average; 205 } else if (is_null($record->numaveraged) || $record->numaveraged == 0) { 206 return html_writer::tag('span', html_writer::tag('span', 207 $average, array('class' => 'average')), array('class' => 'avgcell')); 208 } else { 209 return html_writer::tag('span', html_writer::tag('span', 210 $average, array('class' => 'average')) . ' ' . html_writer::tag('span', 211 '(' . $record->numaveraged . ')', array('class' => 'count')), 212 array('class' => 'avgcell')); 213 } 214 } 215 216 protected function submit_buttons() { 217 if (has_capability('mod/quiz:regrade', $this->context)) { 218 $regradebuttonparams = [ 219 'type' => 'submit', 220 'class' => 'btn btn-secondary mr-1', 221 'name' => 'regrade', 222 'value' => get_string('regradeselected', 'quiz_overview'), 223 'data-action' => 'toggle', 224 'data-togglegroup' => $this->togglegroup, 225 'data-toggle' => 'action', 226 'disabled' => true 227 ]; 228 echo html_writer::empty_tag('input', $regradebuttonparams); 229 } 230 parent::submit_buttons(); 231 } 232 233 public function col_sumgrades($attempt) { 234 if ($attempt->state != quiz_attempt::FINISHED) { 235 return '-'; 236 } 237 238 $grade = quiz_rescale_grade($attempt->sumgrades, $this->quiz); 239 if ($this->is_downloading()) { 240 return $grade; 241 } 242 243 if (isset($this->regradedqs[$attempt->usageid])) { 244 $newsumgrade = 0; 245 $oldsumgrade = 0; 246 foreach ($this->questions as $question) { 247 if (isset($this->regradedqs[$attempt->usageid][$question->slot])) { 248 $newsumgrade += $this->regradedqs[$attempt->usageid] 249 [$question->slot]->newfraction * $question->maxmark; 250 $oldsumgrade += $this->regradedqs[$attempt->usageid] 251 [$question->slot]->oldfraction * $question->maxmark; 252 } else { 253 $newsumgrade += $this->lateststeps[$attempt->usageid] 254 [$question->slot]->fraction * $question->maxmark; 255 $oldsumgrade += $this->lateststeps[$attempt->usageid] 256 [$question->slot]->fraction * $question->maxmark; 257 } 258 } 259 $newsumgrade = quiz_rescale_grade($newsumgrade, $this->quiz); 260 $oldsumgrade = quiz_rescale_grade($oldsumgrade, $this->quiz); 261 $grade = html_writer::tag('del', $oldsumgrade) . '/' . 262 html_writer::empty_tag('br') . $newsumgrade; 263 } 264 return html_writer::link(new moodle_url('/mod/quiz/review.php', 265 array('attempt' => $attempt->attempt)), $grade, 266 array('title' => get_string('reviewattempt', 'quiz'))); 267 } 268 269 /** 270 * @param string $colname the name of the column. 271 * @param object $attempt the row of data - see the SQL in display() in 272 * mod/quiz/report/overview/report.php to see what fields are present, 273 * and what they are called. 274 * @return string the contents of the cell. 275 */ 276 public function other_cols($colname, $attempt) { 277 if (!preg_match('/^qsgrade(\d+)$/', $colname, $matches)) { 278 return parent::other_cols($colname, $attempt); 279 } 280 $slot = $matches[1]; 281 282 $question = $this->questions[$slot]; 283 if (!isset($this->lateststeps[$attempt->usageid][$slot])) { 284 return '-'; 285 } 286 287 $stepdata = $this->lateststeps[$attempt->usageid][$slot]; 288 $state = question_state::get($stepdata->state); 289 290 if ($question->maxmark == 0) { 291 $grade = '-'; 292 } else if (is_null($stepdata->fraction)) { 293 if ($state == question_state::$needsgrading) { 294 $grade = get_string('requiresgrading', 'question'); 295 } else { 296 $grade = '-'; 297 } 298 } else { 299 $grade = quiz_rescale_grade( 300 $stepdata->fraction * $question->maxmark, $this->quiz, 'question'); 301 } 302 303 if ($this->is_downloading()) { 304 return $grade; 305 } 306 307 if (isset($this->regradedqs[$attempt->usageid][$slot])) { 308 $gradefromdb = $grade; 309 $newgrade = quiz_rescale_grade( 310 $this->regradedqs[$attempt->usageid][$slot]->newfraction * $question->maxmark, 311 $this->quiz, 'question'); 312 $oldgrade = quiz_rescale_grade( 313 $this->regradedqs[$attempt->usageid][$slot]->oldfraction * $question->maxmark, 314 $this->quiz, 'question'); 315 316 $grade = html_writer::tag('del', $oldgrade) . '/' . 317 html_writer::empty_tag('br') . $newgrade; 318 } 319 320 return $this->make_review_link($grade, $attempt, $slot); 321 } 322 323 public function col_regraded($attempt) { 324 if ($attempt->regraded == '') { 325 return ''; 326 } else if ($attempt->regraded == 0) { 327 return get_string('needed', 'quiz_overview'); 328 } else if ($attempt->regraded == 1) { 329 return get_string('done', 'quiz_overview'); 330 } 331 } 332 333 protected function update_sql_after_count($fields, $from, $where, $params) { 334 $fields .= ", COALESCE(( 335 SELECT MAX(qqr.regraded) 336 FROM {quiz_overview_regrades} qqr 337 WHERE qqr.questionusageid = quiza.uniqueid 338 ), -1) AS regraded"; 339 if ($this->options->onlyregraded) { 340 $where .= " AND COALESCE(( 341 SELECT MAX(qqr.regraded) 342 FROM {quiz_overview_regrades} qqr 343 WHERE qqr.questionusageid = quiza.uniqueid 344 ), -1) <> -1"; 345 } 346 return [$fields, $from, $where, $params]; 347 } 348 349 protected function requires_latest_steps_loaded() { 350 return $this->options->slotmarks; 351 } 352 353 protected function is_latest_step_column($column) { 354 if (preg_match('/^qsgrade([0-9]+)/', $column, $matches)) { 355 return $matches[1]; 356 } 357 return false; 358 } 359 360 protected function get_required_latest_state_fields($slot, $alias) { 361 return "$alias.fraction * $alias.maxmark AS qsgrade$slot"; 362 } 363 364 public function query_db($pagesize, $useinitialsbar = true) { 365 parent::query_db($pagesize, $useinitialsbar); 366 367 if ($this->options->slotmarks && has_capability('mod/quiz:regrade', $this->context)) { 368 $this->regradedqs = $this->get_regraded_questions(); 369 } 370 } 371 372 /** 373 * Get all the questions in all the attempts being displayed that need regrading. 374 * @return array A two dimensional array $questionusageid => $slot => $regradeinfo. 375 */ 376 protected function get_regraded_questions() { 377 global $DB; 378 379 $qubaids = $this->get_qubaids_condition(); 380 $regradedqs = $DB->get_records_select('quiz_overview_regrades', 381 'questionusageid ' . $qubaids->usage_id_in(), $qubaids->usage_id_in_params()); 382 return quiz_report_index_by_keys($regradedqs, array('questionusageid', 'slot')); 383 } 384 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body