Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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