[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * Helper functions for the quiz reports. 19 * 20 * @package mod_quiz 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/lib.php'); 29 require_once($CFG->libdir . '/filelib.php'); 30 31 /** 32 * Takes an array of objects and constructs a multidimensional array keyed by 33 * the keys it finds on the object. 34 * @param array $datum an array of objects with properties on the object 35 * including the keys passed as the next param. 36 * @param array $keys Array of strings with the names of the properties on the 37 * objects in datum that you want to index the multidimensional array by. 38 * @param bool $keysunique If there is not only one object for each 39 * combination of keys you are using you should set $keysunique to true. 40 * Otherwise all the object will be added to a zero based array. So the array 41 * returned will have count($keys) + 1 indexs. 42 * @return array multidimensional array properly indexed. 43 */ 44 function quiz_report_index_by_keys($datum, $keys, $keysunique = true) { 45 if (!$datum) { 46 return array(); 47 } 48 $key = array_shift($keys); 49 $datumkeyed = array(); 50 foreach ($datum as $data) { 51 if ($keys || !$keysunique) { 52 $datumkeyed[$data->{$key}][]= $data; 53 } else { 54 $datumkeyed[$data->{$key}]= $data; 55 } 56 } 57 if ($keys) { 58 foreach ($datumkeyed as $datakey => $datakeyed) { 59 $datumkeyed[$datakey] = quiz_report_index_by_keys($datakeyed, $keys, $keysunique); 60 } 61 } 62 return $datumkeyed; 63 } 64 65 function quiz_report_unindex($datum) { 66 if (!$datum) { 67 return $datum; 68 } 69 $datumunkeyed = array(); 70 foreach ($datum as $value) { 71 if (is_array($value)) { 72 $datumunkeyed = array_merge($datumunkeyed, quiz_report_unindex($value)); 73 } else { 74 $datumunkeyed[] = $value; 75 } 76 } 77 return $datumunkeyed; 78 } 79 80 /** 81 * Are there any questions in this quiz? 82 * @param int $quizid the quiz id. 83 */ 84 function quiz_has_questions($quizid) { 85 global $DB; 86 return $DB->record_exists('quiz_slots', array('quizid' => $quizid)); 87 } 88 89 /** 90 * Get the slots of real questions (not descriptions) in this quiz, in order. 91 * @param object $quiz the quiz. 92 * @return array of slot => $question object with fields 93 * ->slot, ->id, ->maxmark, ->number, ->length. 94 */ 95 function quiz_report_get_significant_questions($quiz) { 96 global $DB; 97 98 $qsbyslot = $DB->get_records_sql(" 99 SELECT slot.slot, 100 q.id, 101 q.length, 102 slot.maxmark 103 104 FROM {question} q 105 JOIN {quiz_slots} slot ON slot.questionid = q.id 106 107 WHERE slot.quizid = ? 108 AND q.length > 0 109 110 ORDER BY slot.slot", array($quiz->id)); 111 112 $number = 1; 113 foreach ($qsbyslot as $question) { 114 $question->number = $number; 115 $number += $question->length; 116 } 117 118 return $qsbyslot; 119 } 120 121 /** 122 * @param object $quiz the quiz settings. 123 * @return bool whether, for this quiz, it is possible to filter attempts to show 124 * only those that gave the final grade. 125 */ 126 function quiz_report_can_filter_only_graded($quiz) { 127 return $quiz->attempts != 1 && $quiz->grademethod != QUIZ_GRADEAVERAGE; 128 } 129 130 /** 131 * This is a wrapper for {@link quiz_report_grade_method_sql} that takes the whole quiz object instead of just the grading method 132 * as a param. See definition for {@link quiz_report_grade_method_sql} below. 133 * 134 * @param object $quiz 135 * @param string $quizattemptsalias sql alias for 'quiz_attempts' table 136 * @return string sql to test if this is an attempt that will contribute towards the grade of the user 137 */ 138 function quiz_report_qm_filter_select($quiz, $quizattemptsalias = 'quiza') { 139 if ($quiz->attempts == 1) { 140 // This quiz only allows one attempt. 141 return ''; 142 } 143 return quiz_report_grade_method_sql($quiz->grademethod, $quizattemptsalias); 144 } 145 146 /** 147 * Given a quiz grading method return sql to test if this is an 148 * attempt that will be contribute towards the grade of the user. Or return an 149 * empty string if the grading method is QUIZ_GRADEAVERAGE and thus all attempts 150 * contribute to final grade. 151 * 152 * @param string $grademethod quiz grading method. 153 * @param string $quizattemptsalias sql alias for 'quiz_attempts' table 154 * @return string sql to test if this is an attempt that will contribute towards the graded of the user 155 */ 156 function quiz_report_grade_method_sql($grademethod, $quizattemptsalias = 'quiza') { 157 switch ($grademethod) { 158 case QUIZ_GRADEHIGHEST : 159 return "($quizattemptsalias.state = 'finished' AND NOT EXISTS ( 160 SELECT 1 FROM {quiz_attempts} qa2 161 WHERE qa2.quiz = $quizattemptsalias.quiz AND 162 qa2.userid = $quizattemptsalias.userid AND 163 qa2.state = 'finished' AND ( 164 COALESCE(qa2.sumgrades, 0) > COALESCE($quizattemptsalias.sumgrades, 0) OR 165 (COALESCE(qa2.sumgrades, 0) = COALESCE($quizattemptsalias.sumgrades, 0) AND qa2.attempt < $quizattemptsalias.attempt) 166 )))"; 167 168 case QUIZ_GRADEAVERAGE : 169 return ''; 170 171 case QUIZ_ATTEMPTFIRST : 172 return "($quizattemptsalias.state = 'finished' AND NOT EXISTS ( 173 SELECT 1 FROM {quiz_attempts} qa2 174 WHERE qa2.quiz = $quizattemptsalias.quiz AND 175 qa2.userid = $quizattemptsalias.userid AND 176 qa2.state = 'finished' AND 177 qa2.attempt < $quizattemptsalias.attempt))"; 178 179 case QUIZ_ATTEMPTLAST : 180 return "($quizattemptsalias.state = 'finished' AND NOT EXISTS ( 181 SELECT 1 FROM {quiz_attempts} qa2 182 WHERE qa2.quiz = $quizattemptsalias.quiz AND 183 qa2.userid = $quizattemptsalias.userid AND 184 qa2.state = 'finished' AND 185 qa2.attempt > $quizattemptsalias.attempt))"; 186 } 187 } 188 189 /** 190 * Get the number of students whose score was in a particular band for this quiz. 191 * @param number $bandwidth the width of each band. 192 * @param int $bands the number of bands 193 * @param int $quizid the quiz id. 194 * @param array $userids list of user ids. 195 * @return array band number => number of users with scores in that band. 196 */ 197 function quiz_report_grade_bands($bandwidth, $bands, $quizid, $userids = array()) { 198 global $DB; 199 if (!is_int($bands)) { 200 debugging('$bands passed to quiz_report_grade_bands must be an integer. (' . 201 gettype($bands) . ' passed.)', DEBUG_DEVELOPER); 202 $bands = (int) $bands; 203 } 204 205 if ($userids) { 206 list($usql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'u'); 207 $usql = "qg.userid $usql AND"; 208 } else { 209 $usql = ''; 210 $params = array(); 211 } 212 $sql = " 213 SELECT band, COUNT(1) 214 215 FROM ( 216 SELECT FLOOR(qg.grade / :bandwidth) AS band 217 FROM {quiz_grades} qg 218 WHERE $usql qg.quiz = :quizid 219 ) subquery 220 221 GROUP BY 222 band 223 224 ORDER BY 225 band"; 226 227 $params['quizid'] = $quizid; 228 $params['bandwidth'] = $bandwidth; 229 230 $data = $DB->get_records_sql_menu($sql, $params); 231 232 // We need to create array elements with values 0 at indexes where there is no element. 233 $data = $data + array_fill(0, $bands + 1, 0); 234 ksort($data); 235 236 // Place the maximum (perfect grade) into the last band i.e. make last 237 // band for example 9 <= g <=10 (where 10 is the perfect grade) rather than 238 // just 9 <= g <10. 239 $data[$bands - 1] += $data[$bands]; 240 unset($data[$bands]); 241 242 return $data; 243 } 244 245 function quiz_report_highlighting_grading_method($quiz, $qmsubselect, $qmfilter) { 246 if ($quiz->attempts == 1) { 247 return '<p>' . get_string('onlyoneattemptallowed', 'quiz_overview') . '</p>'; 248 249 } else if (!$qmsubselect) { 250 return '<p>' . get_string('allattemptscontributetograde', 'quiz_overview') . '</p>'; 251 252 } else if ($qmfilter) { 253 return '<p>' . get_string('showinggraded', 'quiz_overview') . '</p>'; 254 255 } else { 256 return '<p>' . get_string('showinggradedandungraded', 'quiz_overview', 257 '<span class="gradedattempt">' . quiz_get_grading_option_name($quiz->grademethod) . 258 '</span>') . '</p>'; 259 } 260 } 261 262 /** 263 * Get the feedback text for a grade on this quiz. The feedback is 264 * processed ready for display. 265 * 266 * @param float $grade a grade on this quiz. 267 * @param int $quizid the id of the quiz object. 268 * @return string the comment that corresponds to this grade (empty string if there is not one. 269 */ 270 function quiz_report_feedback_for_grade($grade, $quizid, $context) { 271 global $DB; 272 273 static $feedbackcache = array(); 274 275 if (!isset($feedbackcache[$quizid])) { 276 $feedbackcache[$quizid] = $DB->get_records('quiz_feedback', array('quizid' => $quizid)); 277 } 278 279 // With CBM etc, it is possible to get -ve grades, which would then not match 280 // any feedback. Therefore, we replace -ve grades with 0. 281 $grade = max($grade, 0); 282 283 $feedbacks = $feedbackcache[$quizid]; 284 $feedbackid = 0; 285 $feedbacktext = ''; 286 $feedbacktextformat = FORMAT_MOODLE; 287 foreach ($feedbacks as $feedback) { 288 if ($feedback->mingrade <= $grade && $grade < $feedback->maxgrade) { 289 $feedbackid = $feedback->id; 290 $feedbacktext = $feedback->feedbacktext; 291 $feedbacktextformat = $feedback->feedbacktextformat; 292 break; 293 } 294 } 295 296 // Clean the text, ready for display. 297 $formatoptions = new stdClass(); 298 $formatoptions->noclean = true; 299 $feedbacktext = file_rewrite_pluginfile_urls($feedbacktext, 'pluginfile.php', 300 $context->id, 'mod_quiz', 'feedback', $feedbackid); 301 $feedbacktext = format_text($feedbacktext, $feedbacktextformat, $formatoptions); 302 303 return $feedbacktext; 304 } 305 306 /** 307 * Format a number as a percentage out of $quiz->sumgrades 308 * @param number $rawgrade the mark to format. 309 * @param object $quiz the quiz settings 310 * @param bool $round whether to round the results ot $quiz->decimalpoints. 311 */ 312 function quiz_report_scale_summarks_as_percentage($rawmark, $quiz, $round = true) { 313 if ($quiz->sumgrades == 0) { 314 return ''; 315 } 316 if (!is_numeric($rawmark)) { 317 return $rawmark; 318 } 319 320 $mark = $rawmark * 100 / $quiz->sumgrades; 321 if ($round) { 322 $mark = quiz_format_grade($quiz, $mark); 323 } 324 return $mark . '%'; 325 } 326 327 /** 328 * Returns an array of reports to which the current user has access to. 329 * @return array reports are ordered as they should be for display in tabs. 330 */ 331 function quiz_report_list($context) { 332 global $DB; 333 static $reportlist = null; 334 if (!is_null($reportlist)) { 335 return $reportlist; 336 } 337 338 $reports = $DB->get_records('quiz_reports', null, 'displayorder DESC', 'name, capability'); 339 $reportdirs = core_component::get_plugin_list('quiz'); 340 341 // Order the reports tab in descending order of displayorder. 342 $reportcaps = array(); 343 foreach ($reports as $key => $report) { 344 if (array_key_exists($report->name, $reportdirs)) { 345 $reportcaps[$report->name] = $report->capability; 346 } 347 } 348 349 // Add any other reports, which are on disc but not in the DB, on the end. 350 foreach ($reportdirs as $reportname => $notused) { 351 if (!isset($reportcaps[$reportname])) { 352 $reportcaps[$reportname] = null; 353 } 354 } 355 $reportlist = array(); 356 foreach ($reportcaps as $name => $capability) { 357 if (empty($capability)) { 358 $capability = 'mod/quiz:viewreports'; 359 } 360 if (has_capability($capability, $context)) { 361 $reportlist[] = $name; 362 } 363 } 364 return $reportlist; 365 } 366 367 /** 368 * Create a filename for use when downloading data from a quiz report. It is 369 * expected that this will be passed to flexible_table::is_downloading, which 370 * cleans the filename of bad characters and adds the file extension. 371 * @param string $report the type of report. 372 * @param string $courseshortname the course shortname. 373 * @param string $quizname the quiz name. 374 * @return string the filename. 375 */ 376 function quiz_report_download_filename($report, $courseshortname, $quizname) { 377 return $courseshortname . '-' . format_string($quizname, true) . '-' . $report; 378 } 379 380 /** 381 * Get the default report for the current user. 382 * @param object $context the quiz context. 383 */ 384 function quiz_report_default_report($context) { 385 $reports = quiz_report_list($context); 386 return reset($reports); 387 } 388 389 /** 390 * Generate a message saying that this quiz has no questions, with a button to 391 * go to the edit page, if the user has the right capability. 392 * @param object $quiz the quiz settings. 393 * @param object $cm the course_module object. 394 * @param object $context the quiz context. 395 * @return string HTML to output. 396 */ 397 function quiz_no_questions_message($quiz, $cm, $context) { 398 global $OUTPUT; 399 400 $output = ''; 401 $output .= $OUTPUT->notification(get_string('noquestions', 'quiz')); 402 if (has_capability('mod/quiz:manage', $context)) { 403 $output .= $OUTPUT->single_button(new moodle_url('/mod/quiz/edit.php', 404 array('cmid' => $cm->id)), get_string('editquiz', 'quiz'), 'get'); 405 } 406 407 return $output; 408 } 409 410 /** 411 * Should the grades be displayed in this report. That depends on the quiz 412 * display options, and whether the quiz is graded. 413 * @param object $quiz the quiz settings. 414 * @param context $context the quiz context. 415 * @return bool 416 */ 417 function quiz_report_should_show_grades($quiz, context $context) { 418 if ($quiz->timeclose && time() > $quiz->timeclose) { 419 $when = mod_quiz_display_options::AFTER_CLOSE; 420 } else { 421 $when = mod_quiz_display_options::LATER_WHILE_OPEN; 422 } 423 $reviewoptions = mod_quiz_display_options::make_from_quiz($quiz, $when); 424 425 return quiz_has_grades($quiz) && 426 ($reviewoptions->marks >= question_display_options::MARK_AND_MAX || 427 has_capability('moodle/grade:viewhidden', $context)); 428 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |