Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 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   * 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  
 115          if (!isset($data->cutoffdate)) {
 116              $data->cutoffdate = 0;
 117          }
 118          if (!isset($data->gradingduedate)) {
 119              $data->gradingduedate = 0;
 120          } else {
 121              $data->gradingduedate = $this->apply_date_offset($data->gradingduedate);
 122          }
 123          if (!isset($data->markingworkflow)) {
 124              $data->markingworkflow = 0;
 125          }
 126          if (!isset($data->markingallocation)) {
 127              $data->markingallocation = 0;
 128          }
 129          if (!isset($data->preventsubmissionnotingroup)) {
 130              $data->preventsubmissionnotingroup = 0;
 131          }
 132  
 133          if (!empty($data->preventlatesubmissions)) {
 134              $data->cutoffdate = $data->duedate;
 135          } else {
 136              $data->cutoffdate = $this->apply_date_offset($data->cutoffdate);
 137          }
 138  
 139          if ($data->grade < 0) { // Scale found, get mapping.
 140              $data->grade = -($this->get_mappingid('scale', abs($data->grade)));
 141          }
 142  
 143          $newitemid = $DB->insert_record('assign', $data);
 144  
 145          $this->apply_activity_instance($newitemid);
 146      }
 147  
 148      /**
 149       * Process a submission restore
 150       * @param object $data The data in object form
 151       * @return void
 152       */
 153      protected function process_assign_submission($data) {
 154          global $DB;
 155  
 156          if (!$this->includesubmission) {
 157              return;
 158          }
 159  
 160          $data = (object)$data;
 161          $oldid = $data->id;
 162  
 163          $data->assignment = $this->get_new_parentid('assign');
 164  
 165          if ($data->userid > 0) {
 166              $data->userid = $this->get_mappingid('user', $data->userid);
 167          }
 168          if (!empty($data->groupid)) {
 169              $data->groupid = $this->get_mappingid('group', $data->groupid);
 170              if (!$data->groupid) {
 171                  // If the group does not exist, then the submission cannot be viewed and restoring can
 172                  // violate the unique index on the submission table.
 173                  return;
 174              }
 175          } else {
 176              $data->groupid = 0;
 177          }
 178  
 179          // We will correct this in set_latest_submission_field() once all submissions are restored.
 180          $data->latest = 0;
 181  
 182          $newitemid = $DB->insert_record('assign_submission', $data);
 183  
 184          // Note - the old contextid is required in order to be able to restore files stored in
 185          // sub plugin file areas attached to the submissionid.
 186          $this->set_mapping('submission', $oldid, $newitemid, false, null, $this->task->get_old_contextid());
 187      }
 188  
 189      /**
 190       * Process a user_flags restore
 191       * @param object $data The data in object form
 192       * @return void
 193       */
 194      protected function process_assign_userflag($data) {
 195          global $DB;
 196  
 197          $data = (object)$data;
 198          $oldid = $data->id;
 199  
 200          $data->assignment = $this->get_new_parentid('assign');
 201  
 202          $data->userid = $this->get_mappingid('user', $data->userid);
 203          if (!empty($data->allocatedmarker)) {
 204              $data->allocatedmarker = $this->get_mappingid('user', $data->allocatedmarker);
 205          }
 206          if (!empty($data->extensionduedate)) {
 207              $data->extensionduedate = $this->apply_date_offset($data->extensionduedate);
 208          } else {
 209              $data->extensionduedate = 0;
 210          }
 211          // Flags mailed and locked need no translation on restore.
 212  
 213          $newitemid = $DB->insert_record('assign_user_flags', $data);
 214      }
 215  
 216      /**
 217       * Process a grade restore
 218       * @param object $data The data in object form
 219       * @return void
 220       */
 221      protected function process_assign_grade($data) {
 222          global $DB;
 223  
 224          $data = (object)$data;
 225          $oldid = $data->id;
 226  
 227          $data->assignment = $this->get_new_parentid('assign');
 228  
 229          $data->userid = $this->get_mappingid('user', $data->userid);
 230          $data->grader = $this->get_mappingid('user', $data->grader);
 231  
 232          // Handle flags restore to a different table (for upgrade from old backups).
 233          if (!empty($data->extensionduedate) ||
 234                  !empty($data->mailed) ||
 235                  !empty($data->locked)) {
 236              $flags = new stdClass();
 237              $flags->assignment = $this->get_new_parentid('assign');
 238              if (!empty($data->extensionduedate)) {
 239                  $flags->extensionduedate = $this->apply_date_offset($data->extensionduedate);
 240              }
 241              if (!empty($data->mailed)) {
 242                  $flags->mailed = $data->mailed;
 243              }
 244              if (!empty($data->locked)) {
 245                  $flags->locked = $data->locked;
 246              }
 247              $flags->userid = $this->get_mappingid('user', $data->userid);
 248              $DB->insert_record('assign_user_flags', $flags);
 249          }
 250          // Fix null grades that were rescaled.
 251          if ($data->grade < 0 && $data->grade != ASSIGN_GRADE_NOT_SET) {
 252              $data->grade = ASSIGN_GRADE_NOT_SET;
 253          }
 254          $newitemid = $DB->insert_record('assign_grades', $data);
 255  
 256          // Note - the old contextid is required in order to be able to restore files stored in
 257          // sub plugin file areas attached to the gradeid.
 258          $this->set_mapping('grade', $oldid, $newitemid, false, null, $this->task->get_old_contextid());
 259          $this->set_mapping(restore_gradingform_plugin::itemid_mapping('submissions'), $oldid, $newitemid);
 260      }
 261  
 262      /**
 263       * Process a plugin-config restore
 264       * @param object $data The data in object form
 265       * @return void
 266       */
 267      protected function process_assign_plugin_config($data) {
 268          global $DB;
 269  
 270          $data = (object)$data;
 271          $oldid = $data->id;
 272  
 273          $data->assignment = $this->get_new_parentid('assign');
 274  
 275          $newitemid = $DB->insert_record('assign_plugin_config', $data);
 276      }
 277  
 278      /**
 279       * For all submissions in this assignment, either set the
 280       * submission->latest field to 1 for the latest attempts
 281       * or create a new submission record for grades with no submission.
 282       *
 283       * @return void
 284       */
 285      protected function set_latest_submission_field() {
 286          global $DB, $CFG;
 287  
 288          // Required for constants.
 289          require_once($CFG->dirroot . '/mod/assign/locallib.php');
 290  
 291          $assignmentid = $this->get_new_parentid('assign');
 292  
 293          // First check for records with a grade, but no submission record.
 294          // This happens when a teacher marks a student before they have submitted anything.
 295          $records = $DB->get_recordset_sql('SELECT g.id, g.userid, g.attemptnumber
 296                                             FROM {assign_grades} g
 297                                        LEFT JOIN {assign_submission} s
 298                                               ON s.assignment = g.assignment
 299                                              AND s.userid = g.userid
 300                                            WHERE s.id IS NULL AND g.assignment = ?', array($assignmentid));
 301  
 302          $submissions = array();
 303          foreach ($records as $record) {
 304              $submission = new stdClass();
 305              $submission->assignment = $assignmentid;
 306              $submission->userid = $record->userid;
 307              $submission->attemptnumber = $record->attemptnumber;
 308              $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
 309              $submission->groupid = 0;
 310              $submission->latest = 0;
 311              $submission->timecreated = time();
 312              $submission->timemodified = time();
 313              array_push($submissions, $submission);
 314          }
 315  
 316          $records->close();
 317  
 318          $DB->insert_records('assign_submission', $submissions);
 319  
 320          // This code could be rewritten as a monster SQL - but the point of adding this "latest" field
 321          // to the submissions table in the first place was to get away from those hard to maintain SQL queries.
 322  
 323          // First user submissions.
 324          $sql = 'SELECT DISTINCT userid FROM {assign_submission} WHERE assignment = ? AND groupid = ?';
 325          $params = array($assignmentid, 0);
 326          $users = $DB->get_records_sql($sql, $params);
 327  
 328          foreach ($users as $userid => $unused) {
 329              $params = array('assignment'=>$assignmentid, 'groupid'=>0, 'userid'=>$userid);
 330  
 331              // Only return the row with the highest attemptnumber.
 332              $submission = null;
 333              $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
 334              if ($submissions) {
 335                  $submission = reset($submissions);
 336                  $submission->latest = 1;
 337                  $DB->update_record('assign_submission', $submission);
 338              }
 339          }
 340          // Then group submissions (if any).
 341          $sql = 'SELECT DISTINCT groupid FROM {assign_submission} WHERE assignment = ? AND userid = ?';
 342          $params = array($assignmentid, 0);
 343          $groups = $DB->get_records_sql($sql, $params);
 344  
 345          foreach ($groups as $groupid => $unused) {
 346              $params = array('assignment'=>$assignmentid, 'userid'=>0, 'groupid'=>$groupid);
 347  
 348              // Only return the row with the highest attemptnumber.
 349              $submission = null;
 350              $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
 351              if ($submissions) {
 352                  $submission = reset($submissions);
 353                  $submission->latest = 1;
 354                  $DB->update_record('assign_submission', $submission);
 355              }
 356          }
 357      }
 358  
 359      /**
 360       * Restore files from plugin configuration
 361       * @param string $subtype the plugin type to handle
 362       * @return void
 363       */
 364      protected function add_plugin_config_files($subtype) {
 365          $dummyassign = new assign(null, null, null);
 366          $plugins = $dummyassign->load_plugins($subtype);
 367          foreach ($plugins as $plugin) {
 368              $component = $plugin->get_subtype() . '_' . $plugin->get_type();
 369              $areas = $plugin->get_config_file_areas();
 370              foreach ($areas as $area) {
 371                  $this->add_related_files($component, $area, null);
 372              }
 373          }
 374      }
 375  
 376      /**
 377       * Process a assign override restore
 378       * @param object $data The data in object form
 379       * @return void
 380       */
 381      protected function process_assign_override($data) {
 382          global $DB;
 383  
 384          $data = (object)$data;
 385          $oldid = $data->id;
 386  
 387          // Based on userinfo, we'll restore user overides or no.
 388          $userinfo = $this->get_setting_value('userinfo');
 389  
 390          // Skip user overrides if we are not restoring userinfo.
 391          if (!$userinfo && !is_null($data->userid)) {
 392              return;
 393          }
 394  
 395          // Skip group overrides if we are not restoring groupinfo.
 396          $groupinfo = $this->get_setting_value('groups');
 397          if (!$groupinfo && !is_null($data->groupid)) {
 398              return;
 399          }
 400  
 401          $data->assignid = $this->get_new_parentid('assign');
 402  
 403          if (!is_null($data->userid)) {
 404              $data->userid = $this->get_mappingid('user', $data->userid);
 405          }
 406          if (!is_null($data->groupid)) {
 407              $data->groupid = $this->get_mappingid('group', $data->groupid);
 408          }
 409  
 410          $data->allowsubmissionsfromdate = $this->apply_date_offset($data->allowsubmissionsfromdate);
 411          $data->duedate = $this->apply_date_offset($data->duedate);
 412          $data->cutoffdate = $this->apply_date_offset($data->cutoffdate);
 413  
 414          $newitemid = $DB->insert_record('assign_overrides', $data);
 415  
 416          // Add mapping, restore of logs needs it.
 417          $this->set_mapping('assign_override', $oldid, $newitemid);
 418      }
 419  
 420      /**
 421       * Once the database tables have been fully restored, restore the files
 422       * @return void
 423       */
 424      protected function after_execute() {
 425          $this->add_related_files('mod_assign', 'intro', null);
 426          $this->add_related_files('mod_assign', 'introattachment', null);
 427  
 428          $this->add_plugin_config_files('assignsubmission');
 429          $this->add_plugin_config_files('assignfeedback');
 430  
 431          $this->set_latest_submission_field();
 432      }
 433  }