Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  /**
  17   * Contains the import_backup_helper class.
  18   *
  19   * @package tool_moodlenet
  20   * @copyright 2020 Adrian Greeve <adrian@moodle.com>
  21   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  namespace tool_moodlenet\local;
  24  
  25  /**
  26   * The import_backup_helper class.
  27   *
  28   * The import_backup_helper objects provide a means to prepare a backup for for restoration of a course or activity backup file.
  29   *
  30   * @copyright 2020 Adrian Greeve <adrian@moodle.com>
  31   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32   */
  33  class import_backup_helper {
  34  
  35      /** @var remote_resource $remoteresource A file resource to be restored. */
  36      protected $remoteresource;
  37  
  38      /** @var user $user The user trying to restore a file. */
  39      protected $user;
  40  
  41      /** @var context $context The context we are trying to restore this file into. */
  42      protected $context;
  43  
  44      /** @var int $useruploadlimit The size limit that this user can upload in this context. */
  45      protected $useruploadlimit;
  46  
  47      /**
  48       * Constructor for the import backup helper.
  49       *
  50       * @param remote_resource $remoteresource A remote file resource
  51       * @param \stdClass       $user           The user importing a file.
  52       * @param \context        $context        Context to restore into.
  53       */
  54      public function __construct(remote_resource $remoteresource, \stdClass $user, \context $context) {
  55          $this->remoteresource = $remoteresource;
  56          $this->user = $user;
  57          $this->context = $context;
  58  
  59          $maxbytes = 0;
  60          if ($this->context->contextlevel == CONTEXT_COURSE) {
  61              $course = get_course($this->context->instanceid);
  62              $maxbytes = $course->maxbytes;
  63          }
  64          $this->useruploadlimit = get_user_max_upload_file_size($this->context, get_config('core', 'maxbytes'),
  65                  $maxbytes, 0, $this->user);
  66      }
  67  
  68      /**
  69       * Return a stored user draft file for processing.
  70       *
  71       * @return \stored_file The imported file to ultimately be restored.
  72       */
  73      public function get_stored_file(): \stored_file {
  74  
  75          // Check if the user can upload a backup to this context.
  76          require_capability('moodle/restore:uploadfile', $this->context, $this->user->id);
  77  
  78          // Before starting a potentially lengthy download, try to ensure the file size does not exceed the upload size restrictions
  79          // for the user. This is a time saving measure.
  80          // This is a naive check, that serves only to catch files if they provide the content length header.
  81          // Because of potential content encoding (compression), the stored file will be checked again after download as well.
  82          $size = $this->remoteresource->get_download_size() ?? -1;
  83          if ($this->size_exceeds_upload_limit($size)) {
  84              throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $size,
  85                  'uploadlimit' => $this->useruploadlimit]);
  86          }
  87  
  88          [$filepath, $filename] = $this->remoteresource->download_to_requestdir();
  89          \core\antivirus\manager::scan_file($filepath, $filename, true);
  90  
  91          // Check the final size of file against the user upload limits.
  92          $localsize = filesize(sprintf('%s/%s', $filepath, $filename));
  93          if ($this->size_exceeds_upload_limit($localsize)) {
  94              throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $localsize,
  95                  'uploadlimit' => $this->useruploadlimit]);
  96          }
  97  
  98          return $this->create_user_draft_stored_file($filename, $filepath);
  99      }
 100  
 101      /**
 102       * Does the size exceed the upload limit for the current import, taking into account user and core settings.
 103       *
 104       * @param int $sizeinbytes
 105       * @return bool true if exceeded, false otherwise.
 106       */
 107      protected function size_exceeds_upload_limit(int $sizeinbytes): bool {
 108          $maxbytes = 0;
 109          if ($this->context->contextlevel == CONTEXT_COURSE) {
 110              $course = get_course($this->context->instanceid);
 111              $maxbytes = $course->maxbytes;
 112          }
 113          $maxbytes = get_user_max_upload_file_size($this->context, get_config('core', 'maxbytes'), $maxbytes, 0,
 114              $this->user);
 115          if ($maxbytes != USER_CAN_IGNORE_FILE_SIZE_LIMITS && $sizeinbytes > $maxbytes) {
 116              return true;
 117          }
 118          return false;
 119      }
 120  
 121      /**
 122       * Create a file in the user drafts ready for use by plugins implementing dndupload_handle().
 123       *
 124       * @param string $filename the name of the file on disk
 125       * @param string $path the path where the file is stored on disk
 126       * @return \stored_file
 127       */
 128      protected function create_user_draft_stored_file(string $filename, string $path): \stored_file {
 129          global $CFG;
 130  
 131          $record = new \stdClass();
 132          $record->filearea = 'draft';
 133          $record->component = 'user';
 134          $record->filepath = '/';
 135          $record->itemid   = file_get_unused_draft_itemid();
 136          $record->license  = $CFG->sitedefaultlicense;
 137          $record->author   = '';
 138          $record->filename = clean_param($filename, PARAM_FILE);
 139          $record->contextid = \context_user::instance($this->user->id)->id;
 140          $record->userid = $this->user->id;
 141  
 142          $fullpathwithname = sprintf('%s/%s', $path, $filename);
 143  
 144          $fs = get_file_storage();
 145  
 146          return  $fs->create_file_from_pathname($record, $fullpathwithname);
 147      }
 148  
 149      /**
 150       * Looks for a context that this user has permission to upload backup files to.
 151       * This gets a list of roles that the user has, checks for the restore:uploadfile capability and then sends back a context
 152       * that has this permission if available.
 153       *
 154       * This starts with the highest context level and moves down i.e. system -> category -> course.
 155       *
 156       * @param  int $userid The user ID that we are looking for a working context for.
 157       * @return \context A context that allows the upload of backup files.
 158       */
 159      public static function get_context_for_user(int $userid): ?\context {
 160          global $DB;
 161  
 162          if (is_siteadmin()) {
 163              return \context_system::instance();
 164          }
 165  
 166          $sql = "SELECT ctx.id, ctx.contextlevel, ctx.instanceid, ctx.path, ctx.depth, ctx.locked
 167                    FROM {context} ctx
 168                    JOIN {role_assignments} r ON ctx.id = r.contextid
 169                   WHERE r.userid = :userid AND ctx.contextlevel IN (:contextsystem, :contextcategory, :contextcourse)
 170                ORDER BY ctx.contextlevel ASC";
 171  
 172          $params = [
 173              'userid' => $userid,
 174              'contextsystem' => CONTEXT_SYSTEM,
 175              'contextcategory' => CONTEXT_COURSECAT,
 176              'contextcourse' => CONTEXT_COURSE
 177          ];
 178          $records = $DB->get_records_sql($sql, $params);
 179          foreach ($records as $record) {
 180              \context_helper::preload_from_record($record);
 181              if ($record->contextlevel == CONTEXT_COURSECAT) {
 182                  $context = \context_coursecat::instance($record->instanceid);
 183              } else if ($record->contextlevel == CONTEXT_COURSE) {
 184                  $context = \context_course::instance($record->instanceid);
 185              } else {
 186                  $context = \context_system::instance();
 187              }
 188              if (has_capability('moodle/restore:uploadfile', $context, $userid)) {
 189                  return $context;
 190              }
 191          }
 192          return null;
 193      }
 194  }