Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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'));
 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  
1290          $completions = new backup_nested_element('completions');
1291  
1292          $completion = new backup_nested_element('completion', array('id'), array(
1293              'userid', 'completionstate', 'viewed', 'timemodified'));
1294  
1295          // Build the tree
1296  
1297          $completions->add_child($completion);
1298  
1299          // Define sources
1300  
1301          $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
1302  
1303          // Define id annotations
1304  
1305          $completion->annotate_ids('user', 'userid');
1306  
1307          // Return the root element (completions)
1308          return $completions;
1309      }
1310  }
1311  
1312  /**
1313   * structure step in charge of constructing the main groups.xml file for all the groups and
1314   * groupings information already annotated
1315   */
1316  class backup_groups_structure_step extends backup_structure_step {
1317  
1318      protected function define_structure() {
1319  
1320          // To know if we are including users.
1321          $userinfo = $this->get_setting_value('users');
1322          // To know if we are including groups and groupings.
1323          $groupinfo = $this->get_setting_value('groups');
1324  
1325          // Define each element separated
1326  
1327          $groups = new backup_nested_element('groups');
1328  
1329          $group = new backup_nested_element('group', array('id'), array(
1330              'name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey',
1331              'picture', 'timecreated', 'timemodified'));
1332  
1333          $members = new backup_nested_element('group_members');
1334  
1335          $member = new backup_nested_element('group_member', array('id'), array(
1336              'userid', 'timeadded', 'component', 'itemid'));
1337  
1338          $groupings = new backup_nested_element('groupings');
1339  
1340          $grouping = new backup_nested_element('grouping', 'id', array(
1341              'name', 'idnumber', 'description', 'descriptionformat', 'configdata',
1342              'timecreated', 'timemodified'));
1343  
1344          $groupinggroups = new backup_nested_element('grouping_groups');
1345  
1346          $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
1347              'groupid', 'timeadded'));
1348  
1349          // Build the tree
1350  
1351          $groups->add_child($group);
1352          $groups->add_child($groupings);
1353  
1354          $group->add_child($members);
1355          $members->add_child($member);
1356  
1357          $groupings->add_child($grouping);
1358          $grouping->add_child($groupinggroups);
1359          $groupinggroups->add_child($groupinggroup);
1360  
1361          // Define sources
1362  
1363          // This only happens if we are including groups/groupings.
1364          if ($groupinfo) {
1365              $group->set_source_sql("
1366                  SELECT g.*
1367                    FROM {groups} g
1368                    JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1369                   WHERE bi.backupid = ?
1370                     AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
1371  
1372              $grouping->set_source_sql("
1373                  SELECT g.*
1374                    FROM {groupings} g
1375                    JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1376                   WHERE bi.backupid = ?
1377                     AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
1378              $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
1379  
1380              // This only happens if we are including users.
1381              if ($userinfo) {
1382                  $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
1383              }
1384          }
1385  
1386          // Define id annotations (as final)
1387  
1388          $member->annotate_ids('userfinal', 'userid');
1389  
1390          // Define file annotations
1391  
1392          $group->annotate_files('group', 'description', 'id');
1393          $group->annotate_files('group', 'icon', 'id');
1394          $grouping->annotate_files('grouping', 'description', 'id');
1395  
1396          // Return the root element (groups)
1397          return $groups;
1398      }
1399  }
1400  
1401  /**
1402   * structure step in charge of constructing the main users.xml file for all the users already
1403   * annotated (final). Includes custom profile fields, preferences, tags, role assignments and
1404   * overrides.
1405   */
1406  class backup_users_structure_step extends backup_structure_step {
1407  
1408      protected function define_structure() {
1409          global $CFG;
1410  
1411          // To know if we are anonymizing users
1412          $anonymize = $this->get_setting_value('anonymize');
1413          // To know if we are including role assignments
1414          $roleassignments = $this->get_setting_value('role_assignments');
1415  
1416          // Define each element separate.
1417  
1418          $users = new backup_nested_element('users');
1419  
1420          // Create the array of user fields by hand, as far as we have various bits to control
1421          // anonymize option, password backup, mnethostid...
1422  
1423          // First, the fields not needing anonymization nor special handling
1424          $normalfields = array(
1425              'confirmed', 'policyagreed', 'deleted',
1426              'lang', 'theme', 'timezone', 'firstaccess',
1427              'lastaccess', 'lastlogin', 'currentlogin',
1428              'mailformat', 'maildigest', 'maildisplay',
1429              'autosubscribe', 'trackforums', 'timecreated',
1430              'timemodified', 'trustbitmask');
1431  
1432          // Then, the fields potentially needing anonymization
1433          $anonfields = array(
1434              'username', 'idnumber', 'email', 'phone1',
1435              'phone2', 'institution', 'department', 'address',
1436              'city', 'country', 'lastip', 'picture',
1437              'description', 'descriptionformat', 'imagealt', 'auth');
1438          $anonfields = array_merge($anonfields, \core_user\fields::get_name_fields());
1439  
1440          // Add anonymized fields to $userfields with custom final element
1441          foreach ($anonfields as $field) {
1442              if ($anonymize) {
1443                  $userfields[] = new anonymizer_final_element($field);
1444              } else {
1445                  $userfields[] = $field; // No anonymization, normally added
1446              }
1447          }
1448  
1449          // mnethosturl requires special handling (custom final element)
1450          $userfields[] = new mnethosturl_final_element('mnethosturl');
1451  
1452          // password added conditionally
1453          if (!empty($CFG->includeuserpasswordsinbackup)) {
1454              $userfields[] = 'password';
1455          }
1456  
1457          // Merge all the fields
1458          $userfields = array_merge($userfields, $normalfields);
1459  
1460          $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1461  
1462          $customfields = new backup_nested_element('custom_fields');
1463  
1464          $customfield = new backup_nested_element('custom_field', array('id'), array(
1465              'field_name', 'field_type', 'field_data'));
1466  
1467          $tags = new backup_nested_element('tags');
1468  
1469          $tag = new backup_nested_element('tag', array('id'), array(
1470              'name', 'rawname'));
1471  
1472          $preferences = new backup_nested_element('preferences');
1473  
1474          $preference = new backup_nested_element('preference', array('id'), array(
1475              'name', 'value'));
1476  
1477          $roles = new backup_nested_element('roles');
1478  
1479          $overrides = new backup_nested_element('role_overrides');
1480  
1481          $override = new backup_nested_element('override', array('id'), array(
1482              'roleid', 'capability', 'permission', 'timemodified',
1483              'modifierid'));
1484  
1485          $assignments = new backup_nested_element('role_assignments');
1486  
1487          $assignment = new backup_nested_element('assignment', array('id'), array(
1488              'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
1489              'sortorder'));
1490  
1491          // Build the tree
1492  
1493          $users->add_child($user);
1494  
1495          $user->add_child($customfields);
1496          $customfields->add_child($customfield);
1497  
1498          $user->add_child($tags);
1499          $tags->add_child($tag);
1500  
1501          $user->add_child($preferences);
1502          $preferences->add_child($preference);
1503  
1504          $user->add_child($roles);
1505  
1506          $roles->add_child($overrides);
1507          $roles->add_child($assignments);
1508  
1509          $overrides->add_child($override);
1510          $assignments->add_child($assignment);
1511  
1512          // Define sources
1513  
1514          $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
1515                                   FROM {user} u
1516                                   JOIN {backup_ids_temp} bi ON bi.itemid = u.id
1517                              LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . '
1518                              LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
1519                                  WHERE bi.backupid = ?
1520                                    AND bi.itemname = ?', array(
1521                                        backup_helper::is_sqlparam($this->get_backupid()),
1522                                        backup_helper::is_sqlparam('userfinal')));
1523  
1524          // All the rest on information is only added if we arent
1525          // in an anonymized backup
1526          if (!$anonymize) {
1527              $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data
1528                                              FROM {user_info_field} f
1529                                              JOIN {user_info_data} d ON d.fieldid = f.id
1530                                             WHERE d.userid = ?', array(backup::VAR_PARENTID));
1531  
1532              $customfield->set_source_alias('shortname', 'field_name');
1533              $customfield->set_source_alias('datatype',  'field_type');
1534              $customfield->set_source_alias('data',      'field_data');
1535  
1536              $tag->set_source_sql('SELECT t.id, t.name, t.rawname
1537                                      FROM {tag} t
1538                                      JOIN {tag_instance} ti ON ti.tagid = t.id
1539                                     WHERE ti.itemtype = ?
1540                                       AND ti.itemid = ?', array(
1541                                           backup_helper::is_sqlparam('user'),
1542                                           backup::VAR_PARENTID));
1543  
1544              $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1545  
1546              $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1547  
1548              // Assignments only added if specified
1549              if ($roleassignments) {
1550                  $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1551              }
1552  
1553              // Define id annotations (as final)
1554              $override->annotate_ids('rolefinal', 'roleid');
1555          }
1556          // Return root element (users)
1557          return $users;
1558      }
1559  }
1560  
1561  /**
1562   * structure step in charge of constructing the block.xml file for one
1563   * given block (instance and positions). If the block has custom DB structure
1564   * that will go to a separate file (different step defined in block class)
1565   */
1566  class backup_block_instance_structure_step extends backup_structure_step {
1567  
1568      protected function define_structure() {
1569          global $DB;
1570  
1571          // Define each element separated
1572  
1573          $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
1574                  'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1575                  'subpagepattern', 'defaultregion', 'defaultweight', 'configdata',
1576                  'timecreated', 'timemodified'));
1577  
1578          $positions = new backup_nested_element('block_positions');
1579  
1580          $position = new backup_nested_element('block_position', array('id'), array(
1581              'contextid', 'pagetype', 'subpage', 'visible',
1582              'region', 'weight'));
1583  
1584          // Build the tree
1585  
1586          $block->add_child($positions);
1587          $positions->add_child($position);
1588  
1589          // Transform configdata information if needed (process links and friends)
1590          $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
1591          if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
1592              $configdata = array_filter(
1593                  (array) unserialize_object(base64_decode($blockrec->configdata)),
1594                  static function($value): bool {
1595                      return !($value instanceof __PHP_Incomplete_Class);
1596                  }
1597              );
1598  
1599              foreach ($configdata as $attribute => $value) {
1600                  if (in_array($attribute, $attrstotransform)) {
1601                      $configdata[$attribute] = $this->contenttransformer->process($value);
1602                  }
1603              }
1604              $blockrec->configdata = base64_encode(serialize((object)$configdata));
1605          }
1606          $blockrec->contextid = $this->task->get_contextid();
1607          // Get the version of the block
1608          $blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version');
1609  
1610          // Define sources
1611  
1612          $block->set_source_array(array($blockrec));
1613  
1614          $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
1615  
1616          // File anotations (for fileareas specified on each block)
1617          foreach ($this->task->get_fileareas() as $filearea) {
1618              $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);
1619          }
1620  
1621          // Return the root element (block)
1622          return $block;
1623      }
1624  }
1625  
1626  /**
1627   * structure step in charge of constructing the logs.xml file for all the log records found
1628   * in course. Note that we are sending to backup ALL the log records having cmid = 0. That
1629   * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do
1630   * that just in case they become restored some day in the future
1631   */
1632  class backup_course_logs_structure_step extends backup_structure_step {
1633  
1634      protected function define_structure() {
1635  
1636          // Define each element separated
1637  
1638          $logs = new backup_nested_element('logs');
1639  
1640          $log = new backup_nested_element('log', array('id'), array(
1641              'time', 'userid', 'ip', 'module',
1642              'action', 'url', 'info'));
1643  
1644          // Build the tree
1645  
1646          $logs->add_child($log);
1647  
1648          // Define sources (all the records belonging to the course, having cmid = 0)
1649  
1650          $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0)));
1651  
1652          // Annotations
1653          // NOTE: We don't annotate users from logs as far as they MUST be
1654          //       always annotated by the course (enrol, ras... whatever)
1655  
1656          // Return the root element (logs)
1657  
1658          return $logs;
1659      }
1660  }
1661  
1662  /**
1663   * structure step in charge of constructing the logs.xml file for all the log records found
1664   * in activity
1665   */
1666  class backup_activity_logs_structure_step extends backup_structure_step {
1667  
1668      protected function define_structure() {
1669  
1670          // Define each element separated
1671  
1672          $logs = new backup_nested_element('logs');
1673  
1674          $log = new backup_nested_element('log', array('id'), array(
1675              'time', 'userid', 'ip', 'module',
1676              'action', 'url', 'info'));
1677  
1678          // Build the tree
1679  
1680          $logs->add_child($log);
1681  
1682          // Define sources
1683  
1684          $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1685  
1686          // Annotations
1687          // NOTE: We don't annotate users from logs as far as they MUST be
1688          //       always annotated by the activity (true participants).
1689  
1690          // Return the root element (logs)
1691  
1692          return $logs;
1693      }
1694  }
1695  
1696  /**
1697   * Structure step in charge of constructing the logstores.xml file for the course logs.
1698   *
1699   * This backup step will backup the logs for all the enabled logstore subplugins supporting
1700   * it, for logs belonging to the course level.
1701   */
1702  class backup_course_logstores_structure_step extends backup_structure_step {
1703  
1704      protected function define_structure() {
1705  
1706          // Define the structure of logstores container.
1707          $logstores = new backup_nested_element('logstores');
1708          $logstore = new backup_nested_element('logstore');
1709          $logstores->add_child($logstore);
1710  
1711          // Add the tool_log logstore subplugins information to the logstore element.
1712          $this->add_subplugin_structure('logstore', $logstore, true, 'tool', 'log');
1713  
1714          return $logstores;
1715      }
1716  }
1717  
1718  /**
1719   * Structure step in charge of constructing the loglastaccess.xml file for the course logs.
1720   *
1721   * This backup step will backup the logs of the user_lastaccess table.
1722   */
1723  class backup_course_loglastaccess_structure_step extends backup_structure_step {
1724  
1725      /**
1726       *  This function creates the structures for the loglastaccess.xml file.
1727       *  Expected structure would look like this.
1728       *  <loglastaccesses>
1729       *      <loglastaccess id=2>
1730       *          <userid>5</userid>
1731       *          <timeaccess>1616887341</timeaccess>
1732       *      </loglastaccess>
1733       *  </loglastaccesses>
1734       *
1735       * @return backup_nested_element
1736       */
1737      protected function define_structure() {
1738  
1739          // To know if we are including userinfo.
1740          $userinfo = $this->get_setting_value('users');
1741  
1742          // Define the structure of logstores container.
1743          $lastaccesses = new backup_nested_element('lastaccesses');
1744          $lastaccess = new backup_nested_element('lastaccess', array('id'), array('userid', 'timeaccess'));
1745  
1746          // Define build tree.
1747          $lastaccesses->add_child($lastaccess);
1748  
1749          // This element should only happen if we are including user info.
1750          if ($userinfo) {
1751              // Define sources.
1752              $lastaccess->set_source_sql('
1753                  SELECT id, userid, timeaccess
1754                    FROM {user_lastaccess}
1755                   WHERE courseid = ?',
1756                  array(backup::VAR_COURSEID));
1757  
1758              // Define userid annotation to user.
1759              $lastaccess->annotate_ids('user', 'userid');
1760          }
1761  
1762          // Return the root element (lastaccessess).
1763          return $lastaccesses;
1764      }
1765  }
1766  
1767  /**
1768   * Structure step in charge of constructing the logstores.xml file for the activity logs.
1769   *
1770   * Note: Activity structure is completely equivalent to the course one, so just extend it.
1771   */
1772  class backup_activity_logstores_structure_step extends backup_course_logstores_structure_step {
1773  }
1774  
1775  /**
1776   * Course competencies backup structure step.
1777   */
1778  class backup_course_competencies_structure_step extends backup_structure_step {
1779  
1780      protected function define_structure() {
1781          $userinfo = $this->get_setting_value('users');
1782  
1783          $wrapper = new backup_nested_element('course_competencies');
1784  
1785          $settings = new backup_nested_element('settings', array('id'), array('pushratingstouserplans'));
1786          $wrapper->add_child($settings);
1787  
1788          $sql = 'SELECT s.pushratingstouserplans
1789                    FROM {' . \core_competency\course_competency_settings::TABLE . '} s
1790                   WHERE s.courseid = :courseid';
1791          $settings->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1792  
1793          $competencies = new backup_nested_element('competencies');
1794          $wrapper->add_child($competencies);
1795  
1796          $competency = new backup_nested_element('competency', null, array('id', 'idnumber', 'ruleoutcome',
1797              'sortorder', 'frameworkid', 'frameworkidnumber'));
1798          $competencies->add_child($competency);
1799  
1800          $sql = 'SELECT c.id, c.idnumber, cc.ruleoutcome, cc.sortorder, f.id AS frameworkid, f.idnumber AS frameworkidnumber
1801                    FROM {' . \core_competency\course_competency::TABLE . '} cc
1802                    JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyid
1803                    JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid
1804                   WHERE cc.courseid = :courseid
1805                ORDER BY cc.sortorder';
1806          $competency->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1807  
1808          $usercomps = new backup_nested_element('user_competencies');
1809          $wrapper->add_child($usercomps);
1810          if ($userinfo) {
1811              $usercomp = new backup_nested_element('user_competency', null, array('userid', 'competencyid',
1812                  'proficiency', 'grade'));
1813              $usercomps->add_child($usercomp);
1814  
1815              $sql = 'SELECT ucc.userid, ucc.competencyid, ucc.proficiency, ucc.grade
1816                        FROM {' . \core_competency\user_competency_course::TABLE . '} ucc
1817                       WHERE ucc.courseid = :courseid
1818                         AND ucc.grade IS NOT NULL';
1819              $usercomp->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1820              $usercomp->annotate_ids('user', 'userid');
1821          }
1822  
1823          return $wrapper;
1824      }
1825  
1826      /**
1827       * Execute conditions.
1828       *
1829       * @return bool
1830       */
1831      protected function execute_condition() {
1832  
1833          // Do not execute if competencies are not included.
1834          if (!$this->get_setting_value('competencies')) {
1835              return false;
1836          }
1837  
1838          return true;
1839      }
1840  }
1841  
1842  /**
1843   * Activity competencies backup structure step.
1844   */
1845  class backup_activity_competencies_structure_step extends backup_structure_step {
1846  
1847      protected function define_structure() {
1848          $wrapper = new backup_nested_element('course_module_competencies');
1849  
1850          $competencies = new backup_nested_element('competencies');
1851          $wrapper->add_child($competencies);
1852  
1853          $competency = new backup_nested_element('competency', null, array('idnumber', 'ruleoutcome',
1854              'sortorder', 'frameworkidnumber'));
1855          $competencies->add_child($competency);
1856  
1857          $sql = 'SELECT c.idnumber, cmc.ruleoutcome, cmc.sortorder, f.idnumber AS frameworkidnumber
1858                    FROM {' . \core_competency\course_module_competency::TABLE . '} cmc
1859                    JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cmc.competencyid
1860                    JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid
1861                   WHERE cmc.cmid = :coursemoduleid
1862                ORDER BY cmc.sortorder';
1863          $competency->set_source_sql($sql, array('coursemoduleid' => backup::VAR_MODID));
1864  
1865          return $wrapper;
1866      }
1867  
1868      /**
1869       * Execute conditions.
1870       *
1871       * @return bool
1872       */
1873      protected function execute_condition() {
1874  
1875          // Do not execute if competencies are not included.
1876          if (!$this->get_setting_value('competencies')) {
1877              return false;
1878          }
1879  
1880          return true;
1881      }
1882  }
1883  
1884  /**
1885   * structure in charge of constructing the inforef.xml file for all the items we want
1886   * to have referenced there (users, roles, files...)
1887   */
1888  class backup_inforef_structure_step extends backup_structure_step {
1889  
1890      protected function define_structure() {
1891  
1892          // Items we want to include in the inforef file.
1893          $items = backup_helper::get_inforef_itemnames();
1894  
1895          // Build the tree
1896  
1897          $inforef = new backup_nested_element('inforef');
1898  
1899          // For each item, conditionally, if there are already records, build element
1900          foreach ($items as $itemname) {
1901              if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {
1902                  $elementroot = new backup_nested_element($itemname . 'ref');
1903                  $element = new backup_nested_element($itemname, array(), array('id'));
1904                  $inforef->add_child($elementroot);
1905                  $elementroot->add_child($element);
1906                  $element->set_source_sql("
1907                      SELECT itemid AS id
1908                       FROM {backup_ids_temp}
1909                      WHERE backupid = ?
1910                        AND itemname = ?",
1911                     array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
1912              }
1913          }
1914  
1915          // We don't annotate anything there, but rely in the next step
1916          // (move_inforef_annotations_to_final) that will change all the
1917          // already saved 'inforref' entries to their 'final' annotations.
1918          return $inforef;
1919      }
1920  }
1921  
1922  /**
1923   * This step will get all the annotations already processed to inforef.xml file and
1924   * transform them into 'final' annotations.
1925   */
1926  class move_inforef_annotations_to_final extends backup_execution_step {
1927  
1928      protected function define_execution() {
1929  
1930          // Items we want to include in the inforef file
1931          $items = backup_helper::get_inforef_itemnames();
1932          $progress = $this->task->get_progress();
1933          $progress->start_progress($this->get_name(), count($items));
1934          $done = 1;
1935          foreach ($items as $itemname) {
1936              // Delegate to dbops
1937              backup_structure_dbops::move_annotations_to_final($this->get_backupid(),
1938                      $itemname, $progress);
1939              $progress->progress($done++);
1940          }
1941          $progress->end_progress();
1942      }
1943  }
1944  
1945  /**
1946   * structure in charge of constructing the files.xml file with all the
1947   * annotated (final) files along the process. At, the same time, and
1948   * using one specialised nested_element, will copy them form moodle storage
1949   * to backup storage
1950   */
1951  class backup_final_files_structure_step extends backup_structure_step {
1952  
1953      protected function define_structure() {
1954  
1955          // Define elements
1956  
1957          $files = new backup_nested_element('files');
1958  
1959          $file = new file_nested_element('file', array('id'), array(
1960              'contenthash', 'contextid', 'component', 'filearea', 'itemid',
1961              'filepath', 'filename', 'userid', 'filesize',
1962              'mimetype', 'status', 'timecreated', 'timemodified',
1963              'source', 'author', 'license', 'sortorder',
1964              'repositorytype', 'repositoryid', 'reference'));
1965  
1966          // Build the tree
1967  
1968          $files->add_child($file);
1969  
1970          // Define sources
1971  
1972          $file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference
1973                                   FROM {files} f
1974                                        LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid
1975                                        LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid
1976                                        LEFT JOIN {repository} r ON r.id = ri.typeid
1977                                        JOIN {backup_ids_temp} bi ON f.id = bi.itemid
1978                                  WHERE bi.backupid = ?
1979                                    AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
1980  
1981          return $files;
1982      }
1983  }
1984  
1985  /**
1986   * Structure step in charge of creating the main moodle_backup.xml file
1987   * where all the information related to the backup, settings, license and
1988   * other information needed on restore is added*/
1989  class backup_main_structure_step extends backup_structure_step {
1990  
1991      protected function define_structure() {
1992  
1993          global $CFG;
1994  
1995          $info = array();
1996  
1997          $info['name'] = $this->get_setting_value('filename');
1998          $info['moodle_version'] = $CFG->version;
1999          $info['moodle_release'] = $CFG->release;
2000          $info['backup_version'] = $CFG->backup_version;
2001          $info['backup_release'] = $CFG->backup_release;
2002          $info['backup_date']    = time();
2003          $info['backup_uniqueid']= $this->get_backupid();
2004          $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
2005          $info['include_files'] = backup_controller_dbops::backup_includes_files($this->get_backupid());
2006          $info['include_file_references_to_external_content'] =
2007                  backup_controller_dbops::backup_includes_file_references($this->get_backupid());
2008          $info['original_wwwroot']=$CFG->wwwroot;
2009          $info['original_site_identifier_hash'] = md5(get_site_identifier());
2010          $info['original_course_id'] = $this->get_courseid();
2011          $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
2012          $info['original_course_format'] = $originalcourseinfo->format;
2013          $info['original_course_fullname']  = $originalcourseinfo->fullname;
2014          $info['original_course_shortname'] = $originalcourseinfo->shortname;
2015          $info['original_course_startdate'] = $originalcourseinfo->startdate;
2016          $info['original_course_enddate']   = $originalcourseinfo->enddate;
2017          $info['original_course_contextid'] = context_course::instance($this->get_courseid())->id;
2018          $info['original_system_contextid'] = context_system::instance()->id;
2019  
2020          // Get more information from controller
2021          list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
2022                  $this->get_backupid(), $this->get_task()->get_progress());
2023  
2024          // Define elements
2025  
2026          $moodle_backup = new backup_nested_element('moodle_backup');
2027  
2028          $information = new backup_nested_element('information', null, array(
2029              'name', 'moodle_version', 'moodle_release', 'backup_version',
2030              'backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot',
2031              'original_site_identifier_hash', 'original_course_id', 'original_course_format',
2032              'original_course_fullname', 'original_course_shortname', 'original_course_startdate', 'original_course_enddate',
2033              'original_course_contextid', 'original_system_contextid'));
2034  
2035          $details = new backup_nested_element('details');
2036  
2037          $detail = new backup_nested_element('detail', array('backup_id'), array(
2038              'type', 'format', 'interactive', 'mode',
2039              'execution', 'executiontime'));
2040  
2041          $contents = new backup_nested_element('contents');
2042  
2043          $activities = new backup_nested_element('activities');
2044  
2045          $activity = new backup_nested_element('activity', null, array(
2046              'moduleid', 'sectionid', 'modulename', 'title',
2047              'directory'));
2048  
2049          $sections = new backup_nested_element('sections');
2050  
2051          $section = new backup_nested_element('section', null, array(
2052              'sectionid', 'title', 'directory'));
2053  
2054          $course = new backup_nested_element('course', null, array(
2055              'courseid', 'title', 'directory'));
2056  
2057          $settings = new backup_nested_element('settings');
2058  
2059          $setting = new backup_nested_element('setting', null, array(
2060              'level', 'section', 'activity', 'name', 'value'));
2061  
2062          // Build the tree
2063  
2064          $moodle_backup->add_child($information);
2065  
2066          $information->add_child($details);
2067          $details->add_child($detail);
2068  
2069          $information->add_child($contents);
2070          if (!empty($cinfo['activities'])) {
2071              $contents->add_child($activities);
2072              $activities->add_child($activity);
2073          }
2074          if (!empty($cinfo['sections'])) {
2075              $contents->add_child($sections);
2076              $sections->add_child($section);
2077          }
2078          if (!empty($cinfo['course'])) {
2079              $contents->add_child($course);
2080          }
2081  
2082          $information->add_child($settings);
2083          $settings->add_child($setting);
2084  
2085  
2086          // Set the sources
2087  
2088          $information->set_source_array(array((object)$info));
2089  
2090          $detail->set_source_array($dinfo);
2091  
2092          $activity->set_source_array($cinfo['activities']);
2093  
2094          $section->set_source_array($cinfo['sections']);
2095  
2096          $course->set_source_array($cinfo['course']);
2097  
2098          $setting->set_source_array($sinfo);
2099  
2100          // Prepare some information to be sent to main moodle_backup.xml file
2101          return $moodle_backup;
2102      }
2103  
2104  }
2105  
2106  /**
2107   * Execution step that will generate the final zip (.mbz) file with all the contents
2108   */
2109  class backup_zip_contents extends backup_execution_step implements file_progress {
2110      /**
2111       * @var bool True if we have started tracking progress
2112       */
2113      protected $startedprogress;
2114  
2115      protected function define_execution() {
2116  
2117          // Get basepath
2118          $basepath = $this->get_basepath();
2119  
2120          // Get the list of files in directory
2121          $filestemp = get_directory_list($basepath, '', false, true, true);
2122          $files = array();
2123          foreach ($filestemp as $file) { // Add zip paths and fs paths to all them
2124              $files[$file] = $basepath . '/' . $file;
2125          }
2126  
2127          // Add the log file if exists
2128          $logfilepath = $basepath . '.log';
2129          if (file_exists($logfilepath)) {
2130               $files['moodle_backup.log'] = $logfilepath;
2131          }
2132  
2133          // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
2134          $zipfile = $basepath . '/backup.mbz';
2135  
2136          // Get the zip packer
2137          $zippacker = get_file_packer('application/vnd.moodle.backup');
2138  
2139          // Track overall progress for the 2 long-running steps (archive to
2140          // pathname, get backup information).
2141          $reporter = $this->task->get_progress();
2142          $reporter->start_progress('backup_zip_contents', 2);
2143  
2144          // Zip files
2145          $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this);
2146  
2147          // If any sub-progress happened, end it.
2148          if ($this->startedprogress) {
2149              $this->task->get_progress()->end_progress();
2150              $this->startedprogress = false;
2151          } else {
2152              // No progress was reported, manually move it on to the next overall task.
2153              $reporter->progress(1);
2154          }
2155  
2156          // Something went wrong.
2157          if ($result === false) {
2158              @unlink($zipfile);
2159              throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip');
2160          }
2161          // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid.
2162          try {
2163              backup_general_helper::get_backup_information_from_mbz($zipfile, $this);
2164          } catch (backup_helper_exception $e) {
2165              @unlink($zipfile);
2166              throw new backup_step_exception('error_zip_packing', '', $e->debuginfo);
2167          }
2168  
2169          // If any sub-progress happened, end it.
2170          if ($this->startedprogress) {
2171              $this->task->get_progress()->end_progress();
2172              $this->startedprogress = false;
2173          } else {
2174              $reporter->progress(2);
2175          }
2176          $reporter->end_progress();
2177      }
2178  
2179      /**
2180       * Implementation for file_progress interface to display unzip progress.
2181       *
2182       * @param int $progress Current progress
2183       * @param int $max Max value
2184       */
2185      public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
2186          $reporter = $this->task->get_progress();
2187  
2188          // Start tracking progress if necessary.
2189          if (!$this->startedprogress) {
2190              $reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE)
2191                      ? \core\progress\base::INDETERMINATE : $max);
2192              $this->startedprogress = true;
2193          }
2194  
2195          // Pass progress through to whatever handles it.
2196          $reporter->progress(($progress == file_progress::INDETERMINATE)
2197                  ? \core\progress\base::INDETERMINATE : $progress);
2198       }
2199  }
2200  
2201  /**
2202   * This step will send the generated backup file to its final destination
2203   */
2204  class backup_store_backup_file extends backup_execution_step {
2205  
2206      protected function define_execution() {
2207  
2208          // Get basepath
2209          $basepath = $this->get_basepath();
2210  
2211          // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
2212          $zipfile = $basepath . '/backup.mbz';
2213  
2214          $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid());
2215          // Perform storage and return it (TODO: shouldn't be array but proper result object)
2216          return array(
2217              'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile,
2218                      $this->task->get_progress()),
2219              'include_file_references_to_external_content' => $has_file_references
2220          );
2221      }
2222  }
2223  
2224  
2225  /**
2226   * This step will search for all the activity (not calculations, categories nor aggregations) grade items
2227   * and put them to the backup_ids tables, to be used later as base to backup them
2228   */
2229  class backup_activity_grade_items_to_ids extends backup_execution_step {
2230  
2231      protected function define_execution() {
2232  
2233          // Fetch all activity grade items
2234          if ($items = grade_item::fetch_all(array(
2235                           'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),
2236                           'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {
2237              // Annotate them in backup_ids
2238              foreach ($items as $item) {
2239                  backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);
2240              }
2241          }
2242      }
2243  }
2244  
2245  
2246  /**
2247   * This step allows enrol plugins to annotate custom fields.
2248   *
2249   * @package   core_backup
2250   * @copyright 2014 University of Wisconsin
2251   * @author    Matt Petro
2252   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2253   */
2254  class backup_enrolments_execution_step extends backup_execution_step {
2255  
2256      /**
2257       * Function that will contain all the code to be executed.
2258       */
2259      protected function define_execution() {
2260          global $DB;
2261  
2262          $plugins = enrol_get_plugins(true);
2263          $enrols = $DB->get_records('enrol', array(
2264                  'courseid' => $this->task->get_courseid()));
2265  
2266          // Allow each enrol plugin to add annotations.
2267          foreach ($enrols as $enrol) {
2268              if (isset($plugins[$enrol->enrol])) {
2269                  $plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol);
2270              }
2271          }
2272      }
2273  
2274      /**
2275       * Annotate a single name/id pair.
2276       * This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}.
2277       *
2278       * @param string $itemname
2279       * @param int $itemid
2280       */
2281      public function annotate_id($itemname, $itemid) {
2282          backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid);
2283      }
2284  }
2285  
2286  /**
2287   * This step will annotate all the groups and groupings belonging to the course
2288   */
2289  class backup_annotate_course_groups_and_groupings extends backup_execution_step {
2290  
2291      protected function define_execution() {
2292          global $DB;
2293  
2294          // Get all the course groups
2295          if ($groups = $DB->get_records('groups', array(
2296                  'courseid' => $this->task->get_courseid()))) {
2297              foreach ($groups as $group) {
2298                  backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id);
2299              }
2300          }
2301  
2302          // Get all the course groupings
2303          if ($groupings = $DB->get_records('groupings', array(
2304                  'courseid' => $this->task->get_courseid()))) {
2305              foreach ($groupings as $grouping) {
2306                  backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id);
2307              }
2308          }
2309      }
2310  }
2311  
2312  /**
2313   * This step will annotate all the groups belonging to already annotated groupings
2314   */
2315  class backup_annotate_groups_from_groupings extends backup_execution_step {
2316  
2317      protected function define_execution() {
2318          global $DB;
2319  
2320          // Fetch all the annotated groupings
2321          if ($groupings = $DB->get_records('backup_ids_temp', array(
2322                  'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
2323              foreach ($groupings as $grouping) {
2324                  if ($groups = $DB->get_records('groupings_groups', array(
2325                          'groupingid' => $grouping->itemid))) {
2326                      foreach ($groups as $group) {
2327                          backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
2328                      }
2329                  }
2330              }
2331          }
2332      }
2333  }
2334  
2335  /**
2336   * This step will annotate all the scales belonging to already annotated outcomes
2337   */
2338  class backup_annotate_scales_from_outcomes extends backup_execution_step {
2339  
2340      protected function define_execution() {
2341          global $DB;
2342  
2343          // Fetch all the annotated outcomes
2344          if ($outcomes = $DB->get_records('backup_ids_temp', array(
2345                  'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
2346              foreach ($outcomes as $outcome) {
2347                  if ($scale = $DB->get_record('grade_outcomes', array(
2348                          'id' => $outcome->itemid))) {
2349                      // Annotate as scalefinal because it's > 0
2350                      backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
2351                  }
2352              }
2353          }
2354      }
2355  }
2356  
2357  /**
2358   * This step will generate all the file annotations for the already
2359   * annotated (final) question_categories. It calculates the different
2360   * contexts that are being backup and, annotates all the files
2361   * on every context belonging to the "question" component. As far as
2362   * we are always including *complete* question banks it is safe and
2363   * optimal to do that in this (one pass) way
2364   */
2365  class backup_annotate_all_question_files extends backup_execution_step {
2366  
2367      protected function define_execution() {
2368          global $DB;
2369  
2370          // Get all the different contexts for the final question_categories
2371          // annotated along the whole backup
2372          $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
2373                                          FROM {question_categories} qc
2374                                          JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
2375                                         WHERE bi.backupid = ?
2376                                           AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
2377          // To know about qtype specific components/fileareas
2378          $components = backup_qtype_plugin::get_components_and_fileareas();
2379          $progress = $this->task->get_progress();
2380          $progress->start_progress($this->get_name());
2381          // Let's loop
2382          foreach($rs as $record) {
2383              // Backup all the file areas the are managed by the core question component.
2384              // That is, by the question_type base class. In particular, we don't want
2385              // to include files belonging to responses here.
2386              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'questiontext', null,
2387                                          $progress);
2388              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'generalfeedback', null,
2389                                          $progress);
2390              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answer', null,
2391                                          $progress);
2392              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answerfeedback', null,
2393                                          $progress);
2394              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'hint', null,
2395                                          $progress);
2396              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'correctfeedback', null,
2397                                          $progress);
2398              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question',
2399                                          'partiallycorrectfeedback', null, $progress);
2400              backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'incorrectfeedback', null,
2401                                          $progress);
2402  
2403              // For files belonging to question types, we make the leap of faith that
2404              // all the files belonging to the question type are part of the question definition,
2405              // so we can just backup all the files in bulk, without specifying each
2406              // file area name separately.
2407              foreach ($components as $component => $fileareas) {
2408                  backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null,
2409                                              $progress);
2410              }
2411          }
2412          $progress->end_progress();
2413          $rs->close();
2414      }
2415  }
2416  
2417  /**
2418   * structure step in charge of constructing the questions.xml file for all the
2419   * question categories and questions required by the backup
2420   * and letters related to one activity.
2421   */
2422  class backup_questions_structure_step extends backup_structure_step {
2423  
2424      protected function define_structure() {
2425  
2426          // Define each element separately.
2427          $qcategories = new backup_nested_element('question_categories');
2428  
2429          $qcategory = new backup_nested_element('question_category', ['id'],
2430              [
2431                  'name',
2432                  'contextid',
2433                  'contextlevel',
2434                  'contextinstanceid',
2435                  'info',
2436                  'infoformat',
2437                  'stamp',
2438                  'parent',
2439                  'sortorder',
2440                  'idnumber',
2441              ]);
2442  
2443          $questionbankentries = new backup_nested_element('question_bank_entries');
2444  
2445          $questionbankentry = new backup_nested_element('question_bank_entry', ['id'],
2446              [
2447                  'questioncategoryid',
2448                  'idnumber',
2449                  'ownerid',
2450              ]);
2451  
2452          $questionversions = new backup_nested_element('question_version');
2453  
2454          $questionverion = new backup_nested_element('question_versions', ['id'], ['version', 'status']);
2455  
2456          $questions = new backup_nested_element('questions');
2457  
2458          $question = new backup_nested_element('question', ['id'],
2459              [
2460                  'parent',
2461                  'name',
2462                  'questiontext',
2463                  'questiontextformat',
2464                  'generalfeedback',
2465                  'generalfeedbackformat',
2466                  'defaultmark',
2467                  'penalty',
2468                  'qtype',
2469                  'length',
2470                  'stamp',
2471                  'timecreated',
2472                  'timemodified',
2473                  'createdby',
2474                  'modifiedby',
2475              ]);
2476  
2477          // Attach qtype plugin structure to $question element, only one allowed.
2478          $this->add_plugin_structure('qtype', $question, false);
2479  
2480          // Attach qbank plugin stucture to $question element, multiple allowed.
2481          $this->add_plugin_structure('qbank', $question, true);
2482  
2483          // attach local plugin stucture to $question element, multiple allowed
2484          $this->add_plugin_structure('local', $question, true);
2485  
2486          $qhints = new backup_nested_element('question_hints');
2487  
2488          $qhint = new backup_nested_element('question_hint', ['id'],
2489              [
2490                  'hint',
2491                  'hintformat',
2492                  'shownumcorrect',
2493                  'clearwrong',
2494                  'options',
2495              ]);
2496  
2497          $tags = new backup_nested_element('tags');
2498  
2499          $tag = new backup_nested_element('tag', ['id', 'contextid'], ['name', 'rawname']);
2500  
2501          // Build the initial tree.
2502          $qcategories->add_child($qcategory);
2503          $qcategory->add_child($questionbankentries);
2504          $questionbankentries->add_child($questionbankentry);
2505          $questionbankentry->add_child($questionversions);
2506          $questionversions->add_child($questionverion);
2507          $questionverion->add_child($questions);
2508          $questions->add_child($question);
2509          $question->add_child($qhints);
2510          $qhints->add_child($qhint);
2511  
2512          // Add question tags.
2513          $question->add_child($tags);
2514          $tags->add_child($tag);
2515  
2516          $qcategory->set_source_sql("
2517              SELECT gc.*,
2518                     contextlevel,
2519                     instanceid AS contextinstanceid
2520                FROM {question_categories} gc
2521                JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
2522                JOIN {context} co ON co.id = gc.contextid
2523               WHERE bi.backupid = ?
2524                 AND bi.itemname = 'question_categoryfinal'", [backup::VAR_BACKUPID]);
2525  
2526          $questionbankentry->set_source_table('question_bank_entries', ['questioncategoryid' => backup::VAR_PARENTID]);
2527  
2528          $questionverion->set_source_table('question_versions', ['questionbankentryid' => backup::VAR_PARENTID]);
2529  
2530          $question->set_source_sql('
2531                  SELECT q.*
2532                   FROM {question} q
2533                   JOIN {question_versions} qv ON qv.questionid = q.id
2534                   JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
2535                  WHERE qv.id = ?', [backup::VAR_PARENTID]);
2536  
2537          $qhint->set_source_sql('
2538                  SELECT *
2539                   FROM {question_hints}
2540                  WHERE questionid = :questionid
2541               ORDER BY id', ['questionid' => backup::VAR_PARENTID]);
2542  
2543          $tag->set_source_sql("SELECT t.id, ti.contextid, t.name, t.rawname
2544                                  FROM {tag} t
2545                                  JOIN {tag_instance} ti ON ti.tagid = t.id
2546                                 WHERE ti.itemid = ?
2547                                   AND ti.itemtype = 'question'
2548                                   AND ti.component = 'core_question'", [backup::VAR_PARENTID]);
2549  
2550          // Don't need to annotate ids nor files.
2551          // ...(already done by {@see backup_annotate_all_question_files()}.
2552  
2553          return $qcategories;
2554      }
2555  }
2556  
2557  
2558  
2559  /**
2560   * This step will generate all the file  annotations for the already
2561   * annotated (final) users. Need to do this here because each user
2562   * has its own context and structure tasks only are able to handle
2563   * one context. Also, this step will guarantee that every user has
2564   * its context created (req for other steps)
2565   */
2566  class backup_annotate_all_user_files extends backup_execution_step {
2567  
2568      protected function define_execution() {
2569          global $DB;
2570  
2571          // List of fileareas we are going to annotate
2572          $fileareas = array('profile', 'icon');
2573  
2574          // Fetch all annotated (final) users
2575          $rs = $DB->get_recordset('backup_ids_temp', array(
2576              'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
2577          $progress = $this->task->get_progress();
2578          $progress->start_progress($this->get_name());
2579          foreach ($rs as $record) {
2580              $userid = $record->itemid;
2581              $userctx = context_user::instance($userid, IGNORE_MISSING);
2582              if (!$userctx) {
2583                  continue; // User has not context, sure it's a deleted user, so cannot have files
2584              }
2585              // Proceed with every user filearea
2586              foreach ($fileareas as $filearea) {
2587                  // We don't need to specify itemid ($userid - 5th param) as far as by
2588                  // context we can get all the associated files. See MDL-22092
2589                  backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null);
2590                  $progress->progress();
2591              }
2592          }
2593          $progress->end_progress();
2594          $rs->close();
2595      }
2596  }
2597  
2598  
2599  /**
2600   * Defines the backup step for advanced grading methods attached to the activity module
2601   */
2602  class backup_activity_grading_structure_step extends backup_structure_step {
2603  
2604      /**
2605       * Include the grading.xml only if the module supports advanced grading
2606       */
2607      protected function execute_condition() {
2608  
2609          // No grades on the front page.
2610          if ($this->get_courseid() == SITEID) {
2611              return false;
2612          }
2613  
2614          return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false);
2615      }
2616  
2617      /**
2618       * Declares the gradable areas structures and data sources
2619       */
2620      protected function define_structure() {
2621  
2622          // To know if we are including userinfo
2623          $userinfo = $this->get_setting_value('userinfo');
2624  
2625          // Define the elements
2626  
2627          $areas = new backup_nested_element('areas');
2628  
2629          $area = new backup_nested_element('area', array('id'), array(
2630              'areaname', 'activemethod'));
2631  
2632          $definitions = new backup_nested_element('definitions');
2633  
2634          $definition = new backup_nested_element('definition', array('id'), array(
2635              'method', 'name', 'description', 'descriptionformat', 'status',
2636              'timecreated', 'timemodified', 'options'));
2637  
2638          $instances = new backup_nested_element('instances');
2639  
2640          $instance = new backup_nested_element('instance', array('id'), array(
2641              'raterid', 'itemid', 'rawgrade', 'status', 'feedback',
2642              'feedbackformat', 'timemodified'));
2643  
2644          // Build the tree including the method specific structures
2645          // (beware - the order of how gradingform plugins structures are attached is important)
2646          $areas->add_child($area);
2647          // attach local plugin stucture to $area element, multiple allowed
2648          $this->add_plugin_structure('local', $area, true);
2649          $area->add_child($definitions);
2650          $definitions->add_child($definition);
2651          $this->add_plugin_structure('gradingform', $definition, true);
2652          // attach local plugin stucture to $definition element, multiple allowed
2653          $this->add_plugin_structure('local', $definition, true);
2654          $definition->add_child($instances);
2655          $instances->add_child($instance);
2656          $this->add_plugin_structure('gradingform', $instance, false);
2657          // attach local plugin stucture to $instance element, multiple allowed
2658          $this->add_plugin_structure('local', $instance, true);
2659  
2660          // Define data sources
2661  
2662          $area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID,
2663              'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename())));
2664  
2665          $definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID));
2666  
2667          if ($userinfo) {
2668              $instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID));
2669          }
2670  
2671          // Annotate references
2672          $definition->annotate_files('grading', 'description', 'id');
2673          $instance->annotate_ids('user', 'raterid');
2674  
2675          // Return the root element
2676          return $areas;
2677      }
2678  }
2679  
2680  
2681  /**
2682   * structure step in charge of constructing the grades.xml file for all the grade items
2683   * and letters related to one activity
2684   */
2685  class backup_activity_grades_structure_step extends backup_structure_step {
2686  
2687      /**
2688       * No grades on the front page.
2689       * @return bool
2690       */
2691      protected function execute_condition() {
2692          return ($this->get_courseid() != SITEID);
2693      }
2694  
2695      protected function define_structure() {
2696          global $CFG;
2697  
2698          require_once($CFG->libdir . '/grade/constants.php');
2699  
2700          // To know if we are including userinfo
2701          $userinfo = $this->get_setting_value('userinfo');
2702  
2703          // Define each element separated
2704  
2705          $book = new backup_nested_element('activity_gradebook');
2706  
2707          $items = new backup_nested_element('grade_items');
2708  
2709          $item = new backup_nested_element('grade_item', array('id'), array(
2710              'categoryid', 'itemname', 'itemtype', 'itemmodule',
2711              'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
2712              'calculation', 'gradetype', 'grademax', 'grademin',
2713              'scaleid', 'outcomeid', 'gradepass', 'multfactor',
2714              'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
2715              'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
2716              'needsupdate', 'timecreated', 'timemodified'));
2717  
2718          $grades = new backup_nested_element('grade_grades');
2719  
2720          $grade = new backup_nested_element('grade_grade', array('id'), array(
2721              'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
2722              'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
2723              'locked', 'locktime', 'exported', 'overridden',
2724              'excluded', 'feedback', 'feedbackformat', 'information',
2725              'informationformat', 'timecreated', 'timemodified',
2726              'aggregationstatus', 'aggregationweight'));
2727  
2728          $letters = new backup_nested_element('grade_letters');
2729  
2730          $letter = new backup_nested_element('grade_letter', 'id', array(
2731              'lowerboundary', 'letter'));
2732  
2733          // Build the tree
2734  
2735          $book->add_child($items);
2736          $items->add_child($item);
2737  
2738          $item->add_child($grades);
2739          $grades->add_child($grade);
2740  
2741          $book->add_child($letters);
2742          $letters->add_child($letter);
2743  
2744          // Define sources
2745  
2746          $item->set_source_sql("SELECT gi.*
2747                                 FROM {grade_items} gi
2748                                 JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
2749                                 WHERE bi.backupid = ?
2750                                 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2751  
2752          // This only happens if we are including user info
2753          if ($userinfo) {
2754              $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
2755              $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, 'id');
2756          }
2757  
2758          $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
2759  
2760          // Annotations
2761  
2762          $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
2763          $item->annotate_ids('outcome', 'outcomeid');
2764  
2765          $grade->annotate_ids('user', 'userid');
2766          $grade->annotate_ids('user', 'usermodified');
2767  
2768          // Return the root element (book)
2769  
2770          return $book;
2771      }
2772  }
2773  
2774  /**
2775   * Structure step in charge of constructing the grade history of an activity.
2776   *
2777   * This step is added to the task regardless of the setting 'grade_histories'.
2778   * The reason is to allow for a more flexible step in case the logic needs to be
2779   * split accross different settings to control the history of items and/or grades.
2780   */
2781  class backup_activity_grade_history_structure_step extends backup_structure_step {
2782  
2783      /**
2784       * No grades on the front page.
2785       * @return bool
2786       */
2787      protected function execute_condition() {
2788          return ($this->get_courseid() != SITEID);
2789      }
2790  
2791      protected function define_structure() {
2792          global $CFG;
2793  
2794          require_once($CFG->libdir . '/grade/constants.php');
2795  
2796          // Settings to use.
2797          $userinfo = $this->get_setting_value('userinfo');
2798          $history = $this->get_setting_value('grade_histories');
2799  
2800          // Create the nested elements.
2801          $bookhistory = new backup_nested_element('grade_history');
2802          $grades = new backup_nested_element('grade_grades');
2803          $grade = new backup_nested_element('grade_grade', array('id'), array(
2804              'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
2805              'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
2806              'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
2807              'excluded', 'feedback', 'feedbackformat', 'information',
2808              'informationformat', 'timemodified'));
2809  
2810          // Build the tree.
2811          $bookhistory->add_child($grades);
2812          $grades->add_child($grade);
2813  
2814          // This only happens if we are including user info and history.
2815          if ($userinfo && $history) {
2816              // Define sources. Only select the history related to existing activity items.
2817              $grade->set_source_sql("SELECT ggh.*
2818                                       FROM {grade_grades_history} ggh
2819                                       JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid
2820                                      WHERE bi.backupid = ?
2821                                        AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2822              $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA, 'id');
2823          }
2824  
2825          // Annotations.
2826          $grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0.
2827          $grade->annotate_ids('user', 'loggeduser');
2828          $grade->annotate_ids('user', 'userid');
2829          $grade->annotate_ids('user', 'usermodified');
2830  
2831          // Return the root element.
2832          return $bookhistory;
2833      }
2834  }
2835  
2836  /**
2837   * Backups up the course completion information for the course.
2838   */
2839  class backup_course_completion_structure_step extends backup_structure_step {
2840  
2841      protected function execute_condition() {
2842  
2843          // No completion on front page.
2844          if ($this->get_courseid() == SITEID) {
2845              return false;
2846          }
2847  
2848          // Check that all activities have been included
2849          if ($this->task->is_excluding_activities()) {
2850              return false;
2851          }
2852          return true;
2853      }
2854  
2855      /**
2856       * The structure of the course completion backup
2857       *
2858       * @return backup_nested_element
2859       */
2860      protected function define_structure() {
2861  
2862          // To know if we are including user completion info
2863          $userinfo = $this->get_setting_value('userscompletion');
2864  
2865          $cc = new backup_nested_element('course_completion');
2866  
2867          $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
2868              'course', 'criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod',
2869              'timeend', 'gradepass', 'role', 'roleshortname'
2870          ));
2871  
2872          $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
2873  
2874          $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
2875              'criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted'
2876          ));
2877  
2878          $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
2879              'userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate'
2880          ));
2881  
2882          $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
2883              'course','criteriatype','method','value'
2884          ));
2885  
2886          $cc->add_child($criteria);
2887              $criteria->add_child($criteriacompletions);
2888                  $criteriacompletions->add_child($criteriacomplete);
2889          $cc->add_child($coursecompletions);
2890          $cc->add_child($aggregatemethod);
2891  
2892          // We need some extra data for the restore.
2893          // - courseinstances shortname rather than an ID.
2894          // - roleshortname in case restoring on a different site.
2895          $sourcesql = "SELECT ccc.*, c.shortname AS courseinstanceshortname, r.shortname AS roleshortname
2896                          FROM {course_completion_criteria} ccc
2897                     LEFT JOIN {course} c ON c.id = ccc.courseinstance
2898                     LEFT JOIN {role} r ON r.id = ccc.role
2899                         WHERE ccc.course = ?";
2900          $criteria->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
2901  
2902          $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
2903  
2904          if ($userinfo) {
2905              $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
2906              $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
2907          }
2908  
2909          $criteria->annotate_ids('role', 'role');
2910          $criteriacomplete->annotate_ids('user', 'userid');
2911          $coursecompletions->annotate_ids('user', 'userid');
2912  
2913          return $cc;
2914  
2915      }
2916  }
2917  
2918  /**
2919   * Backup completion defaults for each module type.
2920   *
2921   * @package     core_backup
2922   * @copyright   2017 Marina Glancy
2923   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2924   */
2925  class backup_completion_defaults_structure_step extends backup_structure_step {
2926  
2927      /**
2928       * To conditionally decide if one step will be executed or no
2929       */
2930      protected function execute_condition() {
2931          // No completion on front page.
2932          if ($this->get_courseid() == SITEID) {
2933              return false;
2934          }
2935          return true;
2936      }
2937  
2938      /**
2939       * The structure of the course completion backup
2940       *
2941       * @return backup_nested_element
2942       */
2943      protected function define_structure() {
2944  
2945          $cc = new backup_nested_element('course_completion_defaults');
2946  
2947          $defaults = new backup_nested_element('course_completion_default', array('id'), array(
2948              'modulename', 'completion', 'completionview', 'completionusegrade', 'completionpassgrade',
2949              'completionexpected', 'customrules'
2950          ));
2951  
2952          // Use module name instead of module id so we can insert into another site later.
2953          $sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade,
2954                    d.completionpassgrade, d.completionexpected, d.customrules
2955                  FROM {course_completion_defaults} d join {modules} m on d.module = m.id
2956                  WHERE d.course = ?";
2957          $defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
2958  
2959          $cc->add_child($defaults);
2960          return $cc;
2961  
2962      }
2963  }
2964  
2965  /**
2966   * Structure step in charge of constructing the contentbank.xml file for all the contents found in a given context
2967   */
2968  class backup_contentbankcontent_structure_step extends backup_structure_step {
2969  
2970      /**
2971       * Define structure for content bank step
2972       */
2973      protected function define_structure() {
2974  
2975          // Define each element separated.
2976          $contents = new backup_nested_element('contents');
2977          $content = new backup_nested_element('content', ['id'], [
2978              'name', 'contenttype', 'instanceid', 'configdata', 'usercreated', 'usermodified', 'timecreated', 'timemodified']);
2979  
2980          // Build the tree.
2981          $contents->add_child($content);
2982  
2983          // Define sources.
2984          $content->set_source_table('contentbank_content', ['contextid' => backup::VAR_CONTEXTID]);
2985  
2986          // Define annotations.
2987          $content->annotate_ids('user', 'usercreated');
2988          $content->annotate_ids('user', 'usermodified');
2989          $content->annotate_files('contentbank', 'public', 'id');
2990  
2991          // Return the root element (contents).
2992          return $contents;
2993      }
2994  }