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

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Defines various backup steps that will be used by common tasks in backup
  20   *
  21   * @package     core_backup
  22   * @subpackage  moodle2
  23   * @category    backup
  24   * @copyright   2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  25   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  /**
  31   * Create the temp dir where backup/restore will happen and create temp ids table.
  32   */
  33  class create_and_clean_temp_stuff extends backup_execution_step {
  34  
  35      protected function define_execution() {
  36          $progress = $this->task->get_progress();
  37          $progress->start_progress('Deleting backup directories');
  38          backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
  39          backup_helper::clear_backup_dir($this->get_backupid(), $progress);           // Empty temp dir, just in case
  40          backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
  41          backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
  42          $progress->end_progress();
  43      }
  44  }
  45  
  46  /**
  47   * Delete the temp dir used by backup/restore (conditionally) and drop temp ids table.
  48   * Note we delete the directory but not the corresponding log file that will be
  49   * there until cron cleans it up.
  50   */
  51  class drop_and_clean_temp_stuff extends backup_execution_step {
  52  
  53      protected $skipcleaningtempdir = false;
  54  
  55      protected function define_execution() {
  56          global $CFG;
  57  
  58          backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
  59          // Delete temp dir conditionally:
  60          // 1) If $CFG->keeptempdirectoriesonbackup is not enabled
  61          // 2) If backup temp dir deletion has been marked to be avoided
  62          if (empty($CFG->keeptempdirectoriesonbackup) && !$this->skipcleaningtempdir) {
  63              $progress = $this->task->get_progress();
  64              $progress->start_progress('Deleting backup dir');
  65              backup_helper::delete_backup_dir($this->get_backupid(), $progress); // Empty backup dir
  66              $progress->end_progress();
  67          }
  68      }
  69  
  70      public function skip_cleaning_temp_dir($skip) {
  71          $this->skipcleaningtempdir = $skip;
  72      }
  73  }
  74  
  75  /**
  76   * Create the directory where all the task (activity/block...) information will be stored
  77   */
  78  class create_taskbasepath_directory extends backup_execution_step {
  79  
  80      protected function define_execution() {
  81          global $CFG;
  82          $basepath = $this->task->get_taskbasepath();
  83          if (!check_dir_exists($basepath, true, true)) {
  84              throw new backup_step_exception('cannot_create_taskbasepath_directory', $basepath);
  85          }
  86      }
  87  }
  88  
  89  /**
  90   * Abstract structure step, parent of all the activity structure steps. Used to wrap the
  91   * activity structure definition within the main <activity ...> tag.
  92   */
  93  abstract class backup_activity_structure_step extends backup_structure_step {
  94  
  95      /**
  96       * Wraps any activity backup structure within the common 'activity' element
  97       * that will include common to all activities information like id, context...
  98       *
  99       * @param backup_nested_element $activitystructure the element to wrap
 100       * @return backup_nested_element the $activitystructure wrapped by the common 'activity' element
 101       */
 102      protected function prepare_activity_structure($activitystructure) {
 103  
 104          // Create the wrap element
 105          $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);
 106  
 107          // Build the tree
 108          $activity->add_child($activitystructure);
 109  
 110          // Set the source
 111          $activityarr = array((object)array(
 112              'id'         => $this->task->get_activityid(),
 113              'moduleid'   => $this->task->get_moduleid(),
 114              'modulename' => $this->task->get_modulename(),
 115              'contextid'  => $this->task->get_contextid()));
 116  
 117          $activity->set_source_array($activityarr);
 118  
 119          // Return the root element (activity)
 120          return $activity;
 121      }
 122  }
 123  
 124  /**
 125   * Helper code for use by any plugin that stores question attempt data that it needs to back up.
 126   */
 127  trait backup_questions_attempt_data_trait {
 128  
 129      /**
 130       * Attach to $element (usually attempts) the needed backup structures
 131       * for question_usages and all the associated data.
 132       *
 133       * @param backup_nested_element $element the element that will contain all the question_usages data.
 134       * @param string $usageidname the name of the element that holds the usageid.
 135       *      This must be child of $element, and must be a final element.
 136       * @param string $nameprefix this prefix is added to all the element names we create.
 137       *      Element names in the XML must be unique, so if you are using usages in
 138       *      two different ways, you must give a prefix to at least one of them. If
 139       *      you only use one sort of usage, then you can just use the default empty prefix.
 140       *      This should include a trailing underscore. For example "myprefix_"
 141       */
 142      protected function add_question_usages($element, $usageidname, $nameprefix = '') {
 143          global $CFG;
 144          require_once($CFG->dirroot . '/question/engine/lib.php');
 145  
 146          // Check $element is one nested_backup_element
 147          if (! $element instanceof backup_nested_element) {
 148              throw new backup_step_exception('question_states_bad_parent_element', $element);
 149          }
 150          if (! $element->get_final_element($usageidname)) {
 151              throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname);
 152          }
 153  
 154          $quba = new backup_nested_element($nameprefix . 'question_usage', array('id'),
 155                  array('component', 'preferredbehaviour'));
 156  
 157          $qas = new backup_nested_element($nameprefix . 'question_attempts');
 158          $qa = new backup_nested_element($nameprefix . 'question_attempt', array('id'), array(
 159                  'slot', 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction',
 160                  'flagged', 'questionsummary', 'rightanswer', 'responsesummary',
 161                  'timemodified'));
 162  
 163          $steps = new backup_nested_element($nameprefix . 'steps');
 164          $step = new backup_nested_element($nameprefix . 'step', array('id'), array(
 165                  'sequencenumber', 'state', 'fraction', 'timecreated', 'userid'));
 166  
 167          $response = new backup_nested_element($nameprefix . 'response');
 168          $variable = new backup_nested_element($nameprefix . 'variable', null,  array('name', 'value'));
 169  
 170          // Build the tree
 171          $element->add_child($quba);
 172          $quba->add_child($qas);
 173          $qas->add_child($qa);
 174          $qa->add_child($steps);
 175          $steps->add_child($step);
 176          $step->add_child($response);
 177          $response->add_child($variable);
 178  
 179          // Set the sources
 180          $quba->set_source_table('question_usages',
 181                  array('id'                => '../' . $usageidname));
 182          $qa->set_source_table('question_attempts', array('questionusageid' => backup::VAR_PARENTID), 'slot ASC');
 183          $step->set_source_table('question_attempt_steps', array('questionattemptid' => backup::VAR_PARENTID), 'sequencenumber ASC');
 184          $variable->set_source_table('question_attempt_step_data', array('attemptstepid' => backup::VAR_PARENTID));
 185  
 186          // Annotate ids
 187          $qa->annotate_ids('question', 'questionid');
 188          $step->annotate_ids('user', 'userid');
 189  
 190          // Annotate files
 191          $fileareas = question_engine::get_all_response_file_areas();
 192          foreach ($fileareas as $filearea) {
 193              $step->annotate_files('question', $filearea, 'id');
 194          }
 195      }
 196  }
 197  
 198  
 199  /**
 200   * Abstract structure step to help activities that store question attempt data.
 201   *
 202   * @copyright 2011 The Open University
 203   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 204   */
 205  abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
 206      use backup_questions_attempt_data_trait;
 207  }
 208  
 209  
 210  /**
 211   * backup structure step in charge of calculating the categories to be
 212   * included in backup, based in the context being backuped (module/course)
 213   * and the already annotated questions present in backup_ids_temp
 214   */
 215  class backup_calculate_question_categories extends backup_execution_step {
 216  
 217      protected function define_execution() {
 218          backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());
 219      }
 220  }
 221  
 222  /**
 223   * backup structure step in charge of deleting all the questions annotated
 224   * in the backup_ids_temp table
 225   */
 226  class backup_delete_temp_questions extends backup_execution_step {
 227  
 228      protected function define_execution() {
 229          backup_question_dbops::delete_temp_questions($this->get_backupid());
 230      }
 231  }
 232  
 233  /**
 234   * Abstract structure step, parent of all the block structure steps. Used to wrap the
 235   * block structure definition within the main <block ...> tag
 236   */
 237  abstract class backup_block_structure_step extends backup_structure_step {
 238  
 239      protected function prepare_block_structure($blockstructure) {
 240  
 241          // Create the wrap element
 242          $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);
 243  
 244          // Build the tree
 245          $block->add_child($blockstructure);
 246  
 247          // Set the source
 248          $blockarr = array((object)array(
 249              'id'         => $this->task->get_blockid(),
 250              'blockname'  => $this->task->get_blockname(),
 251              'contextid'  => $this->task->get_contextid()));
 252  
 253          $block->set_source_array($blockarr);
 254  
 255          // Return the root element (block)
 256          return $block;
 257      }
 258  }
 259  
 260  /**
 261   * structure step that will generate the module.xml file for the activity,
 262   * accumulating various information about the activity, annotating groupings
 263   * and completion/avail conf
 264   */
 265  class backup_module_structure_step extends backup_structure_step {
 266  
 267      protected function define_structure() {
 268          global $DB;
 269  
 270          // Define each element separated
 271  
 272          $module = new backup_nested_element('module', array('id', 'version'), array(
 273              'modulename', 'sectionid', 'sectionnumber', 'idnumber',
 274              'added', 'score', 'indent', 'visible', 'visibleoncoursepage',
 275              'visibleold', 'groupmode', 'groupingid',
 276              'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
 277              'availability', 'showdescription'));
 278  
 279          $tags = new backup_nested_element('tags');
 280          $tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));
 281  
 282          // attach format plugin structure to $module element, only one allowed
 283          $this->add_plugin_structure('format', $module, false);
 284  
 285          // Attach report plugin structure to $module element, multiple allowed.
 286          $this->add_plugin_structure('report', $module, true);
 287  
 288          // attach plagiarism plugin structure to $module element, there can be potentially
 289          // many plagiarism plugins storing information about this course
 290          $this->add_plugin_structure('plagiarism', $module, true);
 291  
 292          // attach local plugin structure to $module, multiple allowed
 293          $this->add_plugin_structure('local', $module, true);
 294  
 295          // Attach admin tools plugin structure to $module.
 296          $this->add_plugin_structure('tool', $module, true);
 297  
 298          $module->add_child($tags);
 299          $tags->add_child($tag);
 300  
 301          // Set the sources
 302          $concat = $DB->sql_concat("'mod_'", 'm.name');
 303          $module->set_source_sql("
 304              SELECT cm.*, cp.value AS version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
 305                FROM {course_modules} cm
 306                JOIN {modules} m ON m.id = cm.module
 307                JOIN {config_plugins} cp ON cp.plugin = $concat AND cp.name = 'version'
 308                JOIN {course_sections} s ON s.id = cm.section
 309               WHERE cm.id = ?", array(backup::VAR_MODID));
 310  
 311          $tag->set_source_sql("SELECT t.id, t.name, t.rawname
 312                                  FROM {tag} t
 313                                  JOIN {tag_instance} ti ON ti.tagid = t.id
 314                                 WHERE ti.itemtype = 'course_modules'
 315                                   AND ti.component = 'core'
 316                                   AND ti.itemid = ?", array(backup::VAR_MODID));
 317  
 318          // Define annotations
 319          $module->annotate_ids('grouping', 'groupingid');
 320  
 321          // Return the root element ($module)
 322          return $module;
 323      }
 324  }
 325  
 326  /**
 327   * structure step that will generate the section.xml file for the section
 328   * annotating files
 329   */
 330  class backup_section_structure_step extends backup_structure_step {
 331  
 332      protected function define_structure() {
 333  
 334          // Define each element separated
 335  
 336          $section = new backup_nested_element('section', array('id'), array(
 337                  'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible',
 338                  'availabilityjson', 'timemodified'));
 339  
 340          // attach format plugin structure to $section element, only one allowed
 341          $this->add_plugin_structure('format', $section, false);
 342  
 343          // attach local plugin structure to $section element, multiple allowed
 344          $this->add_plugin_structure('local', $section, true);
 345  
 346          // Add nested elements for course_format_options table
 347          $formatoptions = new backup_nested_element('course_format_options', array('id'), array(
 348              'format', 'name', 'value'));
 349          $section->add_child($formatoptions);
 350  
 351          // Define sources.
 352          $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
 353          $formatoptions->set_source_sql('SELECT cfo.id, cfo.format, cfo.name, cfo.value
 354                FROM {course} c
 355                JOIN {course_format_options} cfo
 356                ON cfo.courseid = c.id AND cfo.format = c.format
 357                WHERE c.id = ? AND cfo.sectionid = ?',
 358                  array(backup::VAR_COURSEID, backup::VAR_SECTIONID));
 359  
 360          // Aliases
 361          $section->set_source_alias('section', 'number');
 362          // The 'availability' field needs to be renamed because it clashes with
 363          // the old nested element structure for availability data.
 364          $section->set_source_alias('availability', 'availabilityjson');
 365  
 366          // Set annotations
 367          $section->annotate_files('course', 'section', 'id');
 368  
 369          return $section;
 370      }
 371  }
 372  
 373  /**
 374   * structure step that will generate the course.xml file for the course, including
 375   * course category reference, tags, modules restriction information
 376   * and some annotations (files & groupings)
 377   */
 378  class backup_course_structure_step extends backup_structure_step {
 379  
 380      protected function define_structure() {
 381          global $DB;
 382  
 383          // Define each element separated
 384  
 385          $course = new backup_nested_element('course', array('id', 'contextid'), array(
 386              'shortname', 'fullname', 'idnumber',
 387              'summary', 'summaryformat', 'format', 'showgrades',
 388              'newsitems', 'startdate', 'enddate',
 389              'marker', 'maxbytes', 'legacyfiles', 'showreports',
 390              'visible', 'groupmode', 'groupmodeforce',
 391              'defaultgroupingid', 'lang', 'theme',
 392              'timecreated', 'timemodified',
 393              'requested',
 394              'showactivitydates',
 395              'showcompletionconditions',
 396              'enablecompletion', 'completionstartonenrol', 'completionnotify'));
 397  
 398          $category = new backup_nested_element('category', array('id'), array(
 399              'name', 'description'));
 400  
 401          $tags = new backup_nested_element('tags');
 402  
 403          $tag = new backup_nested_element('tag', array('id'), array(
 404              'name', 'rawname'));
 405  
 406          $customfields = new backup_nested_element('customfields');
 407          $customfield = new backup_nested_element('customfield', array('id'), array(
 408            'shortname', 'type', 'value', 'valueformat'
 409          ));
 410  
 411          $courseformatoptions = new backup_nested_element('courseformatoptions');
 412          $courseformatoption = new backup_nested_element('courseformatoption', [], [
 413              'courseid', 'format', 'sectionid', 'name', 'value'
 414          ]);
 415  
 416          // attach format plugin structure to $course element, only one allowed
 417          $this->add_plugin_structure('format', $course, false);
 418  
 419          // attach theme plugin structure to $course element; multiple themes can
 420          // save course data (in case of user theme, legacy theme, etc)
 421          $this->add_plugin_structure('theme', $course, true);
 422  
 423          // attach general report plugin structure to $course element; multiple
 424          // reports can save course data if required
 425          $this->add_plugin_structure('report', $course, true);
 426  
 427          // attach course report plugin structure to $course element; multiple
 428          // course reports can save course data if required
 429          $this->add_plugin_structure('coursereport', $course, true);
 430  
 431          // attach plagiarism plugin structure to $course element, there can be potentially
 432          // many plagiarism plugins storing information about this course
 433          $this->add_plugin_structure('plagiarism', $course, true);
 434  
 435          // attach local plugin structure to $course element; multiple local plugins
 436          // can save course data if required
 437          $this->add_plugin_structure('local', $course, true);
 438  
 439          // Attach admin tools plugin structure to $course element; multiple plugins
 440          // can save course data if required.
 441          $this->add_plugin_structure('tool', $course, true);
 442  
 443          // Build the tree
 444  
 445          $course->add_child($category);
 446  
 447          $course->add_child($tags);
 448          $tags->add_child($tag);
 449  
 450          $course->add_child($customfields);
 451          $customfields->add_child($customfield);
 452  
 453          $course->add_child($courseformatoptions);
 454          $courseformatoptions->add_child($courseformatoption);
 455  
 456          // Set the sources
 457  
 458          $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
 459          $courserec->contextid = $this->task->get_contextid();
 460  
 461          // Add 'numsections' in order to be able to restore in previous versions of Moodle.
 462          // Even though Moodle does not officially support restore into older verions of Moodle from the
 463          // version where backup was made, without 'numsections' restoring will go very wrong.
 464          if (!property_exists($courserec, 'numsections') && course_get_format($courserec)->uses_sections()) {
 465              $courserec->numsections = course_get_format($courserec)->get_last_section_number();
 466          }
 467  
 468          $course->set_source_array(array($courserec));
 469  
 470          $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));
 471  
 472          $category->set_source_array(array($categoryrec));
 473  
 474          $tag->set_source_sql('SELECT t.id, t.name, t.rawname
 475                                  FROM {tag} t
 476                                  JOIN {tag_instance} ti ON ti.tagid = t.id
 477                                 WHERE ti.itemtype = ?
 478                                   AND ti.itemid = ?', array(
 479                                       backup_helper::is_sqlparam('course'),
 480                                       backup::VAR_PARENTID));
 481  
 482          $courseformatoption->set_source_sql('SELECT id, format, sectionid, name, value
 483                                   FROM {course_format_options}
 484                                   WHERE courseid = ?', [ backup::VAR_PARENTID ]);
 485  
 486          $handler = core_course\customfield\course_handler::create();
 487          $fieldsforbackup = $handler->get_instance_data_for_backup($this->task->get_courseid());
 488          $customfield->set_source_array($fieldsforbackup);
 489  
 490          // Some annotations
 491  
 492          $course->annotate_ids('grouping', 'defaultgroupingid');
 493  
 494          $course->annotate_files('course', 'summary', null);
 495          $course->annotate_files('course', 'overviewfiles', null);
 496  
 497          if ($this->get_setting_value('legacyfiles')) {
 498              $course->annotate_files('course', 'legacy', null);
 499          }
 500  
 501          // Return root element ($course)
 502  
 503          return $course;
 504      }
 505  }
 506  
 507  /**
 508   * structure step that will generate the enrolments.xml file for the given course
 509   */
 510  class backup_enrolments_structure_step extends backup_structure_step {
 511  
 512      /**
 513       * Skip enrolments on the front page.
 514       * @return bool
 515       */
 516      protected function execute_condition() {
 517          return ($this->get_courseid() != SITEID);
 518      }
 519  
 520      protected function define_structure() {
 521          global $DB;
 522  
 523          // To know if we are including users
 524          $users = $this->get_setting_value('users');
 525          $keptroles = $this->task->get_kept_roles();
 526  
 527          // Define each element separated
 528  
 529          $enrolments = new backup_nested_element('enrolments');
 530  
 531          $enrols = new backup_nested_element('enrols');
 532  
 533          $enrol = new backup_nested_element('enrol', array('id'), array(
 534              'enrol', 'status', 'name', 'enrolperiod', 'enrolstartdate',
 535              'enrolenddate', 'expirynotify', 'expirythreshold', 'notifyall',
 536              'password', 'cost', 'currency', 'roleid',
 537              'customint1', 'customint2', 'customint3', 'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
 538              'customchar1', 'customchar2', 'customchar3',
 539              'customdec1', 'customdec2',
 540              'customtext1', 'customtext2', 'customtext3', 'customtext4',
 541              'timecreated', 'timemodified'));
 542  
 543          $userenrolments = new backup_nested_element('user_enrolments');
 544  
 545          $enrolment = new backup_nested_element('enrolment', array('id'), array(
 546              'status', 'userid', 'timestart', 'timeend', 'modifierid',
 547              'timemodified'));
 548  
 549          // Build the tree
 550          $enrolments->add_child($enrols);
 551          $enrols->add_child($enrol);
 552          $enrol->add_child($userenrolments);
 553          $userenrolments->add_child($enrolment);
 554  
 555          // Define sources - the instances are restored using the same sortorder, we do not need to store it in xml and deal with it afterwards.
 556          $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID), 'sortorder ASC');
 557  
 558          // User enrolments only added only if users included.
 559          if (empty($keptroles) && $users) {
 560              $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));
 561              $enrolment->annotate_ids('user', 'userid');
 562          } else if (!empty($keptroles)) {
 563              list($insql, $inparams) = $DB->get_in_or_equal($keptroles);
 564              $params = array(
 565                  backup::VAR_CONTEXTID,
 566                  backup::VAR_PARENTID
 567              );
 568              foreach ($inparams as $inparam) {
 569                  $params[] = backup_helper::is_sqlparam($inparam);
 570              }
 571              $enrolment->set_source_sql(
 572                 "SELECT ue.*
 573                    FROM {user_enrolments} ue
 574              INNER JOIN {role_assignments} ra ON ue.userid = ra.userid
 575                   WHERE ra.contextid = ?
 576                         AND ue.enrolid = ?
 577                         AND ra.roleid $insql",
 578                  $params);
 579              $enrolment->annotate_ids('user', 'userid');
 580          }
 581  
 582          $enrol->annotate_ids('role', 'roleid');
 583  
 584          // Add enrol plugin structure.
 585          $this->add_plugin_structure('enrol', $enrol, true);
 586  
 587          return $enrolments;
 588      }
 589  }
 590  
 591  /**
 592   * structure step that will generate the roles.xml file for the given context, observing
 593   * the role_assignments setting to know if that part needs to be included
 594   */
 595  class backup_roles_structure_step extends backup_structure_step {
 596  
 597      protected function define_structure() {
 598  
 599          // To know if we are including role assignments
 600          $roleassignments = $this->get_setting_value('role_assignments');
 601  
 602          // Define each element separated
 603  
 604          $roles = new backup_nested_element('roles');
 605  
 606          $overrides = new backup_nested_element('role_overrides');
 607  
 608          $override = new backup_nested_element('override', array('id'), array(
 609              'roleid', 'capability', 'permission', 'timemodified',
 610              'modifierid'));
 611  
 612          $assignments = new backup_nested_element('role_assignments');
 613  
 614          $assignment = new backup_nested_element('assignment', array('id'), array(
 615              'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid',
 616              'sortorder'));
 617  
 618          // Build the tree
 619          $roles->add_child($overrides);
 620          $roles->add_child($assignments);
 621  
 622          $overrides->add_child($override);
 623          $assignments->add_child($assignment);
 624  
 625          // Define sources
 626  
 627          $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));
 628  
 629          // Assignments only added if specified
 630          if ($roleassignments) {
 631              $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));
 632          }
 633  
 634          // Define id annotations
 635          $override->annotate_ids('role', 'roleid');
 636  
 637          $assignment->annotate_ids('role', 'roleid');
 638  
 639          $assignment->annotate_ids('user', 'userid');
 640  
 641          //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)
 642  
 643          return $roles;
 644      }
 645  }
 646  
 647  /**
 648   * structure step that will generate the roles.xml containing the
 649   * list of roles used along the whole backup process. Just raw
 650   * list of used roles from role table
 651   */
 652  class backup_final_roles_structure_step extends backup_structure_step {
 653  
 654      protected function define_structure() {
 655  
 656          // Define elements
 657  
 658          $rolesdef = new backup_nested_element('roles_definition');
 659  
 660          $role = new backup_nested_element('role', array('id'), array(
 661              'name', 'shortname', 'nameincourse', 'description',
 662              'sortorder', 'archetype'));
 663  
 664          // Build the tree
 665  
 666          $rolesdef->add_child($role);
 667  
 668          // Define sources
 669  
 670          $role->set_source_sql("SELECT r.*, rn.name AS nameincourse
 671                                   FROM {role} r
 672                                   JOIN {backup_ids_temp} bi ON r.id = bi.itemid
 673                              LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?
 674                                  WHERE bi.backupid = ?
 675                                    AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));
 676  
 677          // Return main element (rolesdef)
 678          return $rolesdef;
 679      }
 680  }
 681  
 682  /**
 683   * structure step that will generate the scales.xml containing the
 684   * list of scales used along the whole backup process.
 685   */
 686  class backup_final_scales_structure_step extends backup_structure_step {
 687  
 688      protected function define_structure() {
 689  
 690          // Define elements
 691  
 692          $scalesdef = new backup_nested_element('scales_definition');
 693  
 694          $scale = new backup_nested_element('scale', array('id'), array(
 695              'courseid', 'userid', 'name', 'scale',
 696              'description', 'descriptionformat', 'timemodified'));
 697  
 698          // Build the tree
 699  
 700          $scalesdef->add_child($scale);
 701  
 702          // Define sources
 703  
 704          $scale->set_source_sql("SELECT s.*
 705                                    FROM {scale} s
 706                                    JOIN {backup_ids_temp} bi ON s.id = bi.itemid
 707                                   WHERE bi.backupid = ?
 708                                     AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));
 709  
 710          // Annotate scale files (they store files in system context, so pass it instead of default one)
 711          $scale->annotate_files('grade', 'scale', 'id', context_system::instance()->id);
 712  
 713          // Return main element (scalesdef)
 714          return $scalesdef;
 715      }
 716  }
 717  
 718  /**
 719   * structure step that will generate the outcomes.xml containing the
 720   * list of outcomes used along the whole backup process.
 721   */
 722  class backup_final_outcomes_structure_step extends backup_structure_step {
 723  
 724      protected function define_structure() {
 725  
 726          // Define elements
 727  
 728          $outcomesdef = new backup_nested_element('outcomes_definition');
 729  
 730          $outcome = new backup_nested_element('outcome', array('id'), array(
 731              'courseid', 'userid', 'shortname', 'fullname',
 732              'scaleid', 'description', 'descriptionformat', 'timecreated',
 733              'timemodified','usermodified'));
 734  
 735          // Build the tree
 736  
 737          $outcomesdef->add_child($outcome);
 738  
 739          // Define sources
 740  
 741          $outcome->set_source_sql("SELECT o.*
 742                                      FROM {grade_outcomes} o
 743                                      JOIN {backup_ids_temp} bi ON o.id = bi.itemid
 744                                     WHERE bi.backupid = ?
 745                                       AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));
 746  
 747          // Annotate outcome files (they store files in system context, so pass it instead of default one)
 748          $outcome->annotate_files('grade', 'outcome', 'id', context_system::instance()->id);
 749  
 750          // Return main element (outcomesdef)
 751          return $outcomesdef;
 752      }
 753  }
 754  
 755  /**
 756   * structure step in charge of constructing the filters.xml file for all the filters found
 757   * in activity
 758   */
 759  class backup_filters_structure_step extends backup_structure_step {
 760  
 761      protected function define_structure() {
 762  
 763          // Define each element separated
 764  
 765          $filters = new backup_nested_element('filters');
 766  
 767          $actives = new backup_nested_element('filter_actives');
 768  
 769          $active = new backup_nested_element('filter_active', null, array('filter', 'active'));
 770  
 771          $configs = new backup_nested_element('filter_configs');
 772  
 773          $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));
 774  
 775          // Build the tree
 776  
 777          $filters->add_child($actives);
 778          $filters->add_child($configs);
 779  
 780          $actives->add_child($active);
 781          $configs->add_child($config);
 782  
 783          // Define sources
 784  
 785          list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());
 786  
 787          $active->set_source_array($activearr);
 788          $config->set_source_array($configarr);
 789  
 790          // Return the root element (filters)
 791          return $filters;
 792      }
 793  }
 794  
 795  /**
 796   * structure step in charge of constructing the comments.xml file for all the comments found
 797   * in a given context
 798   */
 799  class backup_comments_structure_step extends backup_structure_step {
 800  
 801      protected function define_structure() {
 802  
 803          // Define each element separated
 804  
 805          $comments = new backup_nested_element('comments');
 806  
 807          $comment = new backup_nested_element('comment', array('id'), array(
 808              'commentarea', 'itemid', 'content', 'format',
 809              'userid', 'timecreated'));
 810  
 811          // Build the tree
 812  
 813          $comments->add_child($comment);
 814  
 815          // Define sources
 816  
 817          $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));
 818  
 819          // Define id annotations
 820  
 821          $comment->annotate_ids('user', 'userid');
 822  
 823          // Return the root element (comments)
 824          return $comments;
 825      }
 826  }
 827  
 828  /**
 829   * structure step in charge of constructing the badges.xml file for all the badges found
 830   * in a given context
 831   */
 832  class backup_badges_structure_step extends backup_structure_step {
 833  
 834      protected function execute_condition() {
 835          // Check that all activities have been included.
 836          if ($this->task->is_excluding_activities()) {
 837              return false;
 838          }
 839          return true;
 840      }
 841  
 842      protected function define_structure() {
 843          global $CFG;
 844  
 845          require_once($CFG->libdir . '/badgeslib.php');
 846  
 847          // Define each element separated.
 848  
 849          $badges = new backup_nested_element('badges');
 850          $badge = new backup_nested_element('badge', array('id'), array('name', 'description',
 851                  'timecreated', 'timemodified', 'usercreated', 'usermodified', 'issuername',
 852                  'issuerurl', 'issuercontact', 'expiredate', 'expireperiod', 'type', 'courseid',
 853                  'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron',
 854                  'version', 'language', 'imageauthorname', 'imageauthoremail', 'imageauthorurl',
 855                  'imagecaption'));
 856  
 857          $criteria = new backup_nested_element('criteria');
 858          $criterion = new backup_nested_element('criterion', array('id'), array('badgeid',
 859                  'criteriatype', 'method', 'description', 'descriptionformat'));
 860  
 861          $endorsement = new backup_nested_element('endorsement', array('id'), array('badgeid',
 862                  'issuername', 'issuerurl', 'issueremail', 'claimid', 'claimcomment', 'dateissued'));
 863  
 864          $alignments = new backup_nested_element('alignments');
 865          $alignment = new backup_nested_element('alignment', array('id'), array('badgeid',
 866                  'targetname', 'targeturl', 'targetdescription', 'targetframework', 'targetcode'));
 867  
 868          $relatedbadges = new backup_nested_element('relatedbadges');
 869          $relatedbadge = new backup_nested_element('relatedbadge', array('id'), array('badgeid',
 870                  'relatedbadgeid'));
 871  
 872          $parameters = new backup_nested_element('parameters');
 873          $parameter = new backup_nested_element('parameter', array('id'), array('critid',
 874                  'name', 'value', 'criteriatype'));
 875  
 876          $manual_awards = new backup_nested_element('manual_awards');
 877          $manual_award = new backup_nested_element('manual_award', array('id'), array('badgeid',
 878                  'recipientid', 'issuerid', 'issuerrole', 'datemet'));
 879  
 880          // Build the tree.
 881  
 882          $badges->add_child($badge);
 883          $badge->add_child($criteria);
 884          $criteria->add_child($criterion);
 885          $criterion->add_child($parameters);
 886          $parameters->add_child($parameter);
 887          $badge->add_child($endorsement);
 888          $badge->add_child($alignments);
 889          $alignments->add_child($alignment);
 890          $badge->add_child($relatedbadges);
 891          $relatedbadges->add_child($relatedbadge);
 892          $badge->add_child($manual_awards);
 893          $manual_awards->add_child($manual_award);
 894  
 895          // Define sources.
 896  
 897          $parametersql = '
 898                  SELECT *
 899                  FROM {badge}
 900                  WHERE courseid = :courseid
 901                  AND status != ' . BADGE_STATUS_ARCHIVED;
 902          $parameterparams = [
 903              'courseid' => backup::VAR_COURSEID
 904          ];
 905          $badge->set_source_sql($parametersql, $parameterparams);
 906          $criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID));
 907          $endorsement->set_source_table('badge_endorsement', array('badgeid' => backup::VAR_PARENTID));
 908  
 909          $alignment->set_source_table('badge_alignment', array('badgeid' => backup::VAR_PARENTID));
 910          $relatedbadge->set_source_table('badge_related', array('badgeid' => backup::VAR_PARENTID));
 911  
 912          $parametersql = 'SELECT cp.*, c.criteriatype
 913                               FROM {badge_criteria_param} cp JOIN {badge_criteria} c
 914                                   ON cp.critid = c.id
 915                               WHERE critid = :critid';
 916          $parameterparams = array('critid' => backup::VAR_PARENTID);
 917          $parameter->set_source_sql($parametersql, $parameterparams);
 918  
 919          $manual_award->set_source_table('badge_manual_award', array('badgeid' => backup::VAR_PARENTID));
 920  
 921          // Define id annotations.
 922  
 923          $badge->annotate_ids('user', 'usercreated');
 924          $badge->annotate_ids('user', 'usermodified');
 925          $criterion->annotate_ids('badge', 'badgeid');
 926          $parameter->annotate_ids('criterion', 'critid');
 927          $endorsement->annotate_ids('badge', 'badgeid');
 928          $alignment->annotate_ids('badge', 'badgeid');
 929          $relatedbadge->annotate_ids('badge', 'badgeid');
 930          $relatedbadge->annotate_ids('badge', 'relatedbadgeid');
 931          $badge->annotate_files('badges', 'badgeimage', 'id');
 932          $manual_award->annotate_ids('badge', 'badgeid');
 933          $manual_award->annotate_ids('user', 'recipientid');
 934          $manual_award->annotate_ids('user', 'issuerid');
 935          $manual_award->annotate_ids('role', 'issuerrole');
 936  
 937          // Return the root element ($badges).
 938          return $badges;
 939      }
 940  }
 941  
 942  /**
 943   * structure step in charge of constructing the calender.xml file for all the events found
 944   * in a given context
 945   */
 946  class backup_calendarevents_structure_step extends backup_structure_step {
 947  
 948      protected function define_structure() {
 949  
 950          // Define each element separated
 951  
 952          $events = new backup_nested_element('events');
 953  
 954          $event = new backup_nested_element('event', array('id'), array(
 955                  'name', 'description', 'format', 'courseid', 'groupid', 'userid',
 956                  'repeatid', 'modulename', 'instance', 'type', 'eventtype', 'timestart',
 957                  'timeduration', 'timesort', 'visible', 'uuid', 'sequence', 'timemodified',
 958                  'priority', 'location'));
 959  
 960          // Build the tree
 961          $events->add_child($event);
 962  
 963          // Define sources
 964          if ($this->name == 'course_calendar') {
 965              $calendar_items_sql ="SELECT * FROM {event}
 966                          WHERE courseid = :courseid
 967                          AND (eventtype = 'course' OR eventtype = 'group')";
 968              $calendar_items_params = array('courseid'=>backup::VAR_COURSEID);
 969              $event->set_source_sql($calendar_items_sql, $calendar_items_params);
 970          } else if ($this->name == 'activity_calendar') {
 971              // We don't backup action events.
 972              $params = array('instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME,
 973                  'type' => array('sqlparam' => CALENDAR_EVENT_TYPE_ACTION));
 974              // If we don't want to include the userinfo in the backup then setting the courseid
 975              // will filter out all of the user override events (which have a course id of zero).
 976              $coursewhere = "";
 977              if (!$this->get_setting_value('userinfo')) {
 978                  $params['courseid'] = backup::VAR_COURSEID;
 979                  $coursewhere = " AND courseid = :courseid";
 980              }
 981              $calendarsql = "SELECT * FROM {event}
 982                               WHERE instance = :instance
 983                                 AND type <> :type
 984                                 AND modulename = :modulename";
 985              $calendarsql = $calendarsql . $coursewhere;
 986              $event->set_source_sql($calendarsql, $params);
 987          } else {
 988              $event->set_source_table('event', array('courseid' => backup::VAR_COURSEID, 'instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME));
 989          }
 990  
 991          // Define id annotations
 992  
 993          $event->annotate_ids('user', 'userid');
 994          $event->annotate_ids('group', 'groupid');
 995          $event->annotate_files('calendar', 'event_description', 'id');
 996  
 997          // Return the root element (events)
 998          return $events;
 999      }
1000  }
1001  
1002  /**
1003   * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course
1004   * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step
1005   */
1006  class backup_gradebook_structure_step extends backup_structure_step {
1007  
1008      /**
1009       * We need to decide conditionally, based on dynamic information
1010       * about the execution of this step. Only will be executed if all
1011       * the module gradeitems have been already included in backup
1012       */
1013      protected function execute_condition() {
1014          $courseid = $this->get_courseid();
1015          if ($courseid == SITEID) {
1016              return false;
1017          }
1018  
1019          return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
1020      }
1021  
1022      protected function define_structure() {
1023          global $CFG, $DB;
1024  
1025          // are we including user info?
1026          $userinfo = $this->get_setting_value('users');
1027  
1028          $gradebook = new backup_nested_element('gradebook');
1029  
1030          //grade_letters are done in backup_activity_grades_structure_step()
1031  
1032          //calculated grade items
1033          $grade_items = new backup_nested_element('grade_items');
1034          $grade_item = new backup_nested_element('grade_item', array('id'), array(
1035              'categoryid', 'itemname', 'itemtype', 'itemmodule',
1036              'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
1037              'calculation', 'gradetype', 'grademax', 'grademin',
1038              'scaleid', 'outcomeid', 'gradepass', 'multfactor',
1039              'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
1040              'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
1041              'needsupdate', 'timecreated', 'timemodified'));
1042  
1043          $this->add_plugin_structure('local', $grade_item, true);
1044  
1045          $grade_grades = new backup_nested_element('grade_grades');
1046          $grade_grade = new backup_nested_element('grade_grade', array('id'), array(
1047              'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
1048              'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
1049              'locked', 'locktime', 'exported', 'overridden',
1050              'excluded', 'feedback', 'feedbackformat', 'information',
1051              'informationformat', 'timecreated', 'timemodified',
1052              'aggregationstatus', 'aggregationweight'));
1053  
1054          //grade_categories
1055          $grade_categories = new backup_nested_element('grade_categories');
1056          $grade_category   = new backup_nested_element('grade_category', array('id'), array(
1057                  //'courseid',
1058                  'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
1059                  'droplow', 'aggregateonlygraded', 'aggregateoutcomes',
1060                  'timecreated', 'timemodified', 'hidden'));
1061  
1062          $letters = new backup_nested_element('grade_letters');
1063          $letter = new backup_nested_element('grade_letter', 'id', array(
1064              'lowerboundary', 'letter'));
1065  
1066          $grade_settings = new backup_nested_element('grade_settings');
1067          $grade_setting = new backup_nested_element('grade_setting', 'id', array(
1068              'name', 'value'));
1069  
1070          $gradebook_attributes = new backup_nested_element('attributes', null, array('calculations_freeze'));
1071  
1072          // Build the tree
1073          $gradebook->add_child($gradebook_attributes);
1074  
1075          $gradebook->add_child($grade_categories);
1076          $grade_categories->add_child($grade_category);
1077  
1078          $gradebook->add_child($grade_items);
1079          $grade_items->add_child($grade_item);
1080          $grade_item->add_child($grade_grades);
1081          $grade_grades->add_child($grade_grade);
1082  
1083          $gradebook->add_child($letters);
1084          $letters->add_child($letter);
1085  
1086          $gradebook->add_child($grade_settings);
1087          $grade_settings->add_child($grade_setting);
1088  
1089          // Define sources
1090  
1091          // Add attribute with gradebook calculation freeze date if needed.
1092          $attributes = new stdClass();
1093          $gradebookcalculationfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid());
1094          if ($gradebookcalculationfreeze) {
1095              $attributes->calculations_freeze = $gradebookcalculationfreeze;
1096          }
1097          $gradebook_attributes->set_source_array([$attributes]);
1098  
1099          //Include manual, category and the course grade item
1100          $grade_items_sql ="SELECT * FROM {grade_items}
1101                             WHERE courseid = :courseid
1102                             AND (itemtype='manual' OR itemtype='course' OR itemtype='category')";
1103          $grade_items_params = array('courseid'=>backup::VAR_COURSEID);
1104          $grade_item->set_source_sql($grade_items_sql, $grade_items_params);
1105  
1106          if ($userinfo) {
1107              $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
1108          }
1109  
1110          $grade_category_sql = "SELECT gc.*, gi.sortorder
1111                                 FROM {grade_categories} gc
1112                                 JOIN {grade_items} gi ON (gi.iteminstance = gc.id)
1113                                 WHERE gc.courseid = :courseid
1114                                 AND (gi.itemtype='course' OR gi.itemtype='category')
1115                                 ORDER BY gc.parent ASC";//need parent categories before their children
1116          $grade_category_params = array('courseid'=>backup::VAR_COURSEID);
1117          $grade_category->set_source_sql($grade_category_sql, $grade_category_params);
1118  
1119          $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
1120  
1121          // Set the grade settings source, forcing the inclusion of minmaxtouse if not present.
1122          $settings = array();
1123          $rs = $DB->get_recordset('grade_settings', array('courseid' => $this->get_courseid()));
1124          foreach ($rs as $record) {
1125              $settings[$record->name] = $record;
1126          }
1127          $rs->close();
1128          if (!isset($settings['minmaxtouse'])) {
1129              $settings['minmaxtouse'] = (object) array('name' => 'minmaxtouse', 'value' => $CFG->grade_minmaxtouse);
1130          }
1131          $grade_setting->set_source_array($settings);
1132  
1133  
1134          // Annotations (both as final as far as they are going to be exported in next steps)
1135          $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
1136          $grade_item->annotate_ids('outcomefinal', 'outcomeid');
1137  
1138          //just in case there are any users not already annotated by the activities
1139          $grade_grade->annotate_ids('userfinal', 'userid');
1140  
1141          // Return the root element
1142          return $gradebook;
1143      }
1144  }
1145  
1146  /**
1147   * Step in charge of constructing the grade_history.xml file containing the grade histories.
1148   */
1149  class backup_grade_history_structure_step extends backup_structure_step {
1150  
1151      /**
1152       * Limit the execution.
1153       *
1154       * This applies the same logic than the one applied to {@link backup_gradebook_structure_step},
1155       * because we do not want to save the history of items which are not backed up. At least for now.
1156       */
1157      protected function execute_condition() {
1158          $courseid = $this->get_courseid();
1159          if ($courseid == SITEID) {
1160              return false;
1161          }
1162  
1163          return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
1164      }
1165  
1166      protected function define_structure() {
1167  
1168          // Settings to use.
1169          $userinfo = $this->get_setting_value('users');
1170          $history = $this->get_setting_value('grade_histories');
1171  
1172          // Create the nested elements.
1173          $bookhistory = new backup_nested_element('grade_history');
1174          $grades = new backup_nested_element('grade_grades');
1175          $grade = new backup_nested_element('grade_grade', array('id'), array(
1176              'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
1177              'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
1178              'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
1179              'excluded', 'feedback', 'feedbackformat', 'information',
1180              'informationformat', 'timemodified'));
1181  
1182          // Build the tree.
1183          $bookhistory->add_child($grades);
1184          $grades->add_child($grade);
1185  
1186          // This only happens if we are including user info and history.
1187          if ($userinfo && $history) {
1188              // Only keep the history of grades related to items which have been backed up, The query is
1189              // similar (but not identical) to the one used in backup_gradebook_structure_step::define_structure().
1190              $gradesql = "SELECT ggh.*
1191                             FROM {grade_grades_history} ggh
1192                             JOIN {grade_items} gi ON ggh.itemid = gi.id
1193                            WHERE gi.courseid = :courseid
1194                              AND (gi.itemtype = 'manual' OR gi.itemtype = 'course' OR gi.itemtype = 'category')";
1195              $grade->set_source_sql($gradesql, array('courseid' => backup::VAR_COURSEID));
1196          }
1197  
1198          // Annotations. (Final annotations as this step is part of the final task).
1199          $grade->annotate_ids('scalefinal', 'rawscaleid');
1200          $grade->annotate_ids('userfinal', 'loggeduser');
1201          $grade->annotate_ids('userfinal', 'userid');
1202          $grade->annotate_ids('userfinal', 'usermodified');
1203  
1204          // Return the root element.
1205          return $bookhistory;
1206      }
1207  
1208  }
1209  
1210  /**
1211   * structure step in charge if constructing the completion.xml file for all the users completion
1212   * information in a given activity
1213   */
1214  class backup_userscompletion_structure_step extends backup_structure_step {
1215  
1216      /**
1217       * Skip completion on the front page.
1218       * @return bool
1219       */
1220      protected function execute_condition() {
1221          return ($this->get_courseid() != SITEID);
1222      }
1223  
1224      protected function define_structure() {
1225  
1226          // Define each element separated
1227  
1228          $completions = new backup_nested_element('completions');
1229  
1230          $completion = new backup_nested_element('completion', array('id'), array(
1231              'userid', 'completionstate', 'viewed', 'timemodified'));
1232  
1233          // Build the tree
1234  
1235          $completions->add_child($completion);
1236  
1237          // Define sources
1238  
1239          $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
1240  
1241          // Define id annotations
1242  
1243          $completion->annotate_ids('user', 'userid');
1244  
1245          // Return the root element (completions)
1246          return $completions;
1247      }
1248  }
1249  
1250  /**
1251   * structure step in charge of constructing the main groups.xml file for all the groups and
1252   * groupings information already annotated
1253   */
1254  class backup_groups_structure_step extends backup_structure_step {
1255  
1256      protected function define_structure() {
1257  
1258          // To know if we are including users.
1259          $userinfo = $this->get_setting_value('users');
1260          // To know if we are including groups and groupings.
1261          $groupinfo = $this->get_setting_value('groups');
1262  
1263          // Define each element separated
1264  
1265          $groups = new backup_nested_element('groups');
1266  
1267          $group = new backup_nested_element('group', array('id'), array(
1268              'name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey',
1269              'picture', 'timecreated', 'timemodified'));
1270  
1271          $members = new backup_nested_element('group_members');
1272  
1273          $member = new backup_nested_element('group_member', array('id'), array(
1274              'userid', 'timeadded', 'component', 'itemid'));
1275  
1276          $groupings = new backup_nested_element('groupings');
1277  
1278          $grouping = new backup_nested_element('grouping', 'id', array(
1279              'name', 'idnumber', 'description', 'descriptionformat', 'configdata',
1280              'timecreated', 'timemodified'));
1281  
1282          $groupinggroups = new backup_nested_element('grouping_groups');
1283  
1284          $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
1285              'groupid', 'timeadded'));
1286  
1287          // Build the tree
1288  
1289          $groups->add_child($group);
1290          $groups->add_child($groupings);
1291  
1292          $group->add_child($members);
1293          $members->add_child($member);
1294  
1295          $groupings->add_child($grouping);
1296          $grouping->add_child($groupinggroups);
1297          $groupinggroups->add_child($groupinggroup);
1298  
1299          // Define sources
1300  
1301          // This only happens if we are including groups/groupings.
1302          if ($groupinfo) {
1303              $group->set_source_sql("
1304                  SELECT g.*
1305                    FROM {groups} g
1306                    JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1307                   WHERE bi.backupid = ?
1308                     AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
1309  
1310              $grouping->set_source_sql("
1311                  SELECT g.*
1312                    FROM {groupings} g
1313                    JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1314                   WHERE bi.backupid = ?
1315                     AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
1316              $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
1317  
1318              // This only happens if we are including users.
1319              if ($userinfo) {
1320                  $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
1321              }
1322          }
1323  
1324          // Define id annotations (as final)
1325  
1326          $member->annotate_ids('userfinal', 'userid');
1327  
1328          // Define file annotations
1329  
1330          $group->annotate_files('group', 'description', 'id');
1331          $group->annotate_files('group', 'icon', 'id');
1332          $grouping->annotate_files('grouping', 'description', 'id');
1333  
1334          // Return the root element (groups)
1335          return $groups;
1336      }
1337  }
1338  
1339  /**
1340   * structure step in charge of constructing the main users.xml file for all the users already
1341   * annotated (final). Includes custom profile fields, preferences, tags, role assignments and
1342   * overrides.
1343   */
1344  class backup_users_structure_step extends backup_structure_step {
1345  
1346      protected function define_structure() {
1347          global $CFG;
1348  
1349          // To know if we are anonymizing users
1350          $anonymize = $this->get_setting_value('anonymize');
1351          // To know if we are including role assignments
1352          $roleassignments = $this->get_setting_value('role_assignments');
1353  
1354          // Define each element separate.
1355  
1356          $users = new backup_nested_element('users');
1357  
1358          // Create the array of user fields by hand, as far as we have various bits to control
1359          // anonymize option, password backup, mnethostid...
1360  
1361          // First, the fields not needing anonymization nor special handling
1362          $normalfields = array(
1363              'confirmed', 'policyagreed', 'deleted',
1364              'lang', 'theme', 'timezone', 'firstaccess',
1365              'lastaccess', 'lastlogin', 'currentlogin',
1366              'mailformat', 'maildigest', 'maildisplay',
1367              'autosubscribe', 'trackforums', 'timecreated',
1368              'timemodified', 'trustbitmask');
1369  
1370          // Then, the fields potentially needing anonymization
1371          $anonfields = array(
1372              'username', 'idnumber', 'email', 'phone1',
1373              'phone2', 'institution', 'department', 'address',
1374              'city', 'country', 'lastip', 'picture',
1375              'description', 'descriptionformat', 'imagealt', 'auth');
1376          $anonfields = array_merge($anonfields, \core_user\fields::get_name_fields());
1377  
1378          // Add anonymized fields to $userfields with custom final element
1379          foreach ($anonfields as $field) {
1380              if ($anonymize) {
1381                  $userfields[] = new anonymizer_final_element($field);
1382              } else {
1383                  $userfields[] = $field; // No anonymization, normally added
1384              }
1385          }
1386  
1387          // mnethosturl requires special handling (custom final element)
1388          $userfields[] = new mnethosturl_final_element('mnethosturl');
1389  
1390          // password added conditionally
1391          if (!empty($CFG->includeuserpasswordsinbackup)) {
1392              $userfields[] = 'password';
1393          }
1394  
1395          // Merge all the fields
1396          $userfields = array_merge($userfields, $normalfields);
1397  
1398          $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1399  
1400          $customfields = new backup_nested_element('custom_fields');
1401  
1402          $customfield = new backup_nested_element('custom_field', array('id'), array(
1403              'field_name', 'field_type', 'field_data'));
1404  
1405          $tags = new backup_nested_element('tags');
1406  
1407          $tag = new backup_nested_element('tag', array('id'), array(
1408              'name', 'rawname'));
1409  
1410          $preferences = new backup_nested_element('preferences');
1411  
1412          $preference = new backup_nested_element('preference', array('id'), array(
1413              'name', 'value'));
1414  
1415          $roles = new backup_nested_element('roles');
1416  
1417          $overrides = new backup_nested_element('role_overrides');
1418  
1419          $override = new backup_nested_element('override', array('id'), array(
1420              'roleid', 'capability', 'permission', 'timemodified',
1421              'modifierid'));
1422  
1423          $assignments = new backup_nested_element('role_assignments');
1424  
1425          $assignment = new backup_nested_element('assignment', array('id'), array(
1426              'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
1427              'sortorder'));
1428  
1429          // Build the tree
1430  
1431          $users->add_child($user);
1432  
1433          $user->add_child($customfields);
1434          $customfields->add_child($customfield);
1435  
1436          $user->add_child($tags);
1437          $tags->add_child($tag);
1438  
1439          $user->add_child($preferences);
1440          $preferences->add_child($preference);
1441  
1442          $user->add_child($roles);
1443  
1444          $roles->add_child($overrides);
1445          $roles->add_child($assignments);
1446  
1447          $overrides->add_child($override);
1448          $assignments->add_child($assignment);
1449  
1450          // Define sources
1451  
1452          $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
1453                                   FROM {user} u
1454                                   JOIN {backup_ids_temp} bi ON bi.itemid = u.id
1455                              LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . '
1456                              LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
1457                                  WHERE bi.backupid = ?
1458                                    AND bi.itemname = ?', array(
1459                                        backup_helper::is_sqlparam($this->get_backupid()),
1460                                        backup_helper::is_sqlparam('userfinal')));
1461  
1462          // All the rest on information is only added if we arent
1463          // in an anonymized backup
1464          if (!$anonymize) {
1465              $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data
1466                                              FROM {user_info_field} f
1467                                              JOIN {user_info_data} d ON d.fieldid = f.id
1468                                             WHERE d.userid = ?', array(backup::VAR_PARENTID));
1469  
1470              $customfield->set_source_alias('shortname', 'field_name');
1471              $customfield->set_source_alias('datatype',  'field_type');
1472              $customfield->set_source_alias('data',      'field_data');
1473  
1474              $tag->set_source_sql('SELECT t.id, t.name, t.rawname
1475                                      FROM {tag} t
1476                                      JOIN {tag_instance} ti ON ti.tagid = t.id
1477                                     WHERE ti.itemtype = ?
1478                                       AND ti.itemid = ?', array(
1479                                           backup_helper::is_sqlparam('user'),
1480                                           backup::VAR_PARENTID));
1481  
1482              $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1483  
1484              $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1485  
1486              // Assignments only added if specified
1487              if ($roleassignments) {
1488                  $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1489              }
1490  
1491              // Define id annotations (as final)
1492              $override->annotate_ids('rolefinal', 'roleid');
1493          }
1494          // Return root element (users)
1495          return $users;
1496      }
1497  }
1498  
1499  /**
1500   * structure step in charge of constructing the block.xml file for one
1501   * given block (instance and positions). If the block has custom DB structure
1502   * that will go to a separate file (different step defined in block class)
1503   */
1504  class backup_block_instance_structure_step extends backup_structure_step {
1505  
1506      protected function define_structure() {
1507          global $DB;
1508  
1509          // Define each element separated
1510  
1511          $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
1512                  'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1513                  'subpagepattern', 'defaultregion', 'defaultweight', 'configdata',
1514                  'timecreated', 'timemodified'));
1515  
1516          $positions = new backup_nested_element('block_positions');
1517  
1518          $position = new backup_nested_element('block_position', array('id'), array(
1519              'contextid', 'pagetype', 'subpage', 'visible',
1520              'region', 'weight'));
1521  
1522          // Build the tree
1523  
1524          $block->add_child($positions);
1525          $positions->add_child($position);
1526  
1527          // Transform configdata information if needed (process links and friends)
1528          $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
1529          if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
1530              $configdata = array_filter(
1531                  (array) unserialize_object(base64_decode($blockrec->configdata)),
1532                  static function($value): bool {
1533                      return !($value instanceof __PHP_Incomplete_Class);
1534                  }
1535              );
1536  
1537              foreach ($configdata as $attribute => $value) {
1538                  if (in_array($attribute, $attrstotransform)) {
1539                      $configdata[$attribute] = $this->contenttransformer->process($value);
1540                  }
1541              }
1542              $blockrec->configdata = base64_encode(serialize((object)$configdata));
1543          }
1544          $blockrec->contextid = $this->task->get_contextid();
1545          // Get the version of the block
1546          $blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version');
1547  
1548          // Define sources
1549  
1550          $block->set_source_array(array($blockrec));
1551  
1552          $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
1553  
1554          // File anotations (for fileareas specified on each block)
1555          foreach ($this->task->get_fileareas() as $filearea) {
1556              $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);
1557          }
1558  
1559          // Return the root element (block)
1560          return $block;
1561      }
1562  }
1563  
1564  /**
1565   * structure step in charge of constructing the logs.xml file for all the log records found
1566   * in course. Note that we are sending to backup ALL the log records having cmid = 0. That
1567   * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do
1568   * that just in case they become restored some day in the future
1569   */
1570  class backup_course_logs_structure_step extends backup_structure_step {
1571  
1572      protected function define_structure() {
1573  
1574          // Define each element separated
1575  
1576          $logs = new backup_nested_element('logs');
1577  
1578          $log = new backup_nested_element('log', array('id'), array(
1579              'time', 'userid', 'ip', 'module',
1580              'action', 'url', 'info'));
1581  
1582          // Build the tree
1583  
1584          $logs->add_child($log);
1585  
1586          // Define sources (all the records belonging to the course, having cmid = 0)
1587  
1588          $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0)));
1589  
1590          // Annotations
1591          // NOTE: We don't annotate users from logs as far as they MUST be
1592          //       always annotated by the course (enrol, ras... whatever)
1593  
1594          // Return the root element (logs)
1595  
1596          return $logs;
1597      }
1598  }
1599  
1600  /**
1601   * structure step in charge of constructing the logs.xml file for all the log records found
1602   * in activity
1603   */
1604  class backup_activity_logs_structure_step extends backup_structure_step {
1605  
1606      protected function define_structure() {
1607  
1608          // Define each element separated
1609  
1610          $logs = new backup_nested_element('logs');
1611  
1612          $log = new backup_nested_element('log', array('id'), array(
1613              'time', 'userid', 'ip', 'module',
1614              'action', 'url', 'info'));
1615  
1616          // Build the tree
1617  
1618          $logs->add_child($log);
1619  
1620          // Define sources
1621  
1622          $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1623  
1624          // Annotations
1625          // NOTE: We don't annotate users from logs as far as they MUST be
1626          //       always annotated by the activity (true participants).
1627  
1628          // Return the root element (logs)
1629  
1630          return $logs;
1631      }
1632  }
1633  
1634  /**
1635   * Structure step in charge of constructing the logstores.xml file for the course logs.
1636   *
1637   * This backup step will backup the logs for all the enabled logstore subplugins supporting
1638   * it, for logs belonging to the course level.
1639   */
1640  class backup_course_logstores_structure_step extends backup_structure_step {
1641  
1642      protected function define_structure() {
1643  
1644          // Define the structure of logstores container.
1645          $logstores = new backup_nested_element('logstores');
1646          $logstore = new backup_nested_element('logstore');
1647          $logstores->add_child($logstore);
1648  
1649          // Add the tool_log logstore subplugins information to the logstore element.
1650          $this->add_subplugin_structure('logstore', $logstore, true, 'tool', 'log');
1651  
1652          return $logstores;
1653      }
1654  }
1655  
1656  /**
1657   * Structure step in charge of constructing the loglastaccess.xml file for the course logs.
1658   *
1659   * This backup step will backup the logs of the user_lastaccess table.
1660   */
1661  class backup_course_loglastaccess_structure_step extends backup_structure_step {
1662  
1663      /**
1664       *  This function creates the structures for the loglastaccess.xml file.
1665       *  Expected structure would look like this.
1666       *  <loglastaccesses>
1667       *      <loglastaccess id=2>
1668       *          <userid>5</userid>
1669       *          <timeaccess>1616887341</timeaccess>
1670       *      </loglastaccess>
1671       *  </loglastaccesses>
1672       *
1673       * @return backup_nested_element
1674       */
1675      protected function define_structure() {
1676  
1677          // To know if we are including userinfo.
1678          $userinfo = $this->get_setting_value('users');
1679  
1680          // Define the structure of logstores container.
1681          $lastaccesses = new backup_nested_element('lastaccesses');
1682          $lastaccess = new backup_nested_element('lastaccess', array('id'), array('userid', 'timeaccess'));
1683  
1684          // Define build tree.
1685          $lastaccesses->add_child($lastaccess);
1686  
1687          // This element should only happen if we are including user info.
1688          if ($userinfo) {
1689              // Define sources.
1690              $lastaccess->set_source_sql('
1691                  SELECT id, userid, timeaccess
1692                    FROM {user_lastaccess}
1693                   WHERE courseid = ?',
1694                  array(backup::VAR_COURSEID));
1695  
1696              // Define userid annotation to user.
1697              $lastaccess->annotate_ids('user', 'userid');
1698          }
1699  
1700          // Return the root element (lastaccessess).
1701          return $lastaccesses;
1702      }
1703  }
1704  
1705  /**
1706   * Structure step in charge of constructing the logstores.xml file for the activity logs.
1707   *
1708   * Note: Activity structure is completely equivalent to the course one, so just extend it.
1709   */
1710  class backup_activity_logstores_structure_step extends backup_course_logstores_structure_step {
1711  }
1712  
1713  /**
1714   * Course competencies backup structure step.
1715   */
1716  class backup_course_competencies_structure_step extends backup_structure_step {
1717  
1718      protected function define_structure() {
1719          $userinfo = $this->get_setting_value('users');
1720  
1721          $wrapper = new backup_nested_element('course_competencies');
1722  
1723          $settings = new backup_nested_element('settings', array('id'), array('pushratingstouserplans'));
1724          $wrapper->add_child($settings);
1725  
1726          $sql = 'SELECT s.pushratingstouserplans
1727                    FROM {' . \core_competency\course_competency_settings::TABLE . '} s
1728                   WHERE s.courseid = :courseid';
1729          $settings->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1730  
1731          $competencies = new backup_nested_element('competencies');
1732          $wrapper->add_child($competencies);
1733  
1734          $competency = new backup_nested_element('competency', null, array('id', 'idnumber', 'ruleoutcome',
1735              'sortorder', 'frameworkid', 'frameworkidnumber'));
1736          $competencies->add_child($competency);
1737  
1738          $sql = 'SELECT c.id, c.idnumber, cc.ruleoutcome, cc.sortorder, f.id AS frameworkid, f.idnumber AS frameworkidnumber
1739                    FROM {' . \core_competency\course_competency::TABLE . '} cc
1740                    JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyid
1741                    JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid
1742                   WHERE cc.courseid = :courseid
1743                ORDER BY cc.sortorder';
1744          $competency->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1745  
1746          $usercomps = new backup_nested_element('user_competencies');
1747          $wrapper->add_child($usercomps);
1748          if ($userinfo) {
1749              $usercomp = new backup_nested_element('user_competency', null, array('userid', 'competencyid',
1750                  'proficiency', 'grade'));
1751              $usercomps->add_child($usercomp);
1752  
1753              $sql = 'SELECT ucc.userid, ucc.competencyid, ucc.proficiency, ucc.grade
1754                        FROM {' . \core_competency\user_competency_course::TABLE . '} ucc
1755                       WHERE ucc.courseid = :courseid
1756                         AND ucc.grade IS NOT NULL';
1757              $usercomp->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1758              $usercomp->annotate_ids('user', 'userid');
1759          }
1760  
1761          return $wrapper;
1762      }
1763  
1764      /**
1765       * Execute conditions.
1766       *
1767       * @return bool
1768       */
1769      protected function execute_condition() {
1770  
1771          // Do not execute if competencies are not included.
1772          if (!$this->get_setting_value('competencies')) {
1773              return false;
1774          }
1775  
1776          return true;
1777      }
1778  }
1779  
1780  /**
1781   * Activity competencies backup structure step.
1782   */
1783  class backup_activity_competencies_structure_step extends backup_structure_step {
1784  
1785      protected function define_structure() {
1786          $wrapper = new backup_nested_element('course_module_competencies');
1787  
1788          $competencies = new backup_nested_element('competencies');
1789          $wrapper->add_child($competencies);
1790  
1791          $competency = new backup_nested_element('competency', null, array('idnumber', 'ruleoutcome',
1792              'sortorder', 'frameworkidnumber'));
1793          $competencies->add_child($competency);
1794  
1795          $sql = 'SELECT c.idnumber, cmc.ruleoutcome, cmc.sortorder, f.idnumber AS frameworkidnumber
1796                    FROM {' . \core_competency\course_module_competency::TABLE . '} cmc
1797                    JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cmc.competencyid
1798                    JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid
1799                   WHERE cmc.cmid = :coursemoduleid
1800                ORDER BY cmc.sortorder';
1801          $competency->set_source_sql($sql, array('coursemoduleid' => backup::VAR_MODID));
1802  
1803          return $wrapper;
1804      }
1805  
1806      /**
1807       * Execute conditions.
1808       *
1809       * @return bool
1810       */
1811      protected function execute_condition() {
1812  
1813          // Do not execute if competencies are not included.
1814          if (!$this->get_setting_value('competencies')) {
1815              return false;
1816          }
1817  
1818          return true;
1819      }
1820  }
1821  
1822  /**
1823   * structure in charge of constructing the inforef.xml file for all the items we want
1824   * to have referenced there (users, roles, files...)
1825   */
1826  class backup_inforef_structure_step extends backup_structure_step {
1827  
1828      protected function define_structure() {
1829  
1830          // Items we want to include in the inforef file.
1831          $items = backup_helper::get_inforef_itemnames();
1832  
1833          // Build the tree
1834  
1835          $inforef = new backup_nested_element('inforef');
1836  
1837          // For each item, conditionally, if there are already records, build element
1838          foreach ($items as $itemname) {
1839              if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {
1840                  $elementroot = new backup_nested_element($itemname . 'ref');
1841                  $element = new backup_nested_element($itemname, array(), array('id'));
1842                  $inforef->add_child($elementroot);
1843                  $elementroot->add_child($element);
1844                  $element->set_source_sql("
1845                      SELECT itemid AS id
1846                       FROM {backup_ids_temp}
1847                      WHERE backupid = ?
1848                        AND itemname = ?",
1849                     array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
1850              }
1851          }
1852  
1853          // We don't annotate anything there, but rely in the next step
1854          // (move_inforef_annotations_to_final) that will change all the
1855          // already saved 'inforref' entries to their 'final' annotations.
1856          return $inforef;
1857      }
1858  }
1859  
1860  /**
1861   * This step will get all the annotations already processed to inforef.xml file and
1862   * transform them into 'final' annotations.
1863   */
1864  class move_inforef_annotations_to_final extends backup_execution_step {
1865  
1866      protected function define_execution() {
1867  
1868          // Items we want to include in the inforef file
1869          $items = backup_helper::get_inforef_itemnames();
1870          $progress = $this->task->get_progress();
1871          $progress->start_progress($this->get_name(), count($items));
1872          $done = 1;
1873          foreach ($items as $itemname) {
1874              // Delegate to dbops
1875              backup_structure_dbops::move_annotations_to_final($this->get_backupid(),
1876                      $itemname, $progress);
1877              $progress->progress($done++);
1878          }
1879          $progress->end_progress();
1880      }
1881  }
1882  
1883  /**
1884   * structure in charge of constructing the files.xml file with all the
1885   * annotated (final) files along the process. At, the same time, and
1886   * using one specialised nested_element, will copy them form moodle storage
1887   * to backup storage
1888   */
1889  class backup_final_files_structure_step extends backup_structure_step {
1890  
1891      protected function define_structure() {
1892  
1893          // Define elements
1894  
1895          $files = new backup_nested_element('files');
1896  
1897          $file = new file_nested_element('file', array('id'), array(
1898              'contenthash', 'contextid', 'component', 'filearea', 'itemid',
1899              'filepath', 'filename', 'userid', 'filesize',
1900              'mimetype', 'status', 'timecreated', 'timemodified',
1901              'source', 'author', 'license', 'sortorder',
1902              'repositorytype', 'repositoryid', 'reference'));
1903  
1904          // Build the tree
1905  
1906          $files->add_child($file);
1907  
1908          // Define sources
1909  
1910          $file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference
1911                                   FROM {files} f
1912                                        LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid
1913                                        LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid
1914                                        LEFT JOIN {repository} r ON r.id = ri.typeid
1915                                        JOIN {backup_ids_temp} bi ON f.id = bi.itemid
1916                                  WHERE bi.backupid = ?
1917                                    AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
1918  
1919          return $files;
1920      }
1921  }
1922  
1923  /**
1924   * Structure step in charge of creating the main moodle_backup.xml file
1925   * where all the information related to the backup, settings, license and
1926   * other information needed on restore is added*/
1927  class backup_main_structure_step extends backup_structure_step {
1928  
1929      protected function define_structure() {
1930  
1931          global $CFG;
1932  
1933          $info = array();
1934  
1935          $info['name'] = $this->get_setting_value('filename');
1936          $info['moodle_version'] = $CFG->version;
1937          $info['moodle_release'] = $CFG->release;
1938          $info['backup_version'] = $CFG->backup_version;
1939          $info['backup_release'] = $CFG->backup_release;
1940          $info['backup_date']    = time();
1941          $info['backup_uniqueid']= $this->get_backupid();
1942          $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
1943          $info['include_files'] = backup_controller_dbops::backup_includes_files($this->get_backupid());
1944          $info['include_file_references_to_external_content'] =
1945                  backup_controller_dbops::backup_includes_file_references($this->get_backupid());
1946          $info['original_wwwroot']=$CFG->wwwroot;
1947          $info['original_site_identifier_hash'] = md5(get_site_identifier());
1948          $info['original_course_id'] = $this->get_courseid();
1949          $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
1950          $info['original_course_format'] = $originalcourseinfo->format;
1951          $info['original_course_fullname']  = $originalcourseinfo->fullname;
1952          $info['original_course_shortname'] = $originalcourseinfo->shortname;
1953          $info['original_course_startdate'] = $originalcourseinfo->startdate;
1954          $info['original_course_enddate']   = $originalcourseinfo->enddate;
1955          $info['original_course_contextid'] = context_course::instance($this->get_courseid())->id;
1956          $info['original_system_contextid'] = context_system::instance()->id;
1957  
1958          // Get more information from controller
1959          list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
1960                  $this->get_backupid(), $this->get_task()->get_progress());
1961  
1962          // Define elements
1963  
1964          $moodle_backup = new backup_nested_element('moodle_backup');
1965  
1966          $information = new backup_nested_element('information', null, array(
1967              'name', 'moodle_version', 'moodle_release', 'backup_version',
1968              'backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot',
1969              'original_site_identifier_hash', 'original_course_id', 'original_course_format',
1970              'original_course_fullname', 'original_course_shortname', 'original_course_startdate', 'original_course_enddate',
1971              'original_course_contextid', 'original_system_contextid'));
1972  
1973          $details = new backup_nested_element('details');
1974  
1975          $detail = new backup_nested_element('detail', array('backup_id'), array(
1976              'type', 'format', 'interactive', 'mode',
1977              'execution', 'executiontime'));
1978  
1979          $contents = new backup_nested_element('contents');
1980  
1981          $activities = new backup_nested_element('activities');
1982  
1983          $activity = new backup_nested_element('activity', null, array(
1984              'moduleid', 'sectionid', 'modulename', 'title',
1985              'directory'));
1986  
1987          $sections = new backup_nested_element('sections');
1988  
1989          $section = new backup_nested_element('section', null, array(
1990              'sectionid', 'title', 'directory'));
1991  
1992          $course = new backup_nested_element('course', null, array(
1993              'courseid', 'title', 'directory'));
1994  
1995          $settings = new backup_nested_element('settings');
1996  
1997          $setting = new backup_nested_element('setting', null, array(
1998              'level', 'section', 'activity', 'name', 'value'));
1999  
2000          // Build the tree
2001  
2002          $moodle_backup->add_child($information);
2003  
2004          $information->add_child($details);
2005          $details->add_child($detail);
2006  
2007          $information->add_child($contents);
2008          if (!empty($cinfo['activities'])) {
2009              $contents->add_child($activities);
2010              $activities->add_child($activity);
2011          }
2012          if (!empty($cinfo['sections'])) {
2013              $contents->add_child($sections);
2014              $sections->add_child($section);
2015          }
2016          if (!empty($cinfo['course'])) {
2017              $contents->add_child($course);
2018          }
2019  
2020          $information->add_child($settings);
2021          $settings->add_child($setting);
2022  
2023  
2024          // Set the sources
2025  
2026          $information->set_source_array(array((object)$info));
2027  
2028          $detail->set_source_array($dinfo);
2029  
2030          $activity->set_source_array($cinfo['activities']);
2031  
2032          $section->set_source_array($cinfo['sections']);
2033  
2034          $course->set_source_array($cinfo['course']);
2035  
2036          $setting->set_source_array($sinfo);
2037  
2038          // Prepare some information to be sent to main moodle_backup.xml file
2039          return $moodle_backup;
2040      }
2041  
2042  }
2043  
2044  /**
2045   * Execution step that will generate the final zip (.mbz) file with all the contents
2046   */
2047  class backup_zip_contents extends backup_execution_step implements file_progress {
2048      /**
2049       * @var bool True if we have started tracking progress
2050       */
2051      protected $startedprogress;
2052  
2053      protected function define_execution() {
2054  
2055          // Get basepath
2056          $basepath = $this->get_basepath();
2057  
2058          // Get the list of files in directory
2059          $filestemp = get_directory_list($basepath, '', false, true, true);
2060          $files = array();
2061          foreach ($filestemp as $file) { // Add zip paths and fs paths to all them
2062              $files[$file] = $basepath . '/' . $file;
2063          }
2064  
2065          // Add the log file if exists
2066          $logfilepath = $basepath . '.log';
2067          if (file_exists($logfilepath)) {
2068               $files['moodle_backup.log'] = $logfilepath;
2069          }
2070  
2071          // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
2072          $zipfile = $basepath . '/backup.mbz';
2073  
2074          // Get the zip packer
2075          $zippacker = get_file_packer('application/vnd.moodle.backup');
2076  
2077          // Track overall progress for the 2 long-running steps (archive to
2078          // pathname, get backup information).
2079          $reporter = $this->task->get_progress();
2080          $reporter->start_progress('backup_zip_contents', 2);
2081  
2082          // Zip files
2083          $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this);
2084  
2085          // If any sub-progress happened, end it.
2086          if ($this->startedprogress) {
2087              $this->task->get_progress()->end_progress();
2088              $this->startedprogress = false;
2089          } else {
2090              // No progress was reported, manually move it on to the next overall task.
2091              $reporter->progress(1);
2092          }
2093  
2094          // Something went wrong.
2095          if ($result === false) {
2096              @unlink($zipfile);
2097              throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip');
2098          }
2099          // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid.
2100          try {
2101              backup_general_helper::get_backup_information_from_mbz($zipfile, $this);
2102          } catch (backup_helper_exception $e) {
2103              @unlink($zipfile);
2104              throw new backup_step_exception('error_zip_packing', '', $e->debuginfo);
2105          }
2106  
2107          // If any sub-progress happened, end it.
2108          if ($this->startedprogress) {
2109              $this->task->get_progress()->end_progress();
2110              $this->startedprogress = false;
2111          } else {
2112              $reporter->progress(2);
2113          }
2114          $reporter->end_progress();
2115      }
2116  
2117      /**
2118       * Implementation for file_progress interface to display unzip progress.
2119       *
2120       * @param int $progress Current progress
2121       * @param int $max Max value
2122       */
2123      public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
2124          $reporter = $this->task->get_progress();
2125  
2126          // Start tracking progress if necessary.
2127          if (!$this->startedprogress) {
2128              $reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE)
2129                      ? \core\progress\base::INDETERMINATE : $max);
2130              $this->startedprogress = true;
2131          }
2132  
2133          // Pass progress through to whatever handles it.
2134          $reporter->progress(($progress == file_progress::INDETERMINATE)
2135                  ? \core\progress\base::INDETERMINATE : $progress);
2136       }
2137  }
2138  
2139  /**
2140   * This step will send the generated backup file to its final destination
2141   */
2142  class backup_store_backup_file extends backup_execution_step {
2143  
2144      protected function define_execution() {
2145  
2146          // Get basepath
2147          $basepath = $this->get_basepath();
2148  
2149          // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
2150          $zipfile = $basepath . '/backup.mbz';
2151  
2152          $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid());
2153          // Perform storage and return it (TODO: shouldn't be array but proper result object)
2154          return array(
2155              'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile,
2156                      $this->task->get_progress()),
2157              'include_file_references_to_external_content' => $has_file_references
2158          );
2159      }
2160  }
2161  
2162  
2163  /**
2164   * This step will search for all the activity (not calculations, categories nor aggregations) grade items
2165   * and put them to the backup_ids tables, to be used later as base to backup them
2166   */
2167  class backup_activity_grade_items_to_ids extends backup_execution_step {
2168  
2169      protected function define_execution() {
2170  
2171          // Fetch all activity grade items
2172          if ($items = grade_item::fetch_all(array(
2173                           'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),
2174                           'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {
2175              // Annotate them in backup_ids
2176              foreach ($items as $item) {
2177                  backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);
2178              }
2179          }
2180      }
2181  }
2182  
2183  
2184  /**
2185   * This step allows enrol plugins to annotate custom fields.
2186   *
2187   * @package   core_backup
2188   * @copyright 2014 University of Wisconsin
2189   * @author    Matt Petro
2190   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2191   */
2192  class backup_enrolments_execution_step extends backup_execution_step {
2193  
2194      /**
2195       * Function that will contain all the code to be executed.
2196       */
2197      protected function define_execution() {
2198          global $DB;
2199  
2200          $plugins = enrol_get_plugins(true);
2201          $enrols = $DB->get_records('enrol', array(
2202                  'courseid' => $this->task->get_courseid()));
2203  
2204          // Allow each enrol plugin to add annotations.
2205          foreach ($enrols as $enrol) {
2206              if (isset($plugins[$enrol->enrol])) {
2207                  $plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol);
2208              }
2209          }
2210      }
2211  
2212      /**
2213       * Annotate a single name/id pair.
2214       * This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}.
2215       *
2216       * @param string $itemname
2217       * @param int $itemid
2218       */
2219      public function annotate_id($itemname, $itemid) {
2220          backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid);
2221      }
2222  }
2223  
2224  /**
2225   * This step will annotate all the groups and groupings belonging to the course
2226   */
2227  class backup_annotate_course_groups_and_groupings extends backup_execution_step {
2228  
2229      protected function define_execution() {
2230          global $DB;
2231  
2232          // Get all the course groups
2233          if ($groups = $DB->get_records('groups', array(
2234                  'courseid' => $this->task->get_courseid()))) {
2235              foreach ($groups as $group) {
2236                  backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id);
2237              }
2238          }
2239  
2240          // Get all the course groupings
2241          if ($groupings = $DB->get_records('groupings', array(
2242                  'courseid' => $this->task->get_courseid()))) {
2243              foreach ($groupings as $grouping) {
2244                  backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id);
2245              }
2246          }
2247      }
2248  }
2249  
2250  /**
2251   * This step will annotate all the groups belonging to already annotated groupings
2252   */
2253  class backup_annotate_groups_from_groupings extends backup_execution_step {
2254  
2255      protected function define_execution() {
2256          global $DB;
2257  
2258          // Fetch all the annotated groupings
2259          if ($groupings = $DB->get_records('backup_ids_temp', array(
2260                  'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
2261              foreach ($groupings as $grouping) {
2262                  if ($groups = $DB->get_records('groupings_groups', array(
2263                          'groupingid' => $grouping->itemid))) {
2264                      foreach ($groups as $group) {
2265                          backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
2266                      }
2267                  }
2268              }
2269          }
2270      }
2271  }
2272  
2273  /**
2274   * This step will annotate all the scales belonging to already annotated outcomes
2275   */
2276  class backup_annotate_scales_from_outcomes extends backup_execution_step {
2277  
2278      protected function define_execution() {
2279          global $DB;
2280  
2281          // Fetch all the annotated outcomes
2282          if ($outcomes = $DB->get_records('backup_ids_temp', array(
2283                  'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
2284              foreach ($outcomes as $outcome) {
2285                  if ($scale = $DB->get_record('grade_outcomes', array(
2286                          'id' => $outcome->itemid))) {
2287                      // Annotate as scalefinal because it's > 0
2288                      backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
2289                  }
2290              }
2291          }
2292      }
2293  }
2294  
2295  /**
2296   * This step will generate all the file annotations for the already
2297   * annotated (final) question_categories. It calculates the different
2298   * contexts that are being backup and, annotates all the files
2299   * on every context belonging to the "question" component. As far as
2300   * we are always including *complete* question banks it is safe and
2301   * optimal to do that in this (one pass) way
2302   */
2303  class backup_annotate_all_question_files extends backup_execution_step {
2304  
2305      protected function define_execution() {
2306          global $DB;
2307  
2308          // Get all the different contexts for the final question_categories
2309          // annotated along the whole backup
2310          $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
2311                                          FROM {question_categories} qc
2312                                          JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
2313                                         WHERE bi.backupid = ?
2314                                           AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
2315          // To know about qtype specific components/fileareas
2316          $components = backup_qtype_plugin::get_components_and_fileareas();
2317          // Let's loop
2318          foreach($rs as $record) {
2319              // Backup all the file areas the are managed by the core question component.
2320              // That is, by the question_type base class. In particular, we don't want
2321              // to include files belonging to responses here.
2322              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'questiontext', null);
2323              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'generalfeedback', null);
2324              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answer', null);
2325              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answerfeedback', null);
2326              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'hint', null);
2327              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'correctfeedback', null);
2328              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'partiallycorrectfeedback', null);
2329              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'incorrectfeedback', null);
2330  
2331              // For files belonging to question types, we make the leap of faith that
2332              // all the files belonging to the question type are part of the question definition,
2333              // so we can just backup all the files in bulk, without specifying each
2334              // file area name separately.
2335              foreach ($components as $component => $fileareas) {
2336                  backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null);
2337              }
2338          }
2339          $rs->close();
2340      }
2341  }
2342  
2343  /**
2344   * structure step in charge of constructing the questions.xml file for all the
2345   * question categories and questions required by the backup
2346   * and letters related to one activity
2347   */
2348  class backup_questions_structure_step extends backup_structure_step {
2349  
2350      protected function define_structure() {
2351  
2352          // Define each element separated
2353  
2354          $qcategories = new backup_nested_element('question_categories');
2355  
2356          $qcategory = new backup_nested_element('question_category', array('id'), array(
2357              'name', 'contextid', 'contextlevel', 'contextinstanceid',
2358              'info', 'infoformat', 'stamp', 'parent',
2359              'sortorder', 'idnumber'));
2360  
2361          $questions = new backup_nested_element('questions');
2362  
2363          $question = new backup_nested_element('question', array('id'), array(
2364              'parent', 'name', 'questiontext', 'questiontextformat',
2365              'generalfeedback', 'generalfeedbackformat', 'defaultmark', 'penalty',
2366              'qtype', 'length', 'stamp', 'version',
2367              'hidden', 'timecreated', 'timemodified', 'createdby', 'modifiedby', 'idnumber'));
2368  
2369          // attach qtype plugin structure to $question element, only one allowed
2370          $this->add_plugin_structure('qtype', $question, false);
2371  
2372          // attach local plugin stucture to $question element, multiple allowed
2373          $this->add_plugin_structure('local', $question, true);
2374  
2375          $qhints = new backup_nested_element('question_hints');
2376  
2377          $qhint = new backup_nested_element('question_hint', array('id'), array(
2378              'hint', 'hintformat', 'shownumcorrect', 'clearwrong', 'options'));
2379  
2380          $tags = new backup_nested_element('tags');
2381  
2382          $tag = new backup_nested_element('tag', array('id', 'contextid'), array('name', 'rawname'));
2383  
2384          // Build the tree
2385  
2386          $qcategories->add_child($qcategory);
2387          $qcategory->add_child($questions);
2388          $questions->add_child($question);
2389          $question->add_child($qhints);
2390          $qhints->add_child($qhint);
2391  
2392          $question->add_child($tags);
2393          $tags->add_child($tag);
2394  
2395          // Define the sources
2396  
2397          $qcategory->set_source_sql("
2398              SELECT gc.*, contextlevel, instanceid AS contextinstanceid
2399                FROM {question_categories} gc
2400                JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
2401                JOIN {context} co ON co.id = gc.contextid
2402               WHERE bi.backupid = ?
2403                 AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID));
2404  
2405          $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
2406  
2407          $qhint->set_source_sql('
2408                  SELECT *
2409                  FROM {question_hints}
2410                  WHERE questionid = :questionid
2411                  ORDER BY id',
2412                  array('questionid' => backup::VAR_PARENTID));
2413  
2414          $tag->set_source_sql("SELECT t.id, ti.contextid, t.name, t.rawname
2415                                FROM {tag} t
2416                                JOIN {tag_instance} ti ON ti.tagid = t.id
2417                                WHERE ti.itemid = ?
2418                                AND ti.itemtype = 'question'
2419                                AND ti.component = 'core_question'",
2420              [
2421                  backup::VAR_PARENTID
2422              ]);
2423  
2424          // don't need to annotate ids nor files
2425          // (already done by {@link backup_annotate_all_question_files}
2426  
2427          return $qcategories;
2428      }
2429  }
2430  
2431  
2432  
2433  /**
2434   * This step will generate all the file  annotations for the already
2435   * annotated (final) users. Need to do this here because each user
2436   * has its own context and structure tasks only are able to handle
2437   * one context. Also, this step will guarantee that every user has
2438   * its context created (req for other steps)
2439   */
2440  class backup_annotate_all_user_files extends backup_execution_step {
2441  
2442      protected function define_execution() {
2443          global $DB;
2444  
2445          // List of fileareas we are going to annotate
2446          $fileareas = array('profile', 'icon');
2447  
2448          // Fetch all annotated (final) users
2449          $rs = $DB->get_recordset('backup_ids_temp', array(
2450              'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
2451          $progress = $this->task->get_progress();
2452          $progress->start_progress($this->get_name());
2453          foreach ($rs as $record) {
2454              $userid = $record->itemid;
2455              $userctx = context_user::instance($userid, IGNORE_MISSING);
2456              if (!$userctx) {
2457                  continue; // User has not context, sure it's a deleted user, so cannot have files
2458              }
2459              // Proceed with every user filearea
2460              foreach ($fileareas as $filearea) {
2461                  // We don't need to specify itemid ($userid - 5th param) as far as by
2462                  // context we can get all the associated files. See MDL-22092
2463                  backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null);
2464                  $progress->progress();
2465              }
2466          }
2467          $progress->end_progress();
2468          $rs->close();
2469      }
2470  }
2471  
2472  
2473  /**
2474   * Defines the backup step for advanced grading methods attached to the activity module
2475   */
2476  class backup_activity_grading_structure_step extends backup_structure_step {
2477  
2478      /**
2479       * Include the grading.xml only if the module supports advanced grading
2480       */
2481      protected function execute_condition() {
2482  
2483          // No grades on the front page.
2484          if ($this->get_courseid() == SITEID) {
2485              return false;
2486          }
2487  
2488          return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false);
2489      }
2490  
2491      /**
2492       * Declares the gradable areas structures and data sources
2493       */
2494      protected function define_structure() {
2495  
2496          // To know if we are including userinfo
2497          $userinfo = $this->get_setting_value('userinfo');
2498  
2499          // Define the elements
2500  
2501          $areas = new backup_nested_element('areas');
2502  
2503          $area = new backup_nested_element('area', array('id'), array(
2504              'areaname', 'activemethod'));
2505  
2506          $definitions = new backup_nested_element('definitions');
2507  
2508          $definition = new backup_nested_element('definition', array('id'), array(
2509              'method', 'name', 'description', 'descriptionformat', 'status',
2510              'timecreated', 'timemodified', 'options'));
2511  
2512          $instances = new backup_nested_element('instances');
2513  
2514          $instance = new backup_nested_element('instance', array('id'), array(
2515              'raterid', 'itemid', 'rawgrade', 'status', 'feedback',
2516              'feedbackformat', 'timemodified'));
2517  
2518          // Build the tree including the method specific structures
2519          // (beware - the order of how gradingform plugins structures are attached is important)
2520          $areas->add_child($area);
2521          // attach local plugin stucture to $area element, multiple allowed
2522          $this->add_plugin_structure('local', $area, true);
2523          $area->add_child($definitions);
2524          $definitions->add_child($definition);
2525          $this->add_plugin_structure('gradingform', $definition, true);
2526          // attach local plugin stucture to $definition element, multiple allowed
2527          $this->add_plugin_structure('local', $definition, true);
2528          $definition->add_child($instances);
2529          $instances->add_child($instance);
2530          $this->add_plugin_structure('gradingform', $instance, false);
2531          // attach local plugin stucture to $instance element, multiple allowed
2532          $this->add_plugin_structure('local', $instance, true);
2533  
2534          // Define data sources
2535  
2536          $area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID,
2537              'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename())));
2538  
2539          $definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID));
2540  
2541          if ($userinfo) {
2542              $instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID));
2543          }
2544  
2545          // Annotate references
2546          $definition->annotate_files('grading', 'description', 'id');
2547          $instance->annotate_ids('user', 'raterid');
2548  
2549          // Return the root element
2550          return $areas;
2551      }
2552  }
2553  
2554  
2555  /**
2556   * structure step in charge of constructing the grades.xml file for all the grade items
2557   * and letters related to one activity
2558   */
2559  class backup_activity_grades_structure_step extends backup_structure_step {
2560  
2561      /**
2562       * No grades on the front page.
2563       * @return bool
2564       */
2565      protected function execute_condition() {
2566          return ($this->get_courseid() != SITEID);
2567      }
2568  
2569      protected function define_structure() {
2570          global $CFG;
2571  
2572          require_once($CFG->libdir . '/grade/constants.php');
2573  
2574          // To know if we are including userinfo
2575          $userinfo = $this->get_setting_value('userinfo');
2576  
2577          // Define each element separated
2578  
2579          $book = new backup_nested_element('activity_gradebook');
2580  
2581          $items = new backup_nested_element('grade_items');
2582  
2583          $item = new backup_nested_element('grade_item', array('id'), array(
2584              'categoryid', 'itemname', 'itemtype', 'itemmodule',
2585              'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
2586              'calculation', 'gradetype', 'grademax', 'grademin',
2587              'scaleid', 'outcomeid', 'gradepass', 'multfactor',
2588              'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
2589              'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
2590              'needsupdate', 'timecreated', 'timemodified'));
2591  
2592          $grades = new backup_nested_element('grade_grades');
2593  
2594          $grade = new backup_nested_element('grade_grade', array('id'), array(
2595              'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
2596              'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
2597              'locked', 'locktime', 'exported', 'overridden',
2598              'excluded', 'feedback', 'feedbackformat', 'information',
2599              'informationformat', 'timecreated', 'timemodified',
2600              'aggregationstatus', 'aggregationweight'));
2601  
2602          $letters = new backup_nested_element('grade_letters');
2603  
2604          $letter = new backup_nested_element('grade_letter', 'id', array(
2605              'lowerboundary', 'letter'));
2606  
2607          // Build the tree
2608  
2609          $book->add_child($items);
2610          $items->add_child($item);
2611  
2612          $item->add_child($grades);
2613          $grades->add_child($grade);
2614  
2615          $book->add_child($letters);
2616          $letters->add_child($letter);
2617  
2618          // Define sources
2619  
2620          $item->set_source_sql("SELECT gi.*
2621                                 FROM {grade_items} gi
2622                                 JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
2623                                 WHERE bi.backupid = ?
2624                                 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2625  
2626          // This only happens if we are including user info
2627          if ($userinfo) {
2628              $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
2629              $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, 'id');
2630          }
2631  
2632          $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
2633  
2634          // Annotations
2635  
2636          $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
2637          $item->annotate_ids('outcome', 'outcomeid');
2638  
2639          $grade->annotate_ids('user', 'userid');
2640          $grade->annotate_ids('user', 'usermodified');
2641  
2642          // Return the root element (book)
2643  
2644          return $book;
2645      }
2646  }
2647  
2648  /**
2649   * Structure step in charge of constructing the grade history of an activity.
2650   *
2651   * This step is added to the task regardless of the setting 'grade_histories'.
2652   * The reason is to allow for a more flexible step in case the logic needs to be
2653   * split accross different settings to control the history of items and/or grades.
2654   */
2655  class backup_activity_grade_history_structure_step extends backup_structure_step {
2656  
2657      /**
2658       * No grades on the front page.
2659       * @return bool
2660       */
2661      protected function execute_condition() {
2662          return ($this->get_courseid() != SITEID);
2663      }
2664  
2665      protected function define_structure() {
2666          global $CFG;
2667  
2668          require_once($CFG->libdir . '/grade/constants.php');
2669  
2670          // Settings to use.
2671          $userinfo = $this->get_setting_value('userinfo');
2672          $history = $this->get_setting_value('grade_histories');
2673  
2674          // Create the nested elements.
2675          $bookhistory = new backup_nested_element('grade_history');
2676          $grades = new backup_nested_element('grade_grades');
2677          $grade = new backup_nested_element('grade_grade', array('id'), array(
2678              'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
2679              'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
2680              'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
2681              'excluded', 'feedback', 'feedbackformat', 'information',
2682              'informationformat', 'timemodified'));
2683  
2684          // Build the tree.
2685          $bookhistory->add_child($grades);
2686          $grades->add_child($grade);
2687  
2688          // This only happens if we are including user info and history.
2689          if ($userinfo && $history) {
2690              // Define sources. Only select the history related to existing activity items.
2691              $grade->set_source_sql("SELECT ggh.*
2692                                       FROM {grade_grades_history} ggh
2693                                       JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid
2694                                      WHERE bi.backupid = ?
2695                                        AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2696              $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA, 'id');
2697          }
2698  
2699          // Annotations.
2700          $grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0.
2701          $grade->annotate_ids('user', 'loggeduser');
2702          $grade->annotate_ids('user', 'userid');
2703          $grade->annotate_ids('user', 'usermodified');
2704  
2705          // Return the root element.
2706          return $bookhistory;
2707      }
2708  }
2709  
2710  /**
2711   * Backups up the course completion information for the course.
2712   */
2713  class backup_course_completion_structure_step extends backup_structure_step {
2714  
2715      protected function execute_condition() {
2716  
2717          // No completion on front page.
2718          if ($this->get_courseid() == SITEID) {
2719              return false;
2720          }
2721  
2722          // Check that all activities have been included
2723          if ($this->task->is_excluding_activities()) {
2724              return false;
2725          }
2726          return true;
2727      }
2728  
2729      /**
2730       * The structure of the course completion backup
2731       *
2732       * @return backup_nested_element
2733       */
2734      protected function define_structure() {
2735  
2736          // To know if we are including user completion info
2737          $userinfo = $this->get_setting_value('userscompletion');
2738  
2739          $cc = new backup_nested_element('course_completion');
2740  
2741          $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
2742              'course', 'criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod',
2743              'timeend', 'gradepass', 'role', 'roleshortname'
2744          ));
2745  
2746          $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
2747  
2748          $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
2749              'criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted'
2750          ));
2751  
2752          $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
2753              'userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate'
2754          ));
2755  
2756          $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
2757              'course','criteriatype','method','value'
2758          ));
2759  
2760          $cc->add_child($criteria);
2761              $criteria->add_child($criteriacompletions);
2762                  $criteriacompletions->add_child($criteriacomplete);
2763          $cc->add_child($coursecompletions);
2764          $cc->add_child($aggregatemethod);
2765  
2766          // We need some extra data for the restore.
2767          // - courseinstances shortname rather than an ID.
2768          // - roleshortname in case restoring on a different site.
2769          $sourcesql = "SELECT ccc.*, c.shortname AS courseinstanceshortname, r.shortname AS roleshortname
2770                          FROM {course_completion_criteria} ccc
2771                     LEFT JOIN {course} c ON c.id = ccc.courseinstance
2772                     LEFT JOIN {role} r ON r.id = ccc.role
2773                         WHERE ccc.course = ?";
2774          $criteria->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
2775  
2776          $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
2777  
2778          if ($userinfo) {
2779              $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
2780              $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
2781          }
2782  
2783          $criteria->annotate_ids('role', 'role');
2784          $criteriacomplete->annotate_ids('user', 'userid');
2785          $coursecompletions->annotate_ids('user', 'userid');
2786  
2787          return $cc;
2788  
2789      }
2790  }
2791  
2792  /**
2793   * Backup completion defaults for each module type.
2794   *
2795   * @package     core_backup
2796   * @copyright   2017 Marina Glancy
2797   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2798   */
2799  class backup_completion_defaults_structure_step extends backup_structure_step {
2800  
2801      /**
2802       * To conditionally decide if one step will be executed or no
2803       */
2804      protected function execute_condition() {
2805          // No completion on front page.
2806          if ($this->get_courseid() == SITEID) {
2807              return false;
2808          }
2809          return true;
2810      }
2811  
2812      /**
2813       * The structure of the course completion backup
2814       *
2815       * @return backup_nested_element
2816       */
2817      protected function define_structure() {
2818  
2819          $cc = new backup_nested_element('course_completion_defaults');
2820  
2821          $defaults = new backup_nested_element('course_completion_default', array('id'), array(
2822              'modulename', 'completion', 'completionview', 'completionusegrade', 'completionexpected', 'customrules'
2823          ));
2824  
2825          // Use module name instead of module id so we can insert into another site later.
2826          $sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade,
2827                    d.completionexpected, d.customrules
2828                  FROM {course_completion_defaults} d join {modules} m on d.module = m.id
2829                  WHERE d.course = ?";
2830          $defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
2831  
2832          $cc->add_child($defaults);
2833          return $cc;
2834  
2835      }
2836  }
2837  
2838  /**
2839   * Structure step in charge of constructing the contentbank.xml file for all the contents found in a given context
2840   */
2841  class backup_contentbankcontent_structure_step extends backup_structure_step {
2842  
2843      /**
2844       * Define structure for content bank step
2845       */
2846      protected function define_structure() {
2847  
2848          // Define each element separated.
2849          $contents = new backup_nested_element('contents');
2850          $content = new backup_nested_element('content', ['id'], [
2851              'name', 'contenttype', 'instanceid', 'configdata', 'usercreated', 'usermodified', 'timecreated', 'timemodified']);
2852  
2853          // Build the tree.
2854          $contents->add_child($content);
2855  
2856          // Define sources.
2857          $content->set_source_table('contentbank_content', ['contextid' => backup::VAR_CONTEXTID]);
2858  
2859          // Define annotations.
2860          $content->annotate_ids('user', 'usercreated');
2861          $content->annotate_ids('user', 'usermodified');
2862          $content->annotate_files('contentbank', 'public', 'id');
2863  
2864          // Return the root element (contents).
2865          return $contents;
2866      }
2867  }