Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [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  namespace mod_quiz\task;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  use context_course;
  22  use core_user;
  23  use moodle_recordset;
  24  use question_display_options;
  25  use mod_quiz_display_options;
  26  use quiz_attempt;
  27  
  28  require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  29  
  30  /**
  31   * Cron Quiz Notify Attempts Graded Task.
  32   *
  33   * @package    mod_quiz
  34   * @copyright  2021 The Open University
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   *
  37   */
  38  class quiz_notify_attempt_manual_grading_completed extends \core\task\scheduled_task {
  39      /**
  40       * @var int|null For using in unit testing only. Override the time we consider as now.
  41       */
  42      protected $forcedtime = null;
  43  
  44      /**
  45       * Get name of schedule task.
  46       *
  47       * @return string
  48       */
  49      public function get_name(): string {
  50          return get_string('notifyattemptsgradedtask', 'mod_quiz');
  51      }
  52  
  53      /**
  54       * To let this class be unit tested, we wrap all accesses to the current time in this method.
  55       *
  56       * @return int The current time.
  57       */
  58      protected function get_time(): int {
  59          if (PHPUNIT_TEST && $this->forcedtime !== null) {
  60              return $this->forcedtime;
  61          }
  62  
  63          return time();
  64      }
  65  
  66      /**
  67       * For testing only, pretend the current time is different.
  68       *
  69       * @param int $time The time to set as the current time.
  70       */
  71      public function set_time_for_testing(int $time): void {
  72          if (!PHPUNIT_TEST) {
  73              throw new \coding_exception('set_time_for_testing should only be used in unit tests.');
  74          }
  75          $this->forcedtime = $time;
  76      }
  77  
  78      /**
  79       * Execute sending notification for manual graded attempts.
  80       */
  81      public function execute() {
  82          global $DB;
  83  
  84          mtrace('Looking for quiz attempts which may need a graded notification sent...');
  85  
  86          $attempts = $this->get_list_of_attempts();
  87          $course = null;
  88          $quiz = null;
  89          $cm = null;
  90  
  91          foreach ($attempts as $attempt) {
  92              mtrace('Checking attempt ' . $attempt->id . ' at quiz ' . $attempt->quiz . '.');
  93  
  94              if (!$quiz || $attempt->quiz != $quiz->id) {
  95                  $quiz = $DB->get_record('quiz', ['id' => $attempt->quiz], '*', MUST_EXIST);
  96                  $cm = get_coursemodule_from_instance('quiz', $attempt->quiz);
  97              }
  98  
  99              if (!$course || $course->id != $quiz->course) {
 100                  $course = $DB->get_record('course', ['id' => $quiz->course], '*', MUST_EXIST);
 101                  $coursecontext = context_course::instance($quiz->course);
 102              }
 103  
 104              $quiz = quiz_update_effective_access($quiz, $attempt->userid);
 105              $attemptobj = new quiz_attempt($attempt, $quiz, $cm, $course, false);
 106              $options = mod_quiz_display_options::make_from_quiz($quiz, quiz_attempt_state($quiz, $attempt));
 107  
 108              if ($options->manualcomment == question_display_options::HIDDEN) {
 109                  // User cannot currently see the feedback, so don't message them.
 110                  // However, this may change in future, so leave them on the list.
 111                  continue;
 112              }
 113  
 114              if (!has_capability('mod/quiz:emailnotifyattemptgraded', $coursecontext, $attempt->userid, false)) {
 115                  // User not eligible to get a notification. Mark them done while doing nothing.
 116                  $DB->set_field('quiz_attempts', 'gradednotificationsenttime', $attempt->timefinish, ['id' => $attempt->id]);
 117                  continue;
 118              }
 119  
 120              // OK, send notification.
 121              mtrace('Sending email to user ' . $attempt->userid . '...');
 122              $ok = quiz_send_notify_manual_graded_message($attemptobj, core_user::get_user($attempt->userid));
 123              if ($ok) {
 124                  mtrace('Send email successfully!');
 125                  $attempt->gradednotificationsenttime = $this->get_time();
 126                  $DB->set_field('quiz_attempts', 'gradednotificationsenttime', $attempt->gradednotificationsenttime,
 127                          ['id' => $attempt->id]);
 128                  $attemptobj->fire_attempt_manual_grading_completed_event();
 129              }
 130          }
 131  
 132          $attempts->close();
 133      }
 134  
 135      /**
 136       * Get a number of records as an array of quiz_attempts using a SQL statement.
 137       *
 138       * @return moodle_recordset Of quiz_attempts that need to be processed.
 139       */
 140      public function get_list_of_attempts(): moodle_recordset {
 141          global $DB;
 142  
 143          $delaytime = $this->get_time() - get_config('quiz', 'notifyattemptgradeddelay');
 144  
 145          $sql = "SELECT qa.*
 146                    FROM {quiz_attempts} qa
 147                    JOIN {quiz} quiz ON quiz.id = qa.quiz
 148                   WHERE qa.state = 'finished'
 149                         AND qa.gradednotificationsenttime IS NULL
 150                         AND qa.sumgrades IS NOT NULL
 151                         AND qa.timemodified < :delaytime
 152                ORDER BY quiz.course, qa.quiz";
 153  
 154          return $DB->get_recordset_sql($sql, ['delaytime' => $delaytime]);
 155      }
 156  }