Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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