Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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

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