[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/mod/quiz/report/ -> reportlib.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1