Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 overview report class. 19 * 20 * @package quiz_overview 21 * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} 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.php'); 29 require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_options.php'); 30 require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_form.php'); 31 require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_table.php'); 32 33 34 /** 35 * Quiz report subclass for the overview (grades) report. 36 * 37 * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class quiz_overview_report extends quiz_attempts_report { 41 42 /** 43 * @var bool whether there are actually students to show, given the options. 44 */ 45 protected $hasgroupstudents; 46 47 public function display($quiz, $cm, $course) { 48 global $DB, $OUTPUT, $PAGE; 49 50 list($currentgroup, $studentsjoins, $groupstudentsjoins, $allowedjoins) = $this->init( 51 'overview', 'quiz_overview_settings_form', $quiz, $cm, $course); 52 53 $options = new quiz_overview_options('overview', $quiz, $cm, $course); 54 55 if ($fromform = $this->form->get_data()) { 56 $options->process_settings_from_form($fromform); 57 58 } else { 59 $options->process_settings_from_params(); 60 } 61 62 $this->form->set_data($options->get_initial_form_data()); 63 64 // Load the required questions. 65 $questions = quiz_report_get_significant_questions($quiz); 66 67 // Prepare for downloading, if applicable. 68 $courseshortname = format_string($course->shortname, true, 69 array('context' => context_course::instance($course->id))); 70 $table = new quiz_overview_table($quiz, $this->context, $this->qmsubselect, 71 $options, $groupstudentsjoins, $studentsjoins, $questions, $options->get_url()); 72 $filename = quiz_report_download_filename(get_string('overviewfilename', 'quiz_overview'), 73 $courseshortname, $quiz->name); 74 $table->is_downloading($options->download, $filename, 75 $courseshortname . ' ' . format_string($quiz->name, true)); 76 if ($table->is_downloading()) { 77 raise_memory_limit(MEMORY_EXTRA); 78 } 79 80 $this->hasgroupstudents = false; 81 if (!empty($groupstudentsjoins->joins)) { 82 $sql = "SELECT DISTINCT u.id 83 FROM {user} u 84 $groupstudentsjoins->joins 85 WHERE $groupstudentsjoins->wheres"; 86 $this->hasgroupstudents = $DB->record_exists_sql($sql, $groupstudentsjoins->params); 87 } 88 $hasstudents = false; 89 if (!empty($studentsjoins->joins)) { 90 $sql = "SELECT DISTINCT u.id 91 FROM {user} u 92 $studentsjoins->joins 93 WHERE $studentsjoins->wheres"; 94 $hasstudents = $DB->record_exists_sql($sql, $studentsjoins->params); 95 } 96 if ($options->attempts == self::ALL_WITH) { 97 // This option is only available to users who can access all groups in 98 // groups mode, so setting allowed to empty (which means all quiz attempts 99 // are accessible, is not a security porblem. 100 $allowedjoins = new \core\dml\sql_join(); 101 } 102 103 $this->course = $course; // Hack to make this available in process_actions. 104 $this->process_actions($quiz, $cm, $currentgroup, $groupstudentsjoins, $allowedjoins, $options->get_url()); 105 106 $hasquestions = quiz_has_questions($quiz->id); 107 108 // Start output. 109 if (!$table->is_downloading()) { 110 // Only print headers if not asked to download data. 111 $this->print_standard_header_and_messages($cm, $course, $quiz, 112 $options, $currentgroup, $hasquestions, $hasstudents); 113 114 // Print the display options. 115 $this->form->display(); 116 } 117 118 $hasstudents = $hasstudents && (!$currentgroup || $this->hasgroupstudents); 119 if ($hasquestions && ($hasstudents || $options->attempts == self::ALL_WITH)) { 120 // Construct the SQL. 121 $table->setup_sql_queries($allowedjoins); 122 123 if (!$table->is_downloading()) { 124 // Output the regrade buttons. 125 if (has_capability('mod/quiz:regrade', $this->context)) { 126 $regradesneeded = $this->count_question_attempts_needing_regrade( 127 $quiz, $groupstudentsjoins); 128 if ($currentgroup) { 129 $a= new stdClass(); 130 $a->groupname = groups_get_group_name($currentgroup); 131 $a->coursestudents = get_string('participants'); 132 $a->countregradeneeded = $regradesneeded; 133 $regradealldrydolabel = 134 get_string('regradealldrydogroup', 'quiz_overview', $a); 135 $regradealldrylabel = 136 get_string('regradealldrygroup', 'quiz_overview', $a); 137 $regradealllabel = 138 get_string('regradeallgroup', 'quiz_overview', $a); 139 } else { 140 $regradealldrydolabel = 141 get_string('regradealldrydo', 'quiz_overview', $regradesneeded); 142 $regradealldrylabel = 143 get_string('regradealldry', 'quiz_overview'); 144 $regradealllabel = 145 get_string('regradeall', 'quiz_overview'); 146 } 147 $displayurl = new moodle_url($options->get_url(), array('sesskey' => sesskey())); 148 echo '<div class="mdl-align">'; 149 echo '<form action="'.$displayurl->out_omit_querystring().'">'; 150 echo '<div>'; 151 echo html_writer::input_hidden_params($displayurl); 152 echo '<input type="submit" class="btn btn-secondary" name="regradeall" value="'.$regradealllabel.'"/>'; 153 echo '<input type="submit" class="btn btn-secondary ml-1" name="regradealldry" value="' . 154 $regradealldrylabel . '"/>'; 155 if ($regradesneeded) { 156 echo '<input type="submit" class="btn btn-secondary ml-1" name="regradealldrydo" value="' . 157 $regradealldrydolabel . '"/>'; 158 } 159 echo '</div>'; 160 echo '</form>'; 161 echo '</div>'; 162 } 163 // Print information on the grading method. 164 if ($strattempthighlight = quiz_report_highlighting_grading_method( 165 $quiz, $this->qmsubselect, $options->onlygraded)) { 166 echo '<div class="quizattemptcounts">' . $strattempthighlight . '</div>'; 167 } 168 } 169 170 // Define table columns. 171 $columns = array(); 172 $headers = array(); 173 174 if (!$table->is_downloading() && $options->checkboxcolumn) { 175 $columnname = 'checkbox'; 176 $columns[] = $columnname; 177 $headers[] = $table->checkbox_col_header($columnname); 178 } 179 180 $this->add_user_columns($table, $columns, $headers); 181 $this->add_state_column($columns, $headers); 182 $this->add_time_columns($columns, $headers); 183 184 $this->add_grade_columns($quiz, $options->usercanseegrades, $columns, $headers, false); 185 186 if (!$table->is_downloading() && has_capability('mod/quiz:regrade', $this->context) && 187 $this->has_regraded_questions($table->sql->from, $table->sql->where, $table->sql->params)) { 188 $columns[] = 'regraded'; 189 $headers[] = get_string('regrade', 'quiz_overview'); 190 } 191 192 if ($options->slotmarks) { 193 foreach ($questions as $slot => $question) { 194 // Ignore questions of zero length. 195 $columns[] = 'qsgrade' . $slot; 196 $header = get_string('qbrief', 'quiz', $question->number); 197 if (!$table->is_downloading()) { 198 $header .= '<br />'; 199 } else { 200 $header .= ' '; 201 } 202 $header .= '/' . quiz_rescale_grade($question->maxmark, $quiz, 'question'); 203 $headers[] = $header; 204 } 205 } 206 207 $this->set_up_table_columns($table, $columns, $headers, $this->get_base_url(), $options, false); 208 $table->set_attribute('class', 'generaltable generalbox grades'); 209 210 $table->out($options->pagesize, true); 211 } 212 213 if (!$table->is_downloading() && $options->usercanseegrades) { 214 $output = $PAGE->get_renderer('mod_quiz'); 215 list($bands, $bandwidth) = self::get_bands_count_and_width($quiz); 216 $labels = self::get_bands_labels($bands, $bandwidth, $quiz); 217 218 if ($currentgroup && $this->hasgroupstudents) { 219 $sql = "SELECT qg.id 220 FROM {quiz_grades} qg 221 JOIN {user} u on u.id = qg.userid 222 {$groupstudentsjoins->joins} 223 WHERE qg.quiz = $quiz->id AND {$groupstudentsjoins->wheres}"; 224 if ($DB->record_exists_sql($sql, $groupstudentsjoins->params)) { 225 $data = quiz_report_grade_bands($bandwidth, $bands, $quiz->id, $groupstudentsjoins); 226 $chart = self::get_chart($labels, $data); 227 $graphname = get_string('overviewreportgraphgroup', 'quiz_overview', groups_get_group_name($currentgroup)); 228 // Numerical range data should display in LTR even for RTL languages. 229 echo $output->chart($chart, $graphname, ['dir' => 'ltr']); 230 } 231 } 232 233 if ($DB->record_exists('quiz_grades', array('quiz'=> $quiz->id))) { 234 $data = quiz_report_grade_bands($bandwidth, $bands, $quiz->id, new \core\dml\sql_join()); 235 $chart = self::get_chart($labels, $data); 236 $graphname = get_string('overviewreportgraph', 'quiz_overview'); 237 // Numerical range data should display in LTR even for RTL languages. 238 echo $output->chart($chart, $graphname, ['dir' => 'ltr']); 239 } 240 } 241 return true; 242 } 243 244 /** 245 * Extends parent function processing any submitted actions. 246 * 247 * @param object $quiz 248 * @param object $cm 249 * @param int $currentgroup 250 * @param \core\dml\sql_join $groupstudentsjoins (joins, wheres, params) 251 * @param \core\dml\sql_join $allowedjoins (joins, wheres, params) 252 * @param moodle_url $redirecturl 253 */ 254 protected function process_actions($quiz, $cm, $currentgroup, \core\dml\sql_join $groupstudentsjoins, 255 \core\dml\sql_join $allowedjoins, $redirecturl) { 256 parent::process_actions($quiz, $cm, $currentgroup, $groupstudentsjoins, $allowedjoins, $redirecturl); 257 258 if (empty($currentgroup) || $this->hasgroupstudents) { 259 if (optional_param('regrade', 0, PARAM_BOOL) && confirm_sesskey()) { 260 if ($attemptids = optional_param_array('attemptid', array(), PARAM_INT)) { 261 $this->start_regrade($quiz, $cm); 262 $this->regrade_attempts($quiz, false, $groupstudentsjoins, $attemptids); 263 $this->finish_regrade($redirecturl); 264 } 265 } 266 } 267 268 if (optional_param('regradeall', 0, PARAM_BOOL) && confirm_sesskey()) { 269 $this->start_regrade($quiz, $cm); 270 $this->regrade_attempts($quiz, false, $groupstudentsjoins); 271 $this->finish_regrade($redirecturl); 272 273 } else if (optional_param('regradealldry', 0, PARAM_BOOL) && confirm_sesskey()) { 274 $this->start_regrade($quiz, $cm); 275 $this->regrade_attempts($quiz, true, $groupstudentsjoins); 276 $this->finish_regrade($redirecturl); 277 278 } else if (optional_param('regradealldrydo', 0, PARAM_BOOL) && confirm_sesskey()) { 279 $this->start_regrade($quiz, $cm); 280 $this->regrade_attempts_needing_it($quiz, $groupstudentsjoins); 281 $this->finish_regrade($redirecturl); 282 } 283 } 284 285 /** 286 * Check necessary capabilities, and start the display of the regrade progress page. 287 * @param object $quiz the quiz settings. 288 * @param object $cm the cm object for the quiz. 289 */ 290 protected function start_regrade($quiz, $cm) { 291 require_capability('mod/quiz:regrade', $this->context); 292 $this->print_header_and_tabs($cm, $this->course, $quiz, $this->mode); 293 } 294 295 /** 296 * Finish displaying the regrade progress page. 297 * @param moodle_url $nexturl where to send the user after the regrade. 298 * @uses exit. This method never returns. 299 */ 300 protected function finish_regrade($nexturl) { 301 global $OUTPUT; 302 \core\notification::success(get_string('regradecomplete', 'quiz_overview')); 303 echo $OUTPUT->continue_button($nexturl); 304 echo $OUTPUT->footer(); 305 die(); 306 } 307 308 /** 309 * Unlock the session and allow the regrading process to run in the background. 310 */ 311 protected function unlock_session() { 312 \core\session\manager::write_close(); 313 ignore_user_abort(true); 314 } 315 316 /** 317 * Regrade a particular quiz attempt. Either for real ($dryrun = false), or 318 * as a pretend regrade to see which fractions would change. The outcome is 319 * stored in the quiz_overview_regrades table. 320 * 321 * Note, $attempt is not upgraded in the database. The caller needs to do that. 322 * However, $attempt->sumgrades is updated, if this is not a dry run. 323 * 324 * @param object $attempt the quiz attempt to regrade. 325 * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real. 326 * @param array $slots if null, regrade all questions, otherwise, just regrade 327 * the quetsions with those slots. 328 */ 329 protected function regrade_attempt($attempt, $dryrun = false, $slots = null) { 330 global $DB; 331 // Need more time for a quiz with many questions. 332 core_php_time_limit::raise(300); 333 334 $transaction = $DB->start_delegated_transaction(); 335 336 $quba = question_engine::load_questions_usage_by_activity($attempt->uniqueid); 337 338 if (is_null($slots)) { 339 $slots = $quba->get_slots(); 340 } 341 342 $finished = $attempt->state == quiz_attempt::FINISHED; 343 foreach ($slots as $slot) { 344 $qqr = new stdClass(); 345 $qqr->oldfraction = $quba->get_question_fraction($slot); 346 347 $quba->regrade_question($slot, $finished); 348 349 $qqr->newfraction = $quba->get_question_fraction($slot); 350 351 if (abs($qqr->oldfraction - $qqr->newfraction) > 1e-7) { 352 $qqr->questionusageid = $quba->get_id(); 353 $qqr->slot = $slot; 354 $qqr->regraded = empty($dryrun); 355 $qqr->timemodified = time(); 356 $DB->insert_record('quiz_overview_regrades', $qqr, false); 357 } 358 } 359 360 if (!$dryrun) { 361 question_engine::save_questions_usage_by_activity($quba); 362 363 $params = array( 364 'objectid' => $attempt->id, 365 'relateduserid' => $attempt->userid, 366 'context' => $this->context, 367 'other' => array( 368 'quizid' => $attempt->quiz 369 ) 370 ); 371 $event = \mod_quiz\event\attempt_regraded::create($params); 372 $event->trigger(); 373 } 374 375 $transaction->allow_commit(); 376 377 // Really, PHP should not need this hint, but without this, we just run out of memory. 378 $quba = null; 379 $transaction = null; 380 gc_collect_cycles(); 381 } 382 383 /** 384 * Regrade attempts for this quiz, exactly which attempts are regraded is 385 * controlled by the parameters. 386 * @param object $quiz the quiz settings. 387 * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real. 388 * @param \core\dml\sql_join|array $groupstudentsjoins empty for all attempts, otherwise regrade attempts 389 * for these users. 390 * @param array $attemptids blank for all attempts, otherwise only regrade 391 * attempts whose id is in this list. 392 */ 393 protected function regrade_attempts($quiz, $dryrun = false, 394 \core\dml\sql_join$groupstudentsjoins = null, $attemptids = array()) { 395 global $DB; 396 $this->unlock_session(); 397 398 $userfieldsapi = \core_user\fields::for_name(); 399 $sql = "SELECT quiza.*, " . $userfieldsapi->get_sql('u', false, '', '', false)->selects . " 400 FROM {quiz_attempts} quiza 401 JOIN {user} u ON u.id = quiza.userid"; 402 $where = "quiz = :qid AND preview = 0"; 403 $params = array('qid' => $quiz->id); 404 405 if ($this->hasgroupstudents && !empty($groupstudentsjoins->joins)) { 406 $sql .= "\n{$groupstudentsjoins->joins}"; 407 $where .= " AND {$groupstudentsjoins->wheres}"; 408 $params += $groupstudentsjoins->params; 409 } 410 411 if ($attemptids) { 412 list($attemptidcondition, $attemptidparams) = $DB->get_in_or_equal($attemptids, SQL_PARAMS_NAMED); 413 $where .= " AND quiza.id $attemptidcondition"; 414 $params += $attemptidparams; 415 } 416 417 $sql .= "\nWHERE {$where}"; 418 $attempts = $DB->get_records_sql($sql, $params); 419 if (!$attempts) { 420 return; 421 } 422 423 $this->regrade_batch_of_attempts($quiz, $attempts, $dryrun, $groupstudentsjoins); 424 } 425 426 /** 427 * Regrade those questions in those attempts that are marked as needing regrading 428 * in the quiz_overview_regrades table. 429 * @param object $quiz the quiz settings. 430 * @param \core\dml\sql_join $groupstudentsjoins empty for all attempts, otherwise regrade attempts 431 * for these users. 432 */ 433 protected function regrade_attempts_needing_it($quiz, \core\dml\sql_join $groupstudentsjoins) { 434 global $DB; 435 $this->unlock_session(); 436 437 $join = '{quiz_overview_regrades} qqr ON qqr.questionusageid = quiza.uniqueid'; 438 $where = "quiza.quiz = :qid AND quiza.preview = 0 AND qqr.regraded = 0"; 439 $params = array('qid' => $quiz->id); 440 441 // Fetch all attempts that need regrading. 442 if ($this->hasgroupstudents && !empty($groupstudentsjoins->joins)) { 443 $join .= "\nJOIN {user} u ON u.id = quiza.userid 444 {$groupstudentsjoins->joins}"; 445 $where .= " AND {$groupstudentsjoins->wheres}"; 446 $params += $groupstudentsjoins->params; 447 } 448 449 $toregrade = $DB->get_recordset_sql(" 450 SELECT quiza.uniqueid, qqr.slot 451 FROM {quiz_attempts} quiza 452 JOIN $join 453 WHERE $where", $params); 454 455 $attemptquestions = array(); 456 foreach ($toregrade as $row) { 457 $attemptquestions[$row->uniqueid][] = $row->slot; 458 } 459 $toregrade->close(); 460 461 if (!$attemptquestions) { 462 return; 463 } 464 465 list($uniqueidcondition, $params) = $DB->get_in_or_equal(array_keys($attemptquestions)); 466 $userfieldsapi = \core_user\fields::for_name(); 467 $attempts = $DB->get_records_sql(" 468 SELECT quiza.*, " . $userfieldsapi->get_sql('u', false, '', '', false)->selects . " 469 FROM {quiz_attempts} quiza 470 JOIN {user} u ON u.id = quiza.userid 471 WHERE quiza.uniqueid $uniqueidcondition 472 ", $params); 473 474 foreach ($attempts as $attempt) { 475 $attempt->regradeonlyslots = $attemptquestions[$attempt->uniqueid]; 476 } 477 478 $this->regrade_batch_of_attempts($quiz, $attempts, false, $groupstudentsjoins); 479 } 480 481 /** 482 * This is a helper used by {@link regrade_attempts()} and 483 * {@link regrade_attempts_needing_it()}. 484 * 485 * Given an array of attempts, it regrades them all, or does a dry run. 486 * Each object in the attempts array must be a row from the quiz_attempts 487 * table, with the \core_user\fields::for_name() fields from the user table joined in. 488 * In addition, if $attempt->regradeonlyslots is set, then only those slots 489 * are regraded, otherwise all slots are regraded. 490 * 491 * @param object $quiz the quiz settings. 492 * @param array $attempts of data from the quiz_attempts table, with extra data as above. 493 * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real. 494 * @param \core\dml\sql_join $groupstudentsjoins empty for all attempts, otherwise regrade attempts 495 */ 496 protected function regrade_batch_of_attempts($quiz, array $attempts, 497 bool $dryrun, \core\dml\sql_join $groupstudentsjoins) { 498 $this->clear_regrade_table($quiz, $groupstudentsjoins); 499 500 $progressbar = new progress_bar('quiz_overview_regrade', 500, true); 501 $a = array( 502 'count' => count($attempts), 503 'done' => 0, 504 ); 505 foreach ($attempts as $attempt) { 506 $a['done']++; 507 $a['attemptnum'] = $attempt->attempt; 508 $a['name'] = fullname($attempt); 509 $a['attemptid'] = $attempt->id; 510 if (!isset($attempt->regradeonlyslots)) { 511 $attempt->regradeonlyslots = null; 512 } 513 $progressbar->update($a['done'], $a['count'], 514 get_string('regradingattemptxofywithdetails', 'quiz_overview', $a)); 515 $this->regrade_attempt($attempt, $dryrun, $attempt->regradeonlyslots); 516 } 517 $progressbar->update($a['done'], $a['count'], 518 get_string('regradedsuccessfullyxofy', 'quiz_overview', $a)); 519 520 if (!$dryrun) { 521 $this->update_overall_grades($quiz); 522 } 523 } 524 525 /** 526 * Count the number of attempts in need of a regrade. 527 * 528 * @param object $quiz the quiz settings. 529 * @param \core\dml\sql_join $groupstudentsjoins (joins, wheres, params) If this is given, only data relating 530 * to these users is cleared. 531 * @return int the number of attempts. 532 */ 533 protected function count_question_attempts_needing_regrade($quiz, \core\dml\sql_join $groupstudentsjoins) { 534 global $DB; 535 536 $userjoin = ''; 537 $usertest = ''; 538 $params = array(); 539 if ($this->hasgroupstudents) { 540 $userjoin = "JOIN {user} u ON u.id = quiza.userid 541 {$groupstudentsjoins->joins}"; 542 $usertest = "{$groupstudentsjoins->wheres} AND u.id = quiza.userid AND "; 543 $params = $groupstudentsjoins->params; 544 } 545 546 $params['cquiz'] = $quiz->id; 547 $sql = "SELECT COUNT(DISTINCT quiza.id) 548 FROM {quiz_attempts} quiza 549 JOIN {quiz_overview_regrades} qqr ON quiza.uniqueid = qqr.questionusageid 550 $userjoin 551 WHERE 552 $usertest 553 quiza.quiz = :cquiz AND 554 quiza.preview = 0 AND 555 qqr.regraded = 0"; 556 return $DB->count_records_sql($sql, $params); 557 } 558 559 /** 560 * Are there any pending regrades in the table we are going to show? 561 * @param string $from tables used by the main query. 562 * @param string $where where clause used by the main query. 563 * @param array $params required by the SQL. 564 * @return bool whether there are pending regrades. 565 */ 566 protected function has_regraded_questions($from, $where, $params) { 567 global $DB; 568 return $DB->record_exists_sql(" 569 SELECT 1 570 FROM {$from} 571 JOIN {quiz_overview_regrades} qor ON qor.questionusageid = quiza.uniqueid 572 WHERE {$where}", $params); 573 } 574 575 /** 576 * Remove all information about pending/complete regrades from the database. 577 * @param object $quiz the quiz settings. 578 * @param \core\dml\sql_join $groupstudentsjoins (joins, wheres, params). If this is given, only data relating 579 * to these users is cleared. 580 */ 581 protected function clear_regrade_table($quiz, \core\dml\sql_join $groupstudentsjoins) { 582 global $DB; 583 584 // Fetch all attempts that need regrading. 585 $select = "questionusageid IN ( 586 SELECT uniqueid 587 FROM {quiz_attempts} quiza"; 588 $where = "WHERE quiza.quiz = :qid"; 589 $params = array('qid' => $quiz->id); 590 if ($this->hasgroupstudents && !empty($groupstudentsjoins->joins)) { 591 $select .= "\nJOIN {user} u ON u.id = quiza.userid 592 {$groupstudentsjoins->joins}"; 593 $where .= " AND {$groupstudentsjoins->wheres}"; 594 $params += $groupstudentsjoins->params; 595 } 596 $select .= "\n$where)"; 597 598 $DB->delete_records_select('quiz_overview_regrades', $select, $params); 599 } 600 601 /** 602 * Update the final grades for all attempts. This method is used following 603 * a regrade. 604 * @param object $quiz the quiz settings. 605 * @param array $userids only update scores for these userids. 606 * @param array $attemptids attemptids only update scores for these attempt ids. 607 */ 608 protected function update_overall_grades($quiz) { 609 quiz_update_all_attempt_sumgrades($quiz); 610 quiz_update_all_final_grades($quiz); 611 quiz_update_grades($quiz); 612 } 613 614 /** 615 * Get the bands configuration for the quiz. 616 * 617 * This returns the configuration for having between 11 and 20 bars in 618 * a chart based on the maximum grade to be given on a quiz. The width of 619 * a band is the number of grade points it encapsulates. 620 * 621 * @param object $quiz The quiz object. 622 * @return array Contains the number of bands, and their width. 623 */ 624 public static function get_bands_count_and_width($quiz) { 625 $bands = $quiz->grade; 626 while ($bands > 20 || $bands <= 10) { 627 if ($bands > 50) { 628 $bands /= 5; 629 } else if ($bands > 20) { 630 $bands /= 2; 631 } 632 if ($bands < 4) { 633 $bands *= 5; 634 } else if ($bands <= 10) { 635 $bands *= 2; 636 } 637 } 638 // See MDL-34589. Using doubles as array keys causes problems in PHP 5.4, hence the explicit cast to int. 639 $bands = (int) ceil($bands); 640 return [$bands, $quiz->grade / $bands]; 641 } 642 643 /** 644 * Get the bands labels. 645 * 646 * @param int $bands The number of bands. 647 * @param int $bandwidth The band width. 648 * @param object $quiz The quiz object. 649 * @return string[] The labels. 650 */ 651 public static function get_bands_labels($bands, $bandwidth, $quiz) { 652 $bandlabels = []; 653 for ($i = 1; $i <= $bands; $i++) { 654 $bandlabels[] = quiz_format_grade($quiz, ($i - 1) * $bandwidth) . ' - ' . quiz_format_grade($quiz, $i * $bandwidth); 655 } 656 return $bandlabels; 657 } 658 659 /** 660 * Get a chart. 661 * 662 * @param string[] $labels Chart labels. 663 * @param int[] $data The data. 664 * @return \core\chart_base 665 */ 666 protected static function get_chart($labels, $data) { 667 $chart = new \core\chart_bar(); 668 $chart->set_labels($labels); 669 $chart->get_xaxis(0, true)->set_label(get_string('gradenoun')); 670 671 $yaxis = $chart->get_yaxis(0, true); 672 $yaxis->set_label(get_string('participants')); 673 $yaxis->set_stepsize(max(1, round(max($data) / 10))); 674 675 $series = new \core\chart_series(get_string('participants'), $data); 676 $chart->add_series($series); 677 return $chart; 678 } 679 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body