Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 311 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 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   * List of deprecated mod_quiz functions.
  19   *
  20   * @package   mod_quiz
  21   * @copyright 2021 Shamim Rezaie <shamim@moodle.com>
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  use mod_quiz\access_manager;
  26  use mod_quiz\quiz_settings;
  27  use mod_quiz\task\update_overdue_attempts;
  28  
  29  /**
  30   * Internal function used in quiz_get_completion_state. Check passing grade (or no attempts left) requirement for completion.
  31   *
  32   * @deprecated since Moodle 3.11
  33   * @todo MDL-71196 Final deprecation in Moodle 4.3
  34   * @see \mod_quiz\completion\custom_completion
  35   * @param stdClass $course
  36   * @param cm_info|stdClass $cm
  37   * @param int $userid
  38   * @param stdClass $quiz
  39   * @return bool True if the passing grade (or no attempts left) requirement is disabled or met.
  40   * @throws coding_exception
  41   */
  42  function quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz) {
  43      global $CFG;
  44  
  45      debugging('quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', DEBUG_DEVELOPER);
  46  
  47      if (!$cm->completionpassgrade) {
  48          return true;
  49      }
  50  
  51      // Check for passing grade.
  52      require_once($CFG->libdir . '/gradelib.php');
  53      $item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod',
  54              'itemmodule' => 'quiz', 'iteminstance' => $cm->instance, 'outcomeid' => null]);
  55      if ($item) {
  56          $grades = grade_grade::fetch_users_grades($item, [$userid], false);
  57          if (!empty($grades[$userid]) && $grades[$userid]->is_passed($item)) {
  58              return true;
  59          }
  60      }
  61  
  62      // If a passing grade is required and exhausting all available attempts is not accepted for completion,
  63      // then this quiz is not complete.
  64      if (!$quiz->completionattemptsexhausted) {
  65          return false;
  66      }
  67  
  68      // Check if all attempts are used up.
  69      $attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
  70      if (!$attempts) {
  71          return false;
  72      }
  73      $lastfinishedattempt = end($attempts);
  74      $context = context_module::instance($cm->id);
  75      $quizobj = quiz_settings::create($quiz->id, $userid);
  76      $accessmanager = new access_manager($quizobj, time(),
  77              has_capability('mod/quiz:ignoretimelimits', $context, $userid, false));
  78  
  79      return $accessmanager->is_finished(count($attempts), $lastfinishedattempt);
  80  }
  81  
  82  /**
  83   * Internal function used in quiz_get_completion_state. Check minimum attempts requirement for completion.
  84   *
  85   * @deprecated since Moodle 3.11
  86   * @todo MDL-71196 Final deprecation in Moodle 4.3
  87   * @see \mod_quiz\completion\custom_completion
  88   * @param int $userid
  89   * @param stdClass $quiz
  90   * @return bool True if minimum attempts requirement is disabled or met.
  91   */
  92  function quiz_completion_check_min_attempts($userid, $quiz) {
  93  
  94      debugging('quiz_completion_check_min_attempts has been deprecated.', DEBUG_DEVELOPER);
  95  
  96      if (empty($quiz->completionminattempts)) {
  97          return true;
  98      }
  99  
 100      // Check if the user has done enough attempts.
 101      $attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
 102      return $quiz->completionminattempts <= count($attempts);
 103  }
 104  
 105  /**
 106   * Obtains the automatic completion state for this quiz on any conditions
 107   * in quiz settings, such as if all attempts are used or a certain grade is achieved.
 108   *
 109   * @deprecated since Moodle 3.11
 110   * @todo MDL-71196 Final deprecation in Moodle 4.3
 111   * @see \mod_quiz\completion\custom_completion
 112   * @param stdClass $course Course
 113   * @param cm_info|stdClass $cm Course-module
 114   * @param int $userid User ID
 115   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
 116   * @return bool True if completed, false if not. (If no conditions, then return
 117   *   value depends on comparison type)
 118   */
 119  function quiz_get_completion_state($course, $cm, $userid, $type) {
 120      global $DB;
 121  
 122      // No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
 123  
 124      $quiz = $DB->get_record('quiz', ['id' => $cm->instance], '*', MUST_EXIST);
 125      if (!$quiz->completionattemptsexhausted && !$cm->completionpassgrade && !$quiz->completionminattempts) {
 126          return $type;
 127      }
 128  
 129      if (!quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz)) {
 130          return false;
 131      }
 132  
 133      if (!quiz_completion_check_min_attempts($userid, $quiz)) {
 134          return false;
 135      }
 136  
 137      return true;
 138  }
 139  
 140  /**
 141   * Retrieves tag information for the given list of quiz slot ids.
 142   * Currently the only slots that have tags are random question slots.
 143   *
 144   * Example:
 145   * If we have 3 slots with id 1, 2, and 3. The first slot has two tags, the second
 146   * has one tag, and the third has zero tags. The return structure will look like:
 147   * [
 148   *      1 => [
 149   *          quiz_slot_tags.id => { ...tag data... },
 150   *          quiz_slot_tags.id => { ...tag data... },
 151   *      ],
 152   *      2 => [
 153   *          quiz_slot_tags.id => { ...tag data... },
 154   *      ],
 155   *      3 => [],
 156   * ]
 157   *
 158   * @param int[] $slotids The list of id for the quiz slots.
 159   * @return array[] List of quiz_slot_tags records indexed by slot id.
 160   * @deprecated since Moodle 4.0
 161   * @todo Final deprecation on Moodle 4.4 MDL-72438
 162   */
 163  function quiz_retrieve_tags_for_slot_ids($slotids) {
 164      debugging('Method quiz_retrieve_tags_for_slot_ids() is deprecated, ' .
 165          'see filtercondition->tags from the question_set_reference table.', DEBUG_DEVELOPER);
 166      global $DB;
 167      if (empty($slotids)) {
 168          return [];
 169      }
 170  
 171      $slottags = $DB->get_records_list('quiz_slot_tags', 'slotid', $slotids);
 172      $tagsbyid = core_tag_tag::get_bulk(array_filter(array_column($slottags, 'tagid')), 'id, name');
 173      $tagsbyname = false; // It will be loaded later if required.
 174      $emptytagids = array_reduce($slotids, function($carry, $slotid) {
 175          $carry[$slotid] = [];
 176          return $carry;
 177      }, []);
 178  
 179      return array_reduce(
 180          $slottags,
 181          function($carry, $slottag) use ($slottags, $tagsbyid, $tagsbyname) {
 182              if (isset($tagsbyid[$slottag->tagid])) {
 183                  // Make sure that we're returning the most updated tag name.
 184                  $slottag->tagname = $tagsbyid[$slottag->tagid]->name;
 185              } else {
 186                  if ($tagsbyname === false) {
 187                      // We were hoping that this query could be avoided, but life
 188                      // showed its other side to us!
 189                      $tagcollid = core_tag_area::get_collection('core', 'question');
 190                      $tagsbyname = core_tag_tag::get_by_name_bulk(
 191                          $tagcollid,
 192                          array_column($slottags, 'tagname'),
 193                          'id, name'
 194                      );
 195                  }
 196                  if (isset($tagsbyname[$slottag->tagname])) {
 197                      // Make sure that we're returning the current tag id that matches
 198                      // the given tag name.
 199                      $slottag->tagid = $tagsbyname[$slottag->tagname]->id;
 200                  } else {
 201                      // The tag does not exist anymore (neither the tag id nor the tag name
 202                      // matches an existing tag).
 203                      // We still need to include this row in the result as some callers might
 204                      // be interested in these rows. An example is the editing forms that still
 205                      // need to display tag names even if they don't exist anymore.
 206                      $slottag->tagid = null;
 207                  }
 208              }
 209  
 210              $carry[$slottag->slotid][$slottag->id] = $slottag;
 211              return $carry;
 212          },
 213          $emptytagids
 214      );
 215  }
 216  
 217  /**
 218   * Verify that the question exists, and the user has permission to use it.
 219   *
 220   * @deprecated in 4.1 use mod_quiz\structure::has_use_capability(...) instead.
 221   *
 222   * @param stdClass $quiz the quiz settings.
 223   * @param int $slot which question in the quiz to test.
 224   * @return bool whether the user can use this question.
 225   */
 226  function quiz_has_question_use($quiz, $slot) {
 227      global $DB;
 228  
 229      debugging('Deprecated. Please use mod_quiz\structure::has_use_capability instead.');
 230  
 231      $sql = 'SELECT q.*
 232                FROM {quiz_slots} slot
 233                JOIN {question_references} qre ON qre.itemid = slot.id
 234                JOIN {question_bank_entries} qbe ON qbe.id = qre.questionbankentryid
 235                JOIN {question_versions} qve ON qve.questionbankentryid = qbe.id
 236                JOIN {question} q ON q.id = qve.questionid
 237               WHERE slot.quizid = ?
 238                 AND slot.slot = ?
 239                 AND qre.component = ?
 240                 AND qre.questionarea = ?';
 241  
 242      $question = $DB->get_record_sql($sql, [$quiz->id, $slot, 'mod_quiz', 'slot']);
 243  
 244      if (!$question) {
 245          return false;
 246      }
 247      return question_has_capability_on($question, 'use');
 248  }
 249  
 250  /**
 251   * @copyright 2012 the Open University
 252   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 253   * @deprecated since Moodle 4.2. Code moved to mod_quiz\task\update_overdue_attempts.
 254   * @todo MDL-76612 Final deprecation in Moodle 4.6
 255   */
 256  class mod_quiz_overdue_attempt_updater {
 257  
 258      /**
 259       * @deprecated since Moodle 4.2. Code moved to mod_quiz\task\update_overdue_attempts. that was.
 260       */
 261      public function update_overdue_attempts($timenow, $processto) {
 262          debugging('mod_quiz_overdue_attempt_updater has been deprecated. The code wsa moved to ' .
 263                  'mod_quiz\task\update_overdue_attempts.');
 264          return (new update_overdue_attempts())->update_all_overdue_attempts((int) $timenow, (int) $processto);
 265      }
 266  
 267      /**
 268       * @deprecated since Moodle 4.2. Code moved to mod_quiz\task\update_overdue_attempts.
 269       */
 270      public function get_list_of_overdue_attempts($processto) {
 271          debugging('mod_quiz_overdue_attempt_updater has been deprecated. The code wsa moved to ' .
 272                  'mod_quiz\task\update_overdue_attempts.');
 273          return (new update_overdue_attempts())->get_list_of_overdue_attempts((int) $processto);
 274      }
 275  }
 276  
 277  /**
 278   * Class for quiz exceptions. Just saves a couple of arguments on the
 279   * constructor for a moodle_exception.
 280   *
 281   * @copyright 2008 Tim Hunt
 282   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 283   * @since     Moodle 2.0
 284   * @deprecated since Moodle 4.2. Please just use moodle_exception.
 285   * @todo MDL-76612 Final deprecation in Moodle 4.6
 286   */
 287  class moodle_quiz_exception extends moodle_exception {
 288      /**
 289       * Constructor.
 290       *
 291       * @param quiz_settings $quizobj the quiz the error relates to.
 292       * @param string $errorcode The name of the string from error.php to print.
 293       * @param mixed $a Extra words and phrases that might be required in the error string.
 294       * @param string $link The url where the user will be prompted to continue.
 295       *      If no url is provided the user will be directed to the site index page.
 296       * @param string|null $debuginfo optional debugging information.
 297       * @deprecated since Moodle 4.2. Please just use moodle_exception.
 298       */
 299      public function __construct($quizobj, $errorcode, $a = null, $link = '', $debuginfo = null) {
 300          debugging('Class moodle_quiz_exception is deprecated. ' .
 301                  'Please use a standard moodle_exception instead.', DEBUG_DEVELOPER);
 302          if (!$link) {
 303              $link = $quizobj->view_url();
 304          }
 305          parent::__construct($errorcode, 'quiz', $link, $a, $debuginfo);
 306      }
 307  }
 308  
 309  /**
 310   * Update the sumgrades field of the quiz. This needs to be called whenever
 311   * the grading structure of the quiz is changed. For example if a question is
 312   * added or removed, or a question weight is changed.
 313   *
 314   * You should call {@see quiz_delete_previews()} before you call this function.
 315   *
 316   * @param stdClass $quiz a quiz.
 317   * @deprecated since Moodle 4.2. Please use grade_calculator::recompute_quiz_sumgrades.
 318   * @todo MDL-76612 Final deprecation in Moodle 4.6
 319   */
 320  function quiz_update_sumgrades($quiz) {
 321      debugging('quiz_update_sumgrades is deprecated. ' .
 322          'Please use a standard grade_calculator::recompute_quiz_sumgrades instead.', DEBUG_DEVELOPER);
 323      quiz_settings::create($quiz->id)->get_grade_calculator()->recompute_quiz_sumgrades();
 324  }
 325  
 326  /**
 327   * Update the sumgrades field of the attempts at a quiz.
 328   *
 329   * @param stdClass $quiz a quiz.
 330   * @deprecated since Moodle 4.2. Please use grade_calculator::recompute_all_attempt_sumgrades.
 331   * @todo MDL-76612 Final deprecation in Moodle 4.6
 332   */
 333  function quiz_update_all_attempt_sumgrades($quiz) {
 334      debugging('quiz_update_all_attempt_sumgrades is deprecated. ' .
 335          'Please use a standard grade_calculator::recompute_all_attempt_sumgrades instead.', DEBUG_DEVELOPER);
 336      quiz_settings::create($quiz->id)->get_grade_calculator()->recompute_all_attempt_sumgrades();
 337  }
 338  
 339  /**
 340   * Update the final grade at this quiz for all students.
 341   *
 342   * This function is equivalent to calling quiz_save_best_grade for all
 343   * users, but much more efficient.
 344   *
 345   * @param stdClass $quiz the quiz settings.
 346   * @deprecated since Moodle 4.2. Please use grade_calculator::recompute_all_final_grades.
 347   * @todo MDL-76612 Final deprecation in Moodle 4.6
 348   */
 349  function quiz_update_all_final_grades($quiz) {
 350      debugging('quiz_update_all_final_grades is deprecated. ' .
 351          'Please use a standard grade_calculator::recompute_all_final_grades instead.', DEBUG_DEVELOPER);
 352      quiz_settings::create($quiz->id)->get_grade_calculator()->recompute_all_final_grades();
 353  }
 354  
 355  /**
 356   * The quiz grade is the maximum that student's results are marked out of. When it
 357   * changes, the corresponding data in quiz_grades and quiz_feedback needs to be
 358   * rescaled. After calling this function, you probably need to call
 359   * quiz_update_all_attempt_sumgrades, grade_calculator::recompute_all_final_grades();
 360   * quiz_update_grades. (At least, that is what this comment has said for years, but
 361   * it seems to call recompute_all_final_grades itself.)
 362   *
 363   * @param float $newgrade the new maximum grade for the quiz.
 364   * @param stdClass $quiz the quiz we are updating. Passed by reference so its
 365   *      grade field can be updated too.
 366   * @return bool indicating success or failure.
 367   * @deprecated since Moodle 4.2. Please use grade_calculator::update_quiz_maximum_grade.
 368   * @todo MDL-76612 Final deprecation in Moodle 4.6
 369   */
 370  function quiz_set_grade($newgrade, $quiz) {
 371      debugging('quiz_set_grade is deprecated. ' .
 372          'Please use a standard grade_calculator::update_quiz_maximum_grade instead.', DEBUG_DEVELOPER);
 373      quiz_settings::create($quiz->id)->get_grade_calculator()->update_quiz_maximum_grade($newgrade);
 374      return true;
 375  }
 376  
 377  /**
 378   * Save the overall grade for a user at a quiz in the quiz_grades table
 379   *
 380   * @param stdClass $quiz The quiz for which the best grade is to be calculated and then saved.
 381   * @param int $userid The userid to calculate the grade for. Defaults to the current user.
 382   * @param array $attempts The attempts of this user. Useful if you are
 383   * looping through many users. Attempts can be fetched in one master query to
 384   * avoid repeated querying.
 385   * @return bool Indicates success or failure.
 386   * @deprecated since Moodle 4.2. Please use grade_calculator::update_quiz_maximum_grade.
 387   * @todo MDL-76612 Final deprecation in Moodle 4.6
 388   */
 389  function quiz_save_best_grade($quiz, $userid = null, $attempts = []) {
 390      debugging('quiz_save_best_grade is deprecated. ' .
 391          'Please use a standard grade_calculator::recompute_final_grade instead.', DEBUG_DEVELOPER);
 392      quiz_settings::create($quiz->id)->get_grade_calculator()->recompute_final_grade($userid, $attempts);
 393      return true;
 394  }
 395  
 396  /**
 397   * Calculate the overall grade for a quiz given a number of attempts by a particular user.
 398   *
 399   * @param stdClass $quiz    the quiz settings object.
 400   * @param array $attempts an array of all the user's attempts at this quiz in order.
 401   * @return float          the overall grade
 402   * @deprecated since Moodle 4.2. No direct replacement.
 403   * @todo MDL-76612 Final deprecation in Moodle 4.6
 404   */
 405  function quiz_calculate_best_grade($quiz, $attempts) {
 406      debugging('quiz_calculate_best_grade is deprecated with no direct replacement. It was only used ' .
 407          'in one place in the quiz code so this logic is now private to grade_calculator.', DEBUG_DEVELOPER);
 408  
 409      switch ($quiz->grademethod) {
 410  
 411          case QUIZ_ATTEMPTFIRST:
 412              $firstattempt = reset($attempts);
 413              return $firstattempt->sumgrades;
 414  
 415          case QUIZ_ATTEMPTLAST:
 416              $lastattempt = end($attempts);
 417              return $lastattempt->sumgrades;
 418  
 419          case QUIZ_GRADEAVERAGE:
 420              $sum = 0;
 421              $count = 0;
 422              foreach ($attempts as $attempt) {
 423                  if (!is_null($attempt->sumgrades)) {
 424                      $sum += $attempt->sumgrades;
 425                      $count++;
 426                  }
 427              }
 428              if ($count == 0) {
 429                  return null;
 430              }
 431              return $sum / $count;
 432  
 433          case QUIZ_GRADEHIGHEST:
 434          default:
 435              $max = null;
 436              foreach ($attempts as $attempt) {
 437                  if ($attempt->sumgrades > $max) {
 438                      $max = $attempt->sumgrades;
 439                  }
 440              }
 441              return $max;
 442      }
 443  }
 444  
 445  /**
 446   * Return the attempt with the best grade for a quiz
 447   *
 448   * Which attempt is the best depends on $quiz->grademethod. If the grade
 449   * method is GRADEAVERAGE then this function simply returns the last attempt.
 450   * @return stdClass         The attempt with the best grade
 451   * @param stdClass $quiz    The quiz for which the best grade is to be calculated
 452   * @param array $attempts An array of all the attempts of the user at the quiz
 453   * @deprecated since Moodle 4.2. No direct replacement.
 454   * @todo MDL-76612 Final deprecation in Moodle 4.6
 455   */
 456  function quiz_calculate_best_attempt($quiz, $attempts) {
 457      debugging('quiz_calculate_best_attempt is deprecated with no direct replacement. ' .
 458          'It was not used anywhere!', DEBUG_DEVELOPER);
 459  
 460      switch ($quiz->grademethod) {
 461  
 462          case QUIZ_ATTEMPTFIRST:
 463              foreach ($attempts as $attempt) {
 464                  return $attempt;
 465              }
 466              break;
 467  
 468          case QUIZ_GRADEAVERAGE: // We need to do something with it.
 469          case QUIZ_ATTEMPTLAST:
 470              foreach ($attempts as $attempt) {
 471                  $final = $attempt;
 472              }
 473              return $final;
 474  
 475          default:
 476          case QUIZ_GRADEHIGHEST:
 477              $max = -1;
 478              foreach ($attempts as $attempt) {
 479                  if ($attempt->sumgrades > $max) {
 480                      $max = $attempt->sumgrades;
 481                      $maxattempt = $attempt;
 482                  }
 483              }
 484              return $maxattempt;
 485      }
 486  }