Merge branch 'MDL-81457-main' of https://github.com/andrewnicols/moodle
[moodle.git] / repository / s3 / lib.php
blob10c7b01d00e82642c2977972b14d79545e95f9a1
1 <?php
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.
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/>.
18 /**
19 * This plugin is used to access s3 files
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
26 require_once($CFG->dirroot . '/repository/lib.php');
27 require_once($CFG->dirroot . '/repository/s3/S3.php');
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);
34 /**
35 * This is a repository class used to browse Amazon S3 content.
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
42 class repository_s3 extends repository {
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;
53 /**
54 * Constructor
55 * @param int $repositoryid
56 * @param object $context
57 * @param array $options
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';
68 $this->s = new S3($this->access_key, $this->secret_key, false, $this->endpoint);
69 $this->s->setExceptions(true);
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;
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;
85 if (!empty($CFG->proxytype) && $CFG->proxytype == 'SOCKS5') {
86 $proxytype = CURLPROXY_SOCKS5;
88 $this->s->setProxy($proxyhost, $proxyuser, $proxypass, $proxytype);
92 /**
93 * Extracts the Bucket and URI from the path
95 * @param string $path path in this format 'bucket/path/to/folder/and/file'
96 * @return array including bucket and uri
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 = '';
106 return array($bucket, $uri);
110 * Get S3 file list
112 * @param string $path
113 * @return array The file list and options
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');
121 $list = array();
122 $list['list'] = array();
123 $list['path'] = array(
124 array('name' => get_string('pluginname', 'repository_s3'), 'path' => '')
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;
137 $tree = array();
139 if (empty($path)) {
140 try {
141 $buckets = $this->s->listBuckets();
142 } catch (S3Exception $e) {
143 throw new moodle_exception(
144 'errorwhilecommunicatingwith',
145 'repository',
147 $this->get_name(),
148 $e->getMessage()
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
158 $tree[] = $folder;
160 } else {
161 $files = array();
162 $folders = array();
163 list($bucket, $uri) = $this->explode_path($path);
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',
172 $this->get_name(),
173 $e->getMessage()
176 foreach ($contents as $object) {
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'];
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;
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'],
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)
213 $tree = array_merge($folders, $files);
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);
226 } else {
227 $list['path'][] = array('name' => $path, 'path' => $path);
231 $list['list'] = $tree;
233 return $list;
237 * Download S3 files to moodle
239 * @param string $filepath
240 * @param string $file The file path in moodle
241 * @return array The local stored path
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',
253 $this->get_name(),
254 $e->getMessage()
257 return array('path' => $path);
261 * Return the source information
263 * @param stdClass $filepath
264 * @return string
266 public function get_file_source_info($filepath) {
267 return 'Amazon S3: ' . $filepath;
271 * S3 doesn't require login
273 * @return bool
275 public function check_login() {
276 return true;
280 * S3 doesn't provide search
282 * @return bool
284 public function global_search() {
285 return false;
288 public static function get_type_option_names() {
289 return array('access_key', 'secret_key', 'endpoint', 'pluginname');
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"
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');
319 * S3 plugins doesn't support return links of files
321 * @return int
323 public function supported_returntypes() {
324 return FILE_INTERNAL;
328 * Is this repository accessing private data?
330 * @return bool
332 public function contains_private_data() {
333 return false;