Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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