Search moodle.org's
Developer Documentation

  • 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 37 and 311] [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  }