Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * @package    moodlecore
  20   * @subpackage backup-dbops
  21   * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  /**
  26   * Non instantiable helper class providing DB support to the @backup_controller
  27   *
  28   * This class contains various static methods available for all the DB operations
  29   * performed by the backup_controller class
  30   *
  31   * TODO: Finish phpdocs
  32   */
  33  abstract class backup_controller_dbops extends backup_dbops {
  34  
  35      /**
  36       * @var string Backup id for cached backup_includes_files result.
  37       */
  38      protected static $includesfilescachebackupid;
  39  
  40      /**
  41       * @var int Cached backup_includes_files result
  42       */
  43      protected static $includesfilescache;
  44  
  45      /**
  46       * Send one backup controller to DB
  47       *
  48       * @param backup_controller $controller controller to send to DB
  49       * @param string $checksum hash of the controller to be checked
  50       * @param bool $includeobj to decide if the object itself must be updated (true) or no (false)
  51       * @param bool $cleanobj to decide if the object itself must be cleaned (true) or no (false)
  52       * @return int id of the controller record in the DB
  53       * @throws backup_controller_exception|backup_dbops_exception
  54       */
  55      public static function save_controller($controller, $checksum, $includeobj = true, $cleanobj = false) {
  56          global $DB;
  57          // Check we are going to save one backup_controller
  58          if (! $controller instanceof backup_controller) {
  59              throw new backup_controller_exception('backup_controller_expected');
  60          }
  61          // Check checksum is ok. Only if we are including object info. Sounds silly but it isn't ;-).
  62          if ($includeobj and !$controller->is_checksum_correct($checksum)) {
  63              throw new backup_dbops_exception('backup_controller_dbops_saving_checksum_mismatch');
  64          }
  65          // Cannot request to $includeobj and $cleanobj at the same time.
  66          if ($includeobj and $cleanobj) {
  67              throw new backup_dbops_exception('backup_controller_dbops_saving_cannot_include_and_delete');
  68          }
  69          // Get all the columns
  70          $rec = new stdclass();
  71          $rec->backupid     = $controller->get_backupid();
  72          $rec->operation    = $controller->get_operation();
  73          $rec->type         = $controller->get_type();
  74          $rec->itemid       = $controller->get_id();
  75          $rec->format       = $controller->get_format();
  76          $rec->interactive  = $controller->get_interactive();
  77          $rec->purpose      = $controller->get_mode();
  78          $rec->userid       = $controller->get_userid();
  79          $rec->status       = $controller->get_status();
  80          $rec->execution    = $controller->get_execution();
  81          $rec->executiontime= $controller->get_executiontime();
  82          $rec->checksum     = $checksum;
  83          // Serialize information
  84          if ($includeobj) {
  85              $rec->controller = base64_encode(serialize($controller));
  86          } else if ($cleanobj) {
  87              $rec->controller = '';
  88          }
  89          // Send it to DB
  90          if ($recexists = $DB->get_record('backup_controllers', array('backupid' => $rec->backupid))) {
  91              $rec->id = $recexists->id;
  92              $rec->timemodified = time();
  93              $DB->update_record('backup_controllers', $rec);
  94          } else {
  95              $rec->timecreated = time();
  96              $rec->timemodified = 0;
  97              $rec->id = $DB->insert_record('backup_controllers', $rec);
  98          }
  99          return $rec->id;
 100      }
 101  
 102      public static function load_controller($backupid) {
 103          global $DB;
 104          if (! $controllerrec = $DB->get_record('backup_controllers', array('backupid' => $backupid))) {
 105              throw new backup_dbops_exception('backup_controller_dbops_nonexisting');
 106          }
 107          $controller = unserialize(base64_decode($controllerrec->controller));
 108          if (!is_object($controller)) {
 109              // The controller field of the table did not contain a serialized object.
 110              // It is made empty after it has been used successfully, it is likely that
 111              // the user has pressed the browser back button at some point.
 112              throw new backup_dbops_exception('backup_controller_dbops_loading_invalid_controller');
 113          }
 114          // Check checksum is ok. Sounds silly but it isn't ;-)
 115          if (!$controller->is_checksum_correct($controllerrec->checksum)) {
 116              throw new backup_dbops_exception('backup_controller_dbops_loading_checksum_mismatch');
 117          }
 118          return $controller;
 119      }
 120  
 121      public static function create_backup_ids_temp_table($backupid) {
 122          global $CFG, $DB;
 123          $dbman = $DB->get_manager(); // We are going to use database_manager services
 124  
 125          $xmldb_table = new xmldb_table('backup_ids_temp');
 126          $xmldb_table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 127          // Set default backupid (not needed but this enforce any missing backupid). That's hackery in action!
 128          $xmldb_table->add_field('backupid', XMLDB_TYPE_CHAR, 32, null, XMLDB_NOTNULL, null, $backupid);
 129          $xmldb_table->add_field('itemname', XMLDB_TYPE_CHAR, 160, null, XMLDB_NOTNULL, null, null);
 130          $xmldb_table->add_field('itemid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
 131          $xmldb_table->add_field('newitemid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, '0');
 132          $xmldb_table->add_field('parentitemid', XMLDB_TYPE_INTEGER, 10, null, null, null, null);
 133          $xmldb_table->add_field('info', XMLDB_TYPE_TEXT, null, null, null, null, null);
 134          $xmldb_table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 135          $xmldb_table->add_key('backupid_itemname_itemid_uk', XMLDB_KEY_UNIQUE, array('backupid','itemname','itemid'));
 136          $xmldb_table->add_index('backupid_parentitemid_ix', XMLDB_INDEX_NOTUNIQUE, array('backupid','itemname','parentitemid'));
 137          $xmldb_table->add_index('backupid_itemname_newitemid_ix', XMLDB_INDEX_NOTUNIQUE, array('backupid','itemname','newitemid'));
 138  
 139          $dbman->create_temp_table($xmldb_table); // And create it
 140  
 141      }
 142  
 143      public static function create_backup_files_temp_table($backupid) {
 144          global $CFG, $DB;
 145          $dbman = $DB->get_manager(); // We are going to use database_manager services
 146  
 147          $xmldb_table = new xmldb_table('backup_files_temp');
 148          $xmldb_table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 149          // Set default backupid (not needed but this enforce any missing backupid). That's hackery in action!
 150          $xmldb_table->add_field('backupid', XMLDB_TYPE_CHAR, 32, null, XMLDB_NOTNULL, null, $backupid);
 151          $xmldb_table->add_field('contextid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
 152          $xmldb_table->add_field('component', XMLDB_TYPE_CHAR, 100, null, XMLDB_NOTNULL, null, null);
 153          $xmldb_table->add_field('filearea', XMLDB_TYPE_CHAR, 50, null, XMLDB_NOTNULL, null, null);
 154          $xmldb_table->add_field('itemid', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null);
 155          $xmldb_table->add_field('info', XMLDB_TYPE_TEXT, null, null, null, null, null);
 156          $xmldb_table->add_field('newcontextid', XMLDB_TYPE_INTEGER, 10, null, null, null, null);
 157          $xmldb_table->add_field('newitemid', XMLDB_TYPE_INTEGER, 10, null, null, null, null);
 158          $xmldb_table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 159          $xmldb_table->add_index('backupid_contextid_component_filearea_itemid_ix', XMLDB_INDEX_NOTUNIQUE, array('backupid','contextid','component','filearea','itemid'));
 160  
 161          $dbman->create_temp_table($xmldb_table); // And create it
 162      }
 163  
 164      public static function drop_backup_ids_temp_table($backupid) {
 165          global $DB;
 166          $dbman = $DB->get_manager(); // We are going to use database_manager services
 167  
 168          $targettablename = 'backup_ids_temp';
 169          if ($dbman->table_exists($targettablename)) {
 170              $table = new xmldb_table($targettablename);
 171              $dbman->drop_table($table); // And drop it
 172          }
 173      }
 174  
 175      /**
 176       * Decode the info field from backup_ids_temp or backup_files_temp.
 177       *
 178       * @param mixed $info The info field data to decode, may be an object or a simple integer.
 179       * @return mixed The decoded information.  For simple types it returns, for complex ones we decode.
 180       */
 181      public static function decode_backup_temp_info($info) {
 182          // We encode all data except null.
 183          if ($info != null) {
 184              return unserialize(gzuncompress(base64_decode($info)));
 185          }
 186          return $info;
 187      }
 188  
 189      /**
 190       * Encode the info field for backup_ids_temp or backup_files_temp.
 191       *
 192       * @param mixed $info string The info field data to encode.
 193       * @return string An encoded string of data or null if the input is null.
 194       */
 195      public static function encode_backup_temp_info($info) {
 196          // We encode if there is any information to keep the translations simpler.
 197          if ($info != null) {
 198              // We compress if possible. It reduces db, network and memory storage. The saving is greater than CPU compression cost.
 199              // Compression level 1 is chosen has it produces good compression with the smallest possible overhead, see MDL-40618.
 200              return base64_encode(gzcompress(serialize($info), 1));
 201          }
 202          return $info;
 203      }
 204  
 205      /**
 206       * Given one type and id from controller, return the corresponding courseid
 207       */
 208      public static function get_courseid_from_type_id($type, $id) {
 209          global $DB;
 210          if ($type == backup::TYPE_1COURSE) {
 211              return $id; // id is the course id
 212  
 213          } else if ($type == backup::TYPE_1SECTION) {
 214              if (! $courseid = $DB->get_field('course_sections', 'course', array('id' => $id))) {
 215                  throw new backup_dbops_exception('course_not_found_for_section', $id);
 216              }
 217              return $courseid;
 218          } else if ($type == backup::TYPE_1ACTIVITY) {
 219              if (! $courseid = $DB->get_field('course_modules', 'course', array('id' => $id))) {
 220                  throw new backup_dbops_exception('course_not_found_for_moduleid', $id);
 221              }
 222              return $courseid;
 223          }
 224      }
 225  
 226      /**
 227       * Given one activity task, return the activity information and related settings
 228       * Used by get_moodle_backup_information()
 229       */
 230      private static function get_activity_backup_information($task) {
 231  
 232          $contentinfo = array(
 233              'moduleid'   => $task->get_moduleid(),
 234              'sectionid'  => $task->get_sectionid(),
 235              'modulename' => $task->get_modulename(),
 236              'title'      => $task->get_name(),
 237              'directory'  => 'activities/' . $task->get_modulename() . '_' . $task->get_moduleid());
 238  
 239          // Now get activity settings
 240          // Calculate prefix to find valid settings
 241          $prefix = basename($contentinfo['directory']);
 242          $settingsinfo = array();
 243          foreach ($task->get_settings() as $setting) {
 244              // Discard ones without valid prefix
 245              if (strpos($setting->get_name(), $prefix) !== 0) {
 246                  continue;
 247              }
 248              // Validate level is correct (activity)
 249              if ($setting->get_level() != backup_setting::ACTIVITY_LEVEL) {
 250                  throw new backup_controller_exception('setting_not_activity_level', $setting);
 251              }
 252              $settinginfo = array(
 253                  'level'    => 'activity',
 254                  'activity' => $prefix,
 255                  'name'     => $setting->get_name(),
 256                  'value'    => $setting->get_value());
 257              $settingsinfo[$setting->get_name()] = (object)$settinginfo;
 258          }
 259          return array($contentinfo, $settingsinfo);
 260      }
 261  
 262      /**
 263       * Given one section task, return the section information and related settings
 264       * Used by get_moodle_backup_information()
 265       */
 266      private static function get_section_backup_information($task) {
 267  
 268          $contentinfo = array(
 269              'sectionid'  => $task->get_sectionid(),
 270              'title'      => $task->get_name(),
 271              'directory'  => 'sections/' . 'section_' . $task->get_sectionid());
 272  
 273          // Now get section settings
 274          // Calculate prefix to find valid settings
 275          $prefix = basename($contentinfo['directory']);
 276          $settingsinfo = array();
 277          foreach ($task->get_settings() as $setting) {
 278              // Discard ones without valid prefix
 279              if (strpos($setting->get_name(), $prefix) !== 0) {
 280                  continue;
 281              }
 282              // Validate level is correct (section)
 283              if ($setting->get_level() != backup_setting::SECTION_LEVEL) {
 284                  throw new backup_controller_exception('setting_not_section_level', $setting);
 285              }
 286              $settinginfo = array(
 287                  'level'    => 'section',
 288                  'section'  => $prefix,
 289                  'name'     => $setting->get_name(),
 290                  'value'    => $setting->get_value());
 291              $settingsinfo[$setting->get_name()] = (object)$settinginfo;
 292          }
 293          return array($contentinfo, $settingsinfo);
 294      }
 295  
 296      /**
 297       * Given one course task, return the course information and related settings
 298       * Used by get_moodle_backup_information()
 299       */
 300      private static function get_course_backup_information($task) {
 301  
 302          $contentinfo = array(
 303              'courseid'   => $task->get_courseid(),
 304              'title'      => $task->get_name(),
 305              'directory'  => 'course');
 306  
 307          // Now get course settings
 308          // Calculate prefix to find valid settings
 309          $prefix = basename($contentinfo['directory']);
 310          $settingsinfo = array();
 311          foreach ($task->get_settings() as $setting) {
 312              // Discard ones without valid prefix
 313              if (strpos($setting->get_name(), $prefix) !== 0) {
 314                  continue;
 315              }
 316              // Validate level is correct (course)
 317              if ($setting->get_level() != backup_setting::COURSE_LEVEL) {
 318                  throw new backup_controller_exception('setting_not_course_level', $setting);
 319              }
 320              $settinginfo = array(
 321                  'level'    => 'course',
 322                  'name'     => $setting->get_name(),
 323                  'value'    => $setting->get_value());
 324              $settingsinfo[$setting->get_name()] = (object)$settinginfo;
 325          }
 326          return array($contentinfo, $settingsinfo);
 327      }
 328  
 329      /**
 330       * Given one root task, return the course information and related settings
 331       * Used by get_moodle_backup_information()
 332       */
 333      private static function get_root_backup_information($task) {
 334  
 335          // Now get root settings
 336          $settingsinfo = array();
 337          foreach ($task->get_settings() as $setting) {
 338              // Validate level is correct (root)
 339              if ($setting->get_level() != backup_setting::ROOT_LEVEL) {
 340                  throw new backup_controller_exception('setting_not_root_level', $setting);
 341              }
 342              $settinginfo = array(
 343                  'level'    => 'root',
 344                  'name'     => $setting->get_name(),
 345                  'value'    => $setting->get_value());
 346              $settingsinfo[$setting->get_name()] = (object)$settinginfo;
 347          }
 348          return array(null, $settingsinfo);
 349      }
 350  
 351      /**
 352       * Get details information for main moodle_backup.xml file, extracting it from
 353       * the specified controller.
 354       *
 355       * If you specify the progress monitor, this will start a new progress section
 356       * to track progress in processing (in case this task takes a long time).
 357       *
 358       * @param string $backupid Backup ID
 359       * @param \core\progress\base $progress Optional progress monitor
 360       */
 361      public static function get_moodle_backup_information($backupid,
 362              \core\progress\base $progress = null) {
 363  
 364          // Start tracking progress if required (for load_controller).
 365          if ($progress) {
 366              $progress->start_progress('get_moodle_backup_information', 2);
 367          }
 368  
 369          $detailsinfo = array(); // Information details
 370          $contentsinfo= array(); // Information about backup contents
 371          $settingsinfo= array(); // Information about backup settings
 372          $bc = self::load_controller($backupid); // Load controller
 373  
 374          // Note that we have loaded controller.
 375          if ($progress) {
 376              $progress->progress(1);
 377          }
 378  
 379          // Details info
 380          $detailsinfo['id'] = $bc->get_id();
 381          $detailsinfo['backup_id'] = $bc->get_backupid();
 382          $detailsinfo['type'] = $bc->get_type();
 383          $detailsinfo['format'] = $bc->get_format();
 384          $detailsinfo['interactive'] = $bc->get_interactive();
 385          $detailsinfo['mode'] = $bc->get_mode();
 386          $detailsinfo['execution'] = $bc->get_execution();
 387          $detailsinfo['executiontime'] = $bc->get_executiontime();
 388          $detailsinfo['userid'] = $bc->get_userid();
 389          $detailsinfo['courseid'] = $bc->get_courseid();
 390  
 391  
 392          // Init content placeholders
 393          $contentsinfo['activities'] = array();
 394          $contentsinfo['sections']   = array();
 395          $contentsinfo['course']     = array();
 396  
 397          // Get tasks and start nested progress.
 398          $tasks = $bc->get_plan()->get_tasks();
 399          if ($progress) {
 400              $progress->start_progress('get_moodle_backup_information', count($tasks));
 401              $done = 1;
 402          }
 403  
 404          // Contents info (extract information from tasks)
 405          foreach ($tasks as $task) {
 406  
 407              if ($task instanceof backup_activity_task) { // Activity task
 408  
 409                  if ($task->get_setting_value('included')) { // Only return info about included activities
 410                      list($contentinfo, $settings) = self::get_activity_backup_information($task);
 411                      $contentsinfo['activities'][] = $contentinfo;
 412                      $settingsinfo = array_merge($settingsinfo, $settings);
 413                  }
 414  
 415              } else if ($task instanceof backup_section_task) { // Section task
 416  
 417                  if ($task->get_setting_value('included')) { // Only return info about included sections
 418                      list($contentinfo, $settings) = self::get_section_backup_information($task);
 419                      $contentsinfo['sections'][] = $contentinfo;
 420                      $settingsinfo = array_merge($settingsinfo, $settings);
 421                  }
 422  
 423              } else if ($task instanceof backup_course_task) { // Course task
 424  
 425                  list($contentinfo, $settings) = self::get_course_backup_information($task);
 426                  $contentsinfo['course'][] = $contentinfo;
 427                  $settingsinfo = array_merge($settingsinfo, $settings);
 428  
 429              } else if ($task instanceof backup_root_task) { // Root task
 430  
 431                  list($contentinfo, $settings) = self::get_root_backup_information($task);
 432                  $settingsinfo = array_merge($settingsinfo, $settings);
 433              }
 434  
 435              // Report task handled.
 436              if ($progress) {
 437                  $progress->progress($done++);
 438              }
 439          }
 440  
 441          $bc->destroy(); // Always need to destroy controller to handle circular references
 442  
 443          // Finish progress reporting.
 444          if ($progress) {
 445              $progress->end_progress();
 446              $progress->end_progress();
 447          }
 448  
 449          return array(array((object)$detailsinfo), $contentsinfo, $settingsinfo);
 450      }
 451  
 452      /**
 453       * Update CFG->backup_version and CFG->backup_release if change in
 454       * version is detected.
 455       */
 456      public static function apply_version_and_release() {
 457          global $CFG;
 458  
 459          if ($CFG->backup_version < backup::VERSION) {
 460              set_config('backup_version', backup::VERSION);
 461              set_config('backup_release', backup::RELEASE);
 462          }
 463      }
 464  
 465      /**
 466       * Given the backupid, detect if the backup includes "mnet" remote users or no
 467       */
 468      public static function backup_includes_mnet_remote_users($backupid) {
 469          global $CFG, $DB;
 470  
 471          $sql = "SELECT COUNT(*)
 472                    FROM {backup_ids_temp} b
 473                    JOIN {user} u ON u.id = b.itemid
 474                   WHERE b.backupid = ?
 475                     AND b.itemname = 'userfinal'
 476                     AND u.mnethostid != ?";
 477          $count = $DB->count_records_sql($sql, array($backupid, $CFG->mnet_localhost_id));
 478          return (int)(bool)$count;
 479      }
 480  
 481      /**
 482       * Given the backupid, determine whether this backup should include
 483       * files from the moodle file storage system.
 484       *
 485       * @param string $backupid The ID of the backup.
 486       * @return int Indicates whether files should be included in backups.
 487       */
 488      public static function backup_includes_files($backupid) {
 489          // This function is called repeatedly in a backup with many files.
 490          // Loading the controller is a nontrivial operation (in a large test
 491          // backup it took 0.3 seconds), so we do a temporary cache of it within
 492          // this request.
 493          if (self::$includesfilescachebackupid === $backupid) {
 494              return self::$includesfilescache;
 495          }
 496  
 497          // Load controller, get value, then destroy controller and return result.
 498          self::$includesfilescachebackupid = $backupid;
 499          $bc = self::load_controller($backupid);
 500          self::$includesfilescache = $bc->get_include_files();
 501          $bc->destroy();
 502          return self::$includesfilescache;
 503      }
 504  
 505      /**
 506       * Given the backupid, detect if the backup contains references to external contents
 507       *
 508       * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
 509       * @return int
 510       */
 511      public static function backup_includes_file_references($backupid) {
 512          global $CFG, $DB;
 513  
 514          $sql = "SELECT count(r.repositoryid)
 515                    FROM {files} f
 516                    LEFT JOIN {files_reference} r
 517                         ON r.id = f.referencefileid
 518                    JOIN {backup_ids_temp} bi
 519                         ON f.id = bi.itemid
 520                   WHERE bi.backupid = ?
 521                         AND bi.itemname = 'filefinal'";
 522          $count = $DB->count_records_sql($sql, array($backupid));
 523          return (int)(bool)$count;
 524      }
 525  
 526      /**
 527       * Given the courseid, return some course related information we want to transport
 528       *
 529       * @param int $course the id of the course this backup belongs to
 530       */
 531      public static function backup_get_original_course_info($courseid) {
 532          global $DB;
 533          return $DB->get_record('course', array('id' => $courseid), 'fullname, shortname, startdate, enddate, format');
 534      }
 535  
 536      /**
 537       * Sets the default values for the settings in a backup operation
 538       *
 539       * Based on the mode of the backup it will load proper defaults
 540       * using {@link apply_admin_config_defaults}.
 541       *
 542       * @param backup_controller $controller
 543       */
 544      public static function apply_config_defaults(backup_controller $controller) {
 545          // Based on the mode of the backup (general, automated, import, hub...)
 546          // decide the action to perform to get defaults loaded
 547          $mode = $controller->get_mode();
 548  
 549          switch ($mode) {
 550              case backup::MODE_GENERAL:
 551              case backup::MODE_ASYNC:
 552                  // Load the general defaults
 553                  $settings = array(
 554                          'backup_general_users'              => 'users',
 555                          'backup_general_anonymize'          => 'anonymize',
 556                          'backup_general_role_assignments'   => 'role_assignments',
 557                          'backup_general_activities'         => 'activities',
 558                          'backup_general_blocks'             => 'blocks',
 559                          'backup_general_filters'            => 'filters',
 560                          'backup_general_comments'           => 'comments',
 561                          'backup_general_badges'             => 'badges',
 562                          'backup_general_calendarevents'     => 'calendarevents',
 563                          'backup_general_userscompletion'    => 'userscompletion',
 564                          'backup_general_logs'               => 'logs',
 565                          'backup_general_histories'          => 'grade_histories',
 566                          'backup_general_questionbank'       => 'questionbank',
 567                          'backup_general_groups'             => 'groups',
 568                          'backup_general_competencies'       => 'competencies',
 569                          'backup_general_contentbankcontent' => 'contentbankcontent',
 570                          'backup_general_legacyfiles'        => 'legacyfiles'
 571                  );
 572                  self::apply_admin_config_defaults($controller, $settings, true);
 573                  break;
 574              case backup::MODE_IMPORT:
 575                  // Load the import defaults.
 576                  $settings = array(
 577                          'backup_import_activities'         => 'activities',
 578                          'backup_import_blocks'             => 'blocks',
 579                          'backup_import_filters'            => 'filters',
 580                          'backup_import_calendarevents'     => 'calendarevents',
 581                          'backup_import_permissions'        => 'permissions',
 582                          'backup_import_questionbank'       => 'questionbank',
 583                          'backup_import_groups'             => 'groups',
 584                          'backup_import_competencies'       => 'competencies',
 585                          'backup_import_contentbankcontent' => 'contentbankcontent',
 586                          'backup_import_legacyfiles'        => 'legacyfiles'
 587                  );
 588                  self::apply_admin_config_defaults($controller, $settings, true);
 589                  if ((!$controller->get_interactive()) &&
 590                          $controller->get_type() == backup::TYPE_1ACTIVITY) {
 591                      // This is duplicate - there is no concept of defaults - these settings must be on.
 592                      $settings = array(
 593                           'activities',
 594                           'blocks',
 595                           'filters',
 596                           'questionbank'
 597                      );
 598                      self::force_enable_settings($controller, $settings);
 599                  }
 600                  break;
 601              case backup::MODE_AUTOMATED:
 602                  // Load the automated defaults.
 603                  $settings = array(
 604                          'backup_auto_users'              => 'users',
 605                          'backup_auto_role_assignments'   => 'role_assignments',
 606                          'backup_auto_activities'         => 'activities',
 607                          'backup_auto_blocks'             => 'blocks',
 608                          'backup_auto_filters'            => 'filters',
 609                          'backup_auto_comments'           => 'comments',
 610                          'backup_auto_badges'             => 'badges',
 611                          'backup_auto_calendarevents'     => 'calendarevents',
 612                          'backup_auto_userscompletion'    => 'userscompletion',
 613                          'backup_auto_logs'               => 'logs',
 614                          'backup_auto_histories'          => 'grade_histories',
 615                          'backup_auto_questionbank'       => 'questionbank',
 616                          'backup_auto_groups'             => 'groups',
 617                          'backup_auto_competencies'       => 'competencies',
 618                          'backup_auto_contentbankcontent' => 'contentbankcontent',
 619                          'backup_auto_legacyfiles'        => 'legacyfiles'
 620                  );
 621                  self::apply_admin_config_defaults($controller, $settings, false);
 622                  break;
 623              default:
 624                  // Nothing to do for other modes (HUB...). Some day we
 625                  // can define defaults (admin UI...) for them if we want to
 626          }
 627      }
 628  
 629      /**
 630       * Turn these settings on. No defaults from admin settings.
 631       *
 632       * @param backup_controller $controller
 633       * @param array $settings a map from admin config names to setting names (Config name => Setting name)
 634       */
 635      private static function force_enable_settings(backup_controller $controller, array $settings) {
 636          $plan = $controller->get_plan();
 637          foreach ($settings as $config => $settingname) {
 638              $value = true;
 639              if ($plan->setting_exists($settingname)) {
 640                  $setting = $plan->get_setting($settingname);
 641                  // We do not allow this setting to be locked for a duplicate function.
 642                  if ($setting->get_status() !== base_setting::NOT_LOCKED) {
 643                      $setting->set_status(base_setting::NOT_LOCKED);
 644                  }
 645                  $setting->set_value($value);
 646                  $setting->set_status(base_setting::LOCKED_BY_CONFIG);
 647              } else {
 648                  $controller->log('Unknown setting: ' . $setting, BACKUP::LOG_DEBUG);
 649              }
 650          }
 651      }
 652  
 653      /**
 654       * Sets the controller settings default values from the admin config.
 655       *
 656       * @param backup_controller $controller
 657       * @param array $settings a map from admin config names to setting names (Config name => Setting name)
 658       * @param boolean $uselocks whether "locked" admin settings should be honoured
 659       */
 660      private static function apply_admin_config_defaults(backup_controller $controller, array $settings, $uselocks) {
 661          $plan = $controller->get_plan();
 662          foreach ($settings as $config=>$settingname) {
 663              $value = get_config('backup', $config);
 664              if ($value === false) {
 665                  // Ignore this because the config has not been set. get_config
 666                  // returns false if a setting doesn't exist, '0' is returned when
 667                  // the configuration is set to false.
 668                  $controller->log('Could not find a value for the config ' . $config, BACKUP::LOG_DEBUG);
 669                  continue;
 670              }
 671              $locked = $uselocks && (get_config('backup', $config.'_locked') == true);
 672              if ($plan->setting_exists($settingname)) {
 673                  $setting = $plan->get_setting($settingname);
 674                  // We can only update the setting if it isn't already locked by config or permission.
 675                  if ($setting->get_status() !== base_setting::LOCKED_BY_CONFIG
 676                          && $setting->get_status() !== base_setting::LOCKED_BY_PERMISSION) {
 677                      $setting->set_value($value);
 678                      if ($locked) {
 679                          $setting->set_status(base_setting::LOCKED_BY_CONFIG);
 680                      }
 681                  }
 682              } else {
 683                  $controller->log('Unknown setting: ' . $setting, BACKUP::LOG_DEBUG);
 684              }
 685          }
 686      }
 687  
 688      /**
 689       * Get the progress details of a backup operation.
 690       * Get backup records directly from database, if the backup has successfully completed
 691       * there will be no controller object to load.
 692       *
 693       * @param string $backupid The backup id to query.
 694       * @return array $progress The backup progress details.
 695       */
 696      public static function get_progress($backupid) {
 697          global $DB;
 698  
 699          $progress = array();
 700          $backuprecord = $DB->get_record(
 701              'backup_controllers',
 702              array('backupid' => $backupid),
 703              'status, progress, operation',
 704              MUST_EXIST);
 705  
 706          $status = $backuprecord->status;
 707          $progress = $backuprecord->progress;
 708          $operation = $backuprecord->operation;
 709  
 710          $progress = array('status' => $status, 'progress' => $progress, 'backupid' => $backupid, 'operation' => $operation);
 711  
 712          return $progress;
 713      }
 714  }