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 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   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   * Define all the restore steps that will be used by the restore_assign_activity_task
  19   *
  20   * @package   mod_assign
  21   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once($CFG->dirroot . '/mod/assign/locallib.php');
  28  
  29  /**
  30   * Define the complete assignment structure for restore, with file and id annotations
  31   *
  32   * @package   mod_assign
  33   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class restore_assign_activity_structure_step extends restore_activity_structure_step {
  37  
  38      /**
  39       * Store whether submission details should be included. Details may not be included if the
  40       * this is a team submission, but groups/grouping information was not included in the backup.
  41       */
  42      protected $includesubmission = true;
  43  
  44      /**
  45       * Define the structure of the restore workflow.
  46       *
  47       * @return restore_path_element $structure
  48       */
  49      protected function define_structure() {
  50  
  51          $paths = array();
  52          // To know if we are including userinfo.
  53          $userinfo = $this->get_setting_value('userinfo');
  54  
  55          // Define each element separated.
  56          $paths[] = new restore_path_element('assign', '/activity/assign');
  57          if ($userinfo) {
  58              $submission = new restore_path_element('assign_submission',
  59                                                     '/activity/assign/submissions/submission');
  60              $paths[] = $submission;
  61              $this->add_subplugin_structure('assignsubmission', $submission);
  62              $grade = new restore_path_element('assign_grade', '/activity/assign/grades/grade');
  63              $paths[] = $grade;
  64              $this->add_subplugin_structure('assignfeedback', $grade);
  65              $userflag = new restore_path_element('assign_userflag',
  66                                                     '/activity/assign/userflags/userflag');
  67              $paths[] = $userflag;
  68          }
  69  
  70          $paths[] = new restore_path_element('assign_override', '/activity/assign/overrides/override');
  71          $paths[] = new restore_path_element('assign_plugin_config',
  72                                              '/activity/assign/plugin_configs/plugin_config');
  73  
  74          return $this->prepare_activity_structure($paths);
  75      }
  76  
  77      /**
  78       * Process an assign restore.
  79       *
  80       * @param object $data The data in object form
  81       * @return void
  82       */
  83      protected function process_assign($data) {
  84          global $DB;
  85  
  86          $data = (object)$data;
  87          $oldid = $data->id;
  88          $data->course = $this->get_courseid();
  89  
  90          // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
  91          // See MDL-9367.
  92          $data->allowsubmissionsfromdate = $this->apply_date_offset($data->allowsubmissionsfromdate);
  93          $data->duedate = $this->apply_date_offset($data->duedate);
  94  
  95          // If this is a team submission, but there is no group info we need to flag that the submission
  96          // information should not be included. It should not be restored.
  97          $groupinfo = $this->task->get_setting_value('groups');
  98          if ($data->teamsubmission && !$groupinfo) {
  99              $this->includesubmission = false;
 100          }
 101  
 102          // Reset revealidentities if blindmarking with no user data (MDL-43796).
 103          $userinfo = $this->get_setting_value('userinfo');
 104          if (!$userinfo && $data->blindmarking) {
 105              $data->revealidentities = 0;
 106          }
 107  
 108          if (!empty($data->teamsubmissiongroupingid)) {
 109              $data->teamsubmissiongroupingid = $this->get_mappingid('grouping',
 110                                                                     $data->teamsubmissiongroupingid);
 111          } else {
 112              $data->teamsubmissiongroupingid = 0;
 113          }
 114          if (!isset($data->cutoffdate)) {
 115              $data->cutoffdate = 0;
 116          }
 117          if (!isset($data->gradingduedate)) {
 118              $data->gradingduedate = 0;
 119          } else {
 120              $data->gradingduedate = $this->apply_date_offset($data->gradingduedate);
 121          }
 122          if (!isset($data->markingworkflow)) {
 123              $data->markingworkflow = 0;
 124          }
 125          if (!isset($data->markingallocation)) {
 126              $data->markingallocation = 0;
 127          }
 128          if (!isset($data->preventsubmissionnotingroup)) {
 129              $data->preventsubmissionnotingroup = 0;
 130          }
 131  
 132          if (!empty($data->preventlatesubmissions)) {
 133              $data->cutoffdate = $data->duedate;
 134          } else {
 135              $data->cutoffdate = $this->apply_date_offset($data->cutoffdate);
 136          }
 137  
 138          if ($data->grade < 0) { // Scale found, get mapping.
 139              $data->grade = -($this->get_mappingid('scale', abs($data->grade)));
 140          }
 141  
 142          $newitemid = $DB->insert_record('assign', $data);
 143  
 144          $this->apply_activity_instance($newitemid);
 145      }
 146  
 147      /**
 148       * Process a submission restore
 149       * @param object $data The data in object form
 150       * @return void
 151       */
 152      protected function process_assign_submission($data) {
 153          global $DB;
 154  
 155          if (!$this->includesubmission) {
 156              return;
 157          }
 158  
 159          $data = (object)$data;
 160          $oldid = $data->id;
 161  
 162          $data->assignment = $this->get_new_parentid('assign');
 163  
 164          if ($data->userid > 0) {
 165              $data->userid = $this->get_mappingid('user', $data->userid);
 166          }
 167          if (!empty($data->groupid)) {
 168              $data->groupid = $this->get_mappingid('group', $data->groupid);
 169              if (!$data->groupid) {
 170                  // If the group does not exist, then the submission cannot be viewed and restoring can
 171                  // violate the unique index on the submission table.
 172                  return;
 173              }
 174          } else {
 175              $data->groupid = 0;
 176          }
 177  
 178          // We will correct this in set_latest_submission_field() once all submissions are restored.
 179          $data->latest = 0;
 180  
 181          $newitemid = $DB->insert_record('assign_submission', $data);
 182  
 183          // Note - the old contextid is required in order to be able to restore files stored in
 184          // sub plugin file areas attached to the submissionid.
 185          $this->set_mapping('submission', $oldid, $newitemid, false, null, $this->task->get_old_contextid());
 186      }
 187  
 188      /**
 189       * Process a user_flags restore
 190       * @param object $data The data in object form
 191       * @return void
 192       */
 193      protected function process_assign_userflag($data) {
 194          global $DB;
 195  
 196          $data = (object)$data;
 197          $oldid = $data->id;
 198  
 199          $data->assignment = $this->get_new_parentid('assign');
 200  
 201          $data->userid = $this->get_mappingid('user', $data->userid);
 202          if (!empty($data->allocatedmarker)) {
 203              $data->allocatedmarker = $this->get_mappingid('user', $data->allocatedmarker);
 204          }
 205          if (!empty($data->extensionduedate)) {
 206              $data->extensionduedate = $this->apply_date_offset($data->extensionduedate);
 207          } else {
 208              $data->extensionduedate = 0;
 209          }
 210          // Flags mailed and locked need no translation on restore.
 211  
 212          $newitemid = $DB->insert_record('assign_user_flags', $data);
 213      }
 214  
 215      /**
 216       * Process a grade restore
 217       * @param object $data The data in object form
 218       * @return void
 219       */
 220      protected function process_assign_grade($data) {
 221          global $DB;
 222  
 223          $data = (object)$data;
 224          $oldid = $data->id;
 225  
 226          $data->assignment = $this->get_new_parentid('assign');
 227  
 228          $data->userid = $this->get_mappingid('user', $data->userid);
 229          $data->grader = $this->get_mappingid('user', $data->grader);
 230  
 231          // Handle flags restore to a different table (for upgrade from old backups).
 232          if (!empty($data->extensionduedate) ||
 233                  !empty($data->mailed) ||
 234                  !empty($data->locked)) {
 235              $flags = new stdClass();
 236              $flags->assignment = $this->get_new_parentid('assign');
 237              if (!empty($data->extensionduedate)) {
 238                  $flags->extensionduedate = $this->apply_date_offset($data->extensionduedate);
 239              }
 240              if (!empty($data->mailed)) {
 241                  $flags->mailed = $data->mailed;
 242              }
 243              if (!empty($data->locked)) {
 244                  $flags->locked = $data->locked;
 245              }
 246              $flags->userid = $this->get_mappingid('user', $data->userid);
 247              $DB->insert_record('assign_user_flags', $flags);
 248          }
 249          // Fix null grades that were rescaled.
 250          if ($data->grade < 0 && $data->grade != ASSIGN_GRADE_NOT_SET) {
 251              $data->grade = ASSIGN_GRADE_NOT_SET;
 252          }
 253          $newitemid = $DB->insert_record('assign_grades', $data);
 254  
 255          // Note - the old contextid is required in order to be able to restore files stored in
 256          // sub plugin file areas attached to the gradeid.
 257          $this->set_mapping('grade', $oldid, $newitemid, false, null, $this->task->get_old_contextid());
 258          $this->set_mapping(restore_gradingform_plugin::itemid_mapping('submissions'), $oldid, $newitemid);
 259      }
 260  
 261      /**
 262       * Process a plugin-config restore
 263       * @param object $data The data in object form
 264       * @return void
 265       */
 266      protected function process_assign_plugin_config($data) {
 267          global $DB;
 268  
 269          $data = (object)$data;
 270          $oldid = $data->id;
 271  
 272          $data->assignment = $this->get_new_parentid('assign');
 273  
 274          $newitemid = $DB->insert_record('assign_plugin_config', $data);
 275      }
 276  
 277      /**
 278       * For all submissions in this assignment, either set the
 279       * submission->latest field to 1 for the latest attempts
 280       * or create a new submission record for grades with no submission.
 281       *
 282       * @return void
 283       */
 284      protected function set_latest_submission_field() {
 285          global $DB, $CFG;
 286  
 287          // Required for constants.
 288          require_once($CFG->dirroot . '/mod/assign/locallib.php');
 289  
 290          $assignmentid = $this->get_new_parentid('assign');
 291  
 292          // First check for records with a grade, but no submission record.
 293          // This happens when a teacher marks a student before they have submitted anything.
 294          $records = $DB->get_recordset_sql('SELECT g.id, g.userid, g.attemptnumber
 295                                             FROM {assign_grades} g
 296                                        LEFT JOIN {assign_submission} s
 297                                               ON s.assignment = g.assignment
 298                                              AND s.userid = g.userid
 299                                            WHERE s.id IS NULL AND g.assignment = ?', array($assignmentid));
 300  
 301          $submissions = array();
 302          foreach ($records as $record) {
 303              $submission = new stdClass();
 304              $submission->assignment = $assignmentid;
 305              $submission->userid = $record->userid;
 306              $submission->attemptnumber = $record->attemptnumber;
 307              $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
 308              $submission->groupid = 0;
 309              $submission->latest = 0;
 310              $submission->timecreated = time();
 311              $submission->timemodified = time();
 312              array_push($submissions, $submission);
 313          }
 314  
 315          $records->close();
 316  
 317          $DB->insert_records('assign_submission', $submissions);
 318  
 319          // This code could be rewritten as a monster SQL - but the point of adding this "latest" field
 320          // to the submissions table in the first place was to get away from those hard to maintain SQL queries.
 321  
 322          // First user submissions.
 323          $sql = 'SELECT DISTINCT userid FROM {assign_submission} WHERE assignment = ? AND groupid = ?';
 324          $params = array($assignmentid, 0);
 325          $users = $DB->get_records_sql($sql, $params);
 326  
 327          foreach ($users as $userid => $unused) {
 328              $params = array('assignment'=>$assignmentid, 'groupid'=>0, 'userid'=>$userid);
 329  
 330              // Only return the row with the highest attemptnumber.
 331              $submission = null;
 332              $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
 333              if ($submissions) {
 334                  $submission = reset($submissions);
 335                  $submission->latest = 1;
 336                  $DB->update_record('assign_submission', $submission);
 337              }
 338          }
 339          // Then group submissions (if any).
 340          $sql = 'SELECT DISTINCT groupid FROM {assign_submission} WHERE assignment = ? AND userid = ?';
 341          $params = array($assignmentid, 0);
 342          $groups = $DB->get_records_sql($sql, $params);
 343  
 344          foreach ($groups as $groupid => $unused) {
 345              $params = array('assignment'=>$assignmentid, 'userid'=>0, 'groupid'=>$groupid);
 346  
 347              // Only return the row with the highest attemptnumber.
 348              $submission = null;
 349              $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
 350              if ($submissions) {
 351                  $submission = reset($submissions);
 352                  $submission->latest = 1;
 353                  $DB->update_record('assign_submission', $submission);
 354              }
 355          }
 356      }
 357  
 358      /**
 359       * Restore files from plugin configuration
 360       * @param string $subtype the plugin type to handle
 361       * @return void
 362       */
 363      protected function add_plugin_config_files($subtype) {
 364          $dummyassign = new assign(null, null, null);
 365          $plugins = $dummyassign->load_plugins($subtype);
 366          foreach ($plugins as $plugin) {
 367              $component = $plugin->get_subtype() . '_' . $plugin->get_type();
 368              $areas = $plugin->get_config_file_areas();
 369              foreach ($areas as $area) {
 370                  $this->add_related_files($component, $area, null);
 371              }
 372          }
 373      }
 374  
 375      /**
 376       * Process a assign override restore
 377       * @param object $data The data in object form
 378       * @return void
 379       */
 380      protected function process_assign_override($data) {
 381          global $DB;
 382  
 383          $data = (object)$data;
 384          $oldid = $data->id;
 385  
 386          // Based on userinfo, we'll restore user overides or no.
 387          $userinfo = $this->get_setting_value('userinfo');
 388  
 389          // Skip user overrides if we are not restoring userinfo.
 390          if (!$userinfo && !is_null($data->userid)) {
 391              return;
 392          }
 393  
 394          // Skip group overrides if we are not restoring groupinfo.
 395          $groupinfo = $this->get_setting_value('groups');
 396          if (!$groupinfo && !is_null($data->groupid)) {
 397              return;
 398          }
 399  
 400          $data->assignid = $this->get_new_parentid('assign');
 401  
 402          if (!is_null($data->userid)) {
 403              $data->userid = $this->get_mappingid('user', $data->userid);
 404          }
 405          if (!is_null($data->groupid)) {
 406              $data->groupid = $this->get_mappingid('group', $data->groupid);
 407          }
 408  
 409          $data->allowsubmissionsfromdate = $this->apply_date_offset($data->allowsubmissionsfromdate);
 410          $data->duedate = $this->apply_date_offset($data->duedate);
 411          $data->cutoffdate = $this->apply_date_offset($data->cutoffdate);
 412  
 413          $newitemid = $DB->insert_record('assign_overrides', $data);
 414  
 415          // Add mapping, restore of logs needs it.
 416          $this->set_mapping('assign_override', $oldid, $newitemid);
 417      }
 418  
 419      /**
 420       * Once the database tables have been fully restored, restore the files
 421       * @return void
 422       */
 423      protected function after_execute() {
 424          $this->add_related_files('mod_assign', 'intro', null);
 425          $this->add_related_files('mod_assign', 'introattachment', null);
 426  
 427          $this->add_plugin_config_files('assignsubmission');
 428          $this->add_plugin_config_files('assignfeedback');
 429  
 430          $this->set_latest_submission_field();
 431      }
 432  }