Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

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