Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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-helper
  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   * Base abstract class for all the helper classes providing various operations
  27   *
  28   * TODO: Finish phpdocs
  29   */
  30  abstract class backup_helper {
  31  
  32      /**
  33       * Given one backupid, create all the needed dirs to have one backup temp dir available
  34       */
  35      static public function check_and_create_backup_dir($backupid) {
  36          $backupiddir = make_backup_temp_directory($backupid, false);
  37          if (empty($backupiddir)) {
  38              throw new backup_helper_exception('cannot_create_backup_temp_dir');
  39          }
  40      }
  41  
  42      /**
  43       * Given one backupid, ensure its temp dir is completely empty
  44       *
  45       * If supplied, progress object should be ready to receive indeterminate
  46       * progress reports.
  47       *
  48       * @param string $backupid Backup id
  49       * @param \core\progress\base $progress Optional progress reporting object
  50       */
  51      static public function clear_backup_dir($backupid, \core\progress\base $progress = null) {
  52          $backupiddir = make_backup_temp_directory($backupid, false);
  53          if (!self::delete_dir_contents($backupiddir, '', $progress)) {
  54              throw new backup_helper_exception('cannot_empty_backup_temp_dir');
  55          }
  56          return true;
  57      }
  58  
  59      /**
  60       * Given one backupid, delete completely its temp dir
  61       *
  62       * If supplied, progress object should be ready to receive indeterminate
  63       * progress reports.
  64       *
  65       * @param string $backupid Backup id
  66       * @param \core\progress\base $progress Optional progress reporting object
  67       */
  68       static public function delete_backup_dir($backupid, \core\progress\base $progress = null) {
  69           $backupiddir = make_backup_temp_directory($backupid, false);
  70           self::clear_backup_dir($backupid, $progress);
  71           return rmdir($backupiddir);
  72       }
  73  
  74       /**
  75       * Given one fullpath to directory, delete its contents recursively
  76       * Copied originally from somewhere in the net.
  77       * TODO: Modernise this
  78       *
  79       * If supplied, progress object should be ready to receive indeterminate
  80       * progress reports.
  81       *
  82       * @param string $dir Directory to delete
  83       * @param string $excludedir Exclude this directory
  84       * @param \core\progress\base $progress Optional progress reporting object
  85       */
  86      static public function delete_dir_contents($dir, $excludeddir='', \core\progress\base $progress = null) {
  87          global $CFG;
  88  
  89          if ($progress) {
  90              $progress->progress();
  91          }
  92  
  93          if (!is_dir($dir)) {
  94              // if we've been given a directory that doesn't exist yet, return true.
  95              // this happens when we're trying to clear out a course that has only just
  96              // been created.
  97              return true;
  98          }
  99          $slash = "/";
 100  
 101          // Create arrays to store files and directories
 102          $dir_files      = array();
 103          $dir_subdirs    = array();
 104  
 105          // Make sure we can delete it
 106          chmod($dir, $CFG->directorypermissions);
 107  
 108          if ((($handle = opendir($dir))) == false) {
 109              // The directory could not be opened
 110              return false;
 111          }
 112  
 113          // Loop through all directory entries, and construct two temporary arrays containing files and sub directories
 114          while (false !== ($entry = readdir($handle))) {
 115              if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != "." && $entry != $excludeddir) {
 116                  $dir_subdirs[] = $dir. $slash .$entry;
 117  
 118              } else if ($entry != ".." && $entry != "." && $entry != $excludeddir) {
 119                  $dir_files[] = $dir. $slash .$entry;
 120              }
 121          }
 122  
 123          // Delete all files in the curent directory return false and halt if a file cannot be removed
 124          for ($i=0; $i<count($dir_files); $i++) {
 125              chmod($dir_files[$i], $CFG->directorypermissions);
 126              if (((unlink($dir_files[$i]))) == false) {
 127                  return false;
 128              }
 129          }
 130  
 131          // Empty sub directories and then remove the directory
 132          for ($i=0; $i<count($dir_subdirs); $i++) {
 133              chmod($dir_subdirs[$i], $CFG->directorypermissions);
 134              if (self::delete_dir_contents($dir_subdirs[$i], '', $progress) == false) {
 135                  return false;
 136              } else {
 137                  if (remove_dir($dir_subdirs[$i]) == false) {
 138                      return false;
 139                  }
 140              }
 141          }
 142  
 143          // Close directory
 144          closedir($handle);
 145  
 146          // Success, every thing is gone return true
 147          return true;
 148      }
 149  
 150      /**
 151       * Delete all the temp dirs older than the time specified.
 152       *
 153       * If supplied, progress object should be ready to receive indeterminate
 154       * progress reports.
 155       *
 156       * @param int $deletefrom Time to delete from
 157       * @param \core\progress\base $progress Optional progress reporting object
 158       */
 159      static public function delete_old_backup_dirs($deletefrom, \core\progress\base $progress = null) {
 160          $status = true;
 161          // Get files and directories in the backup temp dir without descend.
 162          $backuptempdir = make_backup_temp_directory('');
 163          $list = get_directory_list($backuptempdir, '', false, true, true);
 164          foreach ($list as $file) {
 165              $file_path = $backuptempdir . '/' . $file;
 166              $moddate = filemtime($file_path);
 167              if ($status && $moddate < $deletefrom) {
 168                  //If directory, recurse
 169                  if (is_dir($file_path)) {
 170                      // $file is really the backupid
 171                      $status = self::delete_backup_dir($file, $progress);
 172                  //If file
 173                  } else {
 174                      unlink($file_path);
 175                  }
 176              }
 177          }
 178          if (!$status) {
 179              throw new backup_helper_exception('problem_deleting_old_backup_temp_dirs');
 180          }
 181      }
 182  
 183      /**
 184       * This function will be invoked by any log() method in backup/restore, acting
 185       * as a simple forwarder to the standard loggers but also, if the $display
 186       * parameter is true, supporting translation via get_string() and sending to
 187       * standard output.
 188       */
 189      static public function log($message, $level, $a, $depth, $display, $logger) {
 190          // Send to standard loggers
 191          $logmessage = $message;
 192          $options = empty($depth) ? array() : array('depth' => $depth);
 193          if (!empty($a)) {
 194              $logmessage = $logmessage . ' ' . implode(', ', (array)$a);
 195          }
 196          $logger->process($logmessage, $level, $options);
 197  
 198          // If $display specified, send translated string to output_controller
 199          if ($display) {
 200              output_controller::get_instance()->output($message, 'backup', $a, $depth);
 201          }
 202      }
 203  
 204      /**
 205       * Given one backupid and the (FS) final generated file, perform its final storage
 206       * into Moodle file storage. For stored files it returns the complete file_info object
 207       *
 208       * Note: the $filepath is deleted if the backup file is created successfully
 209       *
 210       * If you specify the progress monitor, this will start a new progress section
 211       * to track progress in processing (in case this task takes a long time).
 212       *
 213       * @param int $backupid
 214       * @param string $filepath zip file containing the backup
 215       * @param \core\progress\base $progress Optional progress monitor
 216       * @return stored_file if created, null otherwise
 217       *
 218       * @throws moodle_exception in case of any problems
 219       */
 220      static public function store_backup_file($backupid, $filepath, \core\progress\base $progress = null) {
 221          global $CFG;
 222  
 223          // First of all, get some information from the backup_controller to help us decide
 224          list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
 225                  $backupid, $progress);
 226  
 227          // Extract useful information to decide
 228          $hasusers  = (bool)$sinfo['users']->value;     // Backup has users
 229          $isannon   = (bool)$sinfo['anonymize']->value; // Backup is anonymised
 230          $filename  = $sinfo['filename']->value;        // Backup filename
 231          $backupmode= $dinfo[0]->mode;                  // Backup mode backup::MODE_GENERAL/IMPORT/HUB
 232          $backuptype= $dinfo[0]->type;                  // Backup type backup::TYPE_1ACTIVITY/SECTION/COURSE
 233          $userid    = $dinfo[0]->userid;                // User->id executing the backup
 234          $id        = $dinfo[0]->id;                    // Id of activity/section/course (depends of type)
 235          $courseid  = $dinfo[0]->courseid;              // Id of the course
 236          $format    = $dinfo[0]->format;                // Type of backup file
 237  
 238          // Quick hack. If for any reason, filename is blank, fix it here.
 239          // TODO: This hack will be out once MDL-22142 - P26 gets fixed
 240          if (empty($filename)) {
 241              $filename = backup_plan_dbops::get_default_backup_filename('moodle2', $backuptype, $id, $hasusers, $isannon);
 242          }
 243  
 244          // Backups of type IMPORT aren't stored ever
 245          if ($backupmode == backup::MODE_IMPORT) {
 246              return null;
 247          }
 248  
 249          if (!is_readable($filepath)) {
 250              // we have a problem if zip file does not exist
 251              throw new coding_exception('backup_helper::store_backup_file() expects valid $filepath parameter');
 252  
 253          }
 254  
 255          // Calculate file storage options of id being backup
 256          $ctxid     = 0;
 257          $filearea  = '';
 258          $component = '';
 259          $itemid    = 0;
 260          switch ($backuptype) {
 261              case backup::TYPE_1ACTIVITY:
 262                  $ctxid     = context_module::instance($id)->id;
 263                  $component = 'backup';
 264                  $filearea  = 'activity';
 265                  $itemid    = 0;
 266                  break;
 267              case backup::TYPE_1SECTION:
 268                  $ctxid     = context_course::instance($courseid)->id;
 269                  $component = 'backup';
 270                  $filearea  = 'section';
 271                  $itemid    = $id;
 272                  break;
 273              case backup::TYPE_1COURSE:
 274                  $ctxid     = context_course::instance($courseid)->id;
 275                  $component = 'backup';
 276                  $filearea  = 'course';
 277                  $itemid    = 0;
 278                  break;
 279          }
 280  
 281          if ($backupmode == backup::MODE_AUTOMATED) {
 282              // Automated backups have there own special area!
 283              $filearea  = 'automated';
 284  
 285              // If we're keeping the backup only in a chosen path, just move it there now
 286              // this saves copying from filepool to here later and filling trashdir.
 287              $config = get_config('backup');
 288              $dir = $config->backup_auto_destination;
 289              if ($config->backup_auto_storage == 1 and $dir and is_dir($dir) and is_writable($dir)) {
 290                  $filedest = $dir.'/'
 291                          .backup_plan_dbops::get_default_backup_filename(
 292                                  $format,
 293                                  $backuptype,
 294                                  $courseid,
 295                                  $hasusers,
 296                                  $isannon,
 297                                  !$config->backup_shortname,
 298                                  (bool)$config->backup_auto_files);
 299                  // first try to move the file, if it is not possible copy and delete instead
 300                  if (@rename($filepath, $filedest)) {
 301                      return null;
 302                  }
 303                  umask($CFG->umaskpermissions);
 304                  if (copy($filepath, $filedest)) {
 305                      @chmod($filedest, $CFG->filepermissions); // may fail because the permissions may not make sense outside of dataroot
 306                      unlink($filepath);
 307                      return null;
 308                  } else {
 309                      $bc = backup_controller::load_controller($backupid);
 310                      $bc->log('Attempt to copy backup file to the specified directory using filesystem failed - ',
 311                              backup::LOG_WARNING, $dir);
 312                      $bc->destroy();
 313                  }
 314                  // bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
 315              }
 316          }
 317  
 318          // Backups of type HUB (by definition never have user info)
 319          // are sent to user's "user_tohub" file area. The upload process
 320          // will be responsible for cleaning that filearea once finished
 321          if ($backupmode == backup::MODE_HUB) {
 322              $ctxid     = context_user::instance($userid)->id;
 323              $component = 'user';
 324              $filearea  = 'tohub';
 325              $itemid    = 0;
 326          }
 327  
 328          // Backups without user info or with the anonymise functionality
 329          // enabled are sent to user's "user_backup"
 330          // file area. Maintenance of such area is responsibility of
 331          // the user via corresponding file manager frontend
 332          if ($backupmode == backup::MODE_GENERAL && (!$hasusers || $isannon)) {
 333              $ctxid     = context_user::instance($userid)->id;
 334              $component = 'user';
 335              $filearea  = 'backup';
 336              $itemid    = 0;
 337          }
 338  
 339          // Let's send the file to file storage, everything already defined
 340          $fs = get_file_storage();
 341          $fr = array(
 342              'contextid'   => $ctxid,
 343              'component'   => $component,
 344              'filearea'    => $filearea,
 345              'itemid'      => $itemid,
 346              'filepath'    => '/',
 347              'filename'    => $filename,
 348              'userid'      => $userid,
 349              'timecreated' => time(),
 350              'timemodified'=> time());
 351          // If file already exists, delete if before
 352          // creating it again. This is BC behaviour - copy()
 353          // overwrites by default
 354          if ($fs->file_exists($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename'])) {
 355              $pathnamehash = $fs->get_pathname_hash($fr['contextid'], $fr['component'], $fr['filearea'], $fr['itemid'], $fr['filepath'], $fr['filename']);
 356              $sf = $fs->get_file_by_hash($pathnamehash);
 357              $sf->delete();
 358          }
 359          $file = $fs->create_file_from_pathname($fr, $filepath);
 360          unlink($filepath);
 361          return $file;
 362      }
 363  
 364      /**
 365       * This function simply marks one param to be considered as straight sql
 366       * param, so it won't be searched in the structure tree nor converted at
 367       * all. Useful for better integration of definition of sources in structure
 368       * and DB stuff
 369       */
 370      public static function is_sqlparam($value) {
 371          return array('sqlparam' => $value);
 372      }
 373  
 374      /**
 375       * This function returns one array of itemnames that are being handled by
 376       * inforef.xml files. Used both by backup and restore
 377       */
 378      public static function get_inforef_itemnames() {
 379          return array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item', 'question_category');
 380      }
 381  }
 382  
 383  /*
 384   * Exception class used by all the @helper stuff
 385   */
 386  class backup_helper_exception extends backup_exception {
 387  
 388      public function __construct($errorcode, $a=NULL, $debuginfo=null) {
 389          parent::__construct($errorcode, $a, $debuginfo);
 390      }
 391  }