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 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   * This plugin is used to access s3 files
  20   *
  21   * @since Moodle 2.0
  22   * @package    repository_s3
  23   * @copyright  2010 Dongsheng Cai {@link http://dongsheng.org}
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  require_once($CFG->dirroot . '/repository/lib.php');
  27  require_once($CFG->dirroot . '/repository/s3/S3.php');
  28  
  29  // This constant is not defined in php 5.4. Set it to avoid errors.
  30  if (!defined('CURL_SSLVERSION_TLSv1')) {
  31      define('CURL_SSLVERSION_TLSv1', 1);
  32  }
  33  
  34  /**
  35   * This is a repository class used to browse Amazon S3 content.
  36   *
  37   * @since Moodle 2.0
  38   * @package    repository_s3
  39   * @copyright  2009 Dongsheng Cai {@link http://dongsheng.org}
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  class repository_s3 extends repository {
  43  
  44      /**
  45       * Constructor
  46       * @param int $repositoryid
  47       * @param object $context
  48       * @param array $options
  49       */
  50      public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
  51          global $CFG;
  52          parent::__construct($repositoryid, $context, $options);
  53          $this->access_key = get_config('s3', 'access_key');
  54          $this->secret_key = get_config('s3', 'secret_key');
  55          $this->endpoint = get_config('s3', 'endpoint');
  56          if ($this->endpoint === false) { // If no endpoint has been set, use the default.
  57              $this->endpoint = 's3.amazonaws.com';
  58          }
  59          $this->s = new S3($this->access_key, $this->secret_key, false, $this->endpoint);
  60          $this->s->setExceptions(true);
  61  
  62          // Port of curl::__construct().
  63          if (!empty($CFG->proxyhost)) {
  64              if (empty($CFG->proxyport)) {
  65                  $proxyhost = $CFG->proxyhost;
  66              } else {
  67                  $proxyhost = $CFG->proxyhost . ':' . $CFG->proxyport;
  68              }
  69              $proxytype = CURLPROXY_HTTP;
  70              $proxyuser = null;
  71              $proxypass = null;
  72              if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
  73                  $proxyuser = $CFG->proxyuser;
  74                  $proxypass = $CFG->proxypassword;
  75              }
  76              if (!empty($CFG->proxytype) && $CFG->proxytype == 'SOCKS5') {
  77                  $proxytype = CURLPROXY_SOCKS5;
  78              }
  79              $this->s->setProxy($proxyhost, $proxyuser, $proxypass, $proxytype);
  80          }
  81      }
  82  
  83      /**
  84       * Extracts the Bucket and URI from the path
  85       *
  86       * @param string $path path in this format 'bucket/path/to/folder/and/file'
  87       * @return array including bucket and uri
  88       */
  89      protected function explode_path($path) {
  90          $parts = explode('/', $path, 2);
  91          if (isset($parts[1]) && $parts[1] !== '') {
  92              list($bucket, $uri) = $parts;
  93          } else {
  94              $bucket = $parts[0];
  95              $uri = '';
  96          }
  97          return array($bucket, $uri);
  98      }
  99  
 100      /**
 101       * Get S3 file list
 102       *
 103       * @param string $path
 104       * @return array The file list and options
 105       */
 106      public function get_listing($path = '', $page = '') {
 107          global $CFG, $OUTPUT;
 108          if (empty($this->access_key)) {
 109              throw new moodle_exception('needaccesskey', 'repository_s3');
 110          }
 111  
 112          $list = array();
 113          $list['list'] = array();
 114          $list['path'] = array(
 115              array('name' => get_string('pluginname', 'repository_s3'), 'path' => '')
 116          );
 117  
 118          // the management interface url
 119          $list['manage'] = false;
 120          // dynamically loading
 121          $list['dynload'] = true;
 122          // the current path of this list.
 123          // set to true, the login link will be removed
 124          $list['nologin'] = true;
 125          // set to true, the search button will be removed
 126          $list['nosearch'] = true;
 127  
 128          $tree = array();
 129  
 130          if (empty($path)) {
 131              try {
 132                  $buckets = $this->s->listBuckets();
 133              } catch (S3Exception $e) {
 134                  throw new moodle_exception(
 135                      'errorwhilecommunicatingwith',
 136                      'repository',
 137                      '',
 138                      $this->get_name(),
 139                      $e->getMessage()
 140                  );
 141              }
 142              foreach ($buckets as $bucket) {
 143                  $folder = array(
 144                      'title' => $bucket,
 145                      'children' => array(),
 146                      'thumbnail' => $OUTPUT->image_url(file_folder_icon(90))->out(false),
 147                      'path' => $bucket
 148                      );
 149                  $tree[] = $folder;
 150              }
 151          } else {
 152              $files = array();
 153              $folders = array();
 154              list($bucket, $uri) = $this->explode_path($path);
 155  
 156              try {
 157                  $contents = $this->s->getBucket($bucket, $uri, null, null, '/', true);
 158              } catch (S3Exception $e) {
 159                  throw new moodle_exception(
 160                      'errorwhilecommunicatingwith',
 161                      'repository',
 162                      '',
 163                      $this->get_name(),
 164                      $e->getMessage()
 165                  );
 166              }
 167              foreach ($contents as $object) {
 168  
 169                  // If object has a prefix, it is a 'CommonPrefix', which we consider a folder
 170                  if (isset($object['prefix'])) {
 171                      $title = rtrim($object['prefix'], '/');
 172                  } else {
 173                      $title = $object['name'];
 174                  }
 175  
 176                  // Removes the prefix (folder path) from the title
 177                  if (strlen($uri) > 0) {
 178                      $title = substr($title, strlen($uri));
 179                      // Check if title is empty and not zero
 180                      if (empty($title) && !is_numeric($title)) {
 181                          // Amazon returns the prefix itself, we skip it
 182                          continue;
 183                      }
 184                  }
 185  
 186                  // This is a so-called CommonPrefix, we consider it as a folder
 187                  if (isset($object['prefix'])) {
 188                      $folders[] = array(
 189                          'title' => $title,
 190                          'children' => array(),
 191                          'thumbnail'=> $OUTPUT->image_url(file_folder_icon(90))->out(false),
 192                          'path' => $bucket . '/' . $object['prefix']
 193                      );
 194                  } else {
 195                      $files[] = array(
 196                          'title' => $title,
 197                          'size' => $object['size'],
 198                          'datemodified' => $object['time'],
 199                          'source' => $bucket . '/' . $object['name'],
 200                          'thumbnail' => $OUTPUT->image_url(file_extension_icon($title, 90))->out(false)
 201                      );
 202                  }
 203              }
 204              $tree = array_merge($folders, $files);
 205          }
 206  
 207          $trail = '';
 208          if (!empty($path)) {
 209              $parts = explode('/', $path);
 210              if (count($parts) > 1) {
 211                  foreach ($parts as $part) {
 212                      if (!empty($part)) {
 213                          $trail .= $part . '/';
 214                          $list['path'][] = array('name' => $part, 'path' => $trail);
 215                      }
 216                  }
 217              } else {
 218                  $list['path'][] = array('name' => $path, 'path' => $path);
 219              }
 220          }
 221  
 222          $list['list'] = $tree;
 223  
 224          return $list;
 225      }
 226  
 227      /**
 228       * Download S3 files to moodle
 229       *
 230       * @param string $filepath
 231       * @param string $file The file path in moodle
 232       * @return array The local stored path
 233       */
 234      public function get_file($filepath, $file = '') {
 235          list($bucket, $uri) = $this->explode_path($filepath);
 236          $path = $this->prepare_file($file);
 237          try {
 238              $this->s->getObject($bucket, $uri, $path);
 239          } catch (S3Exception $e) {
 240              throw new moodle_exception(
 241                  'errorwhilecommunicatingwith',
 242                  'repository',
 243                  '',
 244                  $this->get_name(),
 245                  $e->getMessage()
 246              );
 247          }
 248          return array('path' => $path);
 249      }
 250  
 251      /**
 252       * Return the source information
 253       *
 254       * @param stdClass $filepath
 255       * @return string
 256       */
 257      public function get_file_source_info($filepath) {
 258          return 'Amazon S3: ' . $filepath;
 259      }
 260  
 261      /**
 262       * S3 doesn't require login
 263       *
 264       * @return bool
 265       */
 266      public function check_login() {
 267          return true;
 268      }
 269  
 270      /**
 271       * S3 doesn't provide search
 272       *
 273       * @return bool
 274       */
 275      public function global_search() {
 276          return false;
 277      }
 278  
 279      public static function get_type_option_names() {
 280          return array('access_key', 'secret_key', 'endpoint', 'pluginname');
 281      }
 282  
 283      public static function type_config_form($mform, $classname = 'repository') {
 284          parent::type_config_form($mform);
 285          $strrequired = get_string('required');
 286          $endpointselect = array( // List of possible Amazon S3 Endpoints.
 287              "s3.amazonaws.com" => "s3.amazonaws.com",
 288              "s3-external-1.amazonaws.com" => "s3-external-1.amazonaws.com",
 289              "s3-us-west-2.amazonaws.com" => "s3-us-west-2.amazonaws.com",
 290              "s3-us-west-1.amazonaws.com" => "s3-us-west-1.amazonaws.com",
 291              "s3-eu-west-1.amazonaws.com" => "s3-eu-west-1.amazonaws.com",
 292              "s3.eu-central-1.amazonaws.com" => "s3.eu-central-1.amazonaws.com",
 293              "s3-eu-central-1.amazonaws.com" => "s3-eu-central-1.amazonaws.com",
 294              "s3-ap-southeast-1.amazonaws.com" => "s3-ap-southeast-1.amazonaws.com",
 295              "s3-ap-southeast-2.amazonaws.com" => "s3-ap-southeast-2.amazonaws.com",
 296              "s3-ap-northeast-1.amazonaws.com" => "s3-ap-northeast-1.amazonaws.com",
 297              "s3-sa-east-1.amazonaws.com" => "s3-sa-east-1.amazonaws.com"
 298          );
 299          $mform->addElement('text', 'access_key', get_string('access_key', 'repository_s3'));
 300          $mform->setType('access_key', PARAM_RAW_TRIMMED);
 301          $mform->addElement('text', 'secret_key', get_string('secret_key', 'repository_s3'));
 302          $mform->setType('secret_key', PARAM_RAW_TRIMMED);
 303          $mform->addElement('select', 'endpoint', get_string('endpoint', 'repository_s3'), $endpointselect);
 304          $mform->setDefault('endpoint', 's3.amazonaws.com'); // Default to US Endpoint.
 305          $mform->addRule('access_key', $strrequired, 'required', null, 'client');
 306          $mform->addRule('secret_key', $strrequired, 'required', null, 'client');
 307      }
 308  
 309      /**
 310       * S3 plugins doesn't support return links of files
 311       *
 312       * @return int
 313       */
 314      public function supported_returntypes() {
 315          return FILE_INTERNAL;
 316      }
 317  
 318      /**
 319       * Is this repository accessing private data?
 320       *
 321       * @return bool
 322       */
 323      public function contains_private_data() {
 324          return false;
 325      }
 326  }