Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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