MDL-53467 repositories: Upgrade s3 to v0.5.1
[moodle.git] / repository / s3 / lib.php
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.
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/>.
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');
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 }
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 {
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);
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     }
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     }
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         }
112         $list = array();
113         $list['list'] = array();
114         $list['path'] = array(
115             array('name' => get_string('pluginname', 'repository_s3'), 'path' => '')
116         );
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;
128         $tree = array();
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->pix_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);
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) {
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                 }
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                 }
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->pix_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->pix_url(file_extension_icon($title, 90))->out(false)
201                     );
202                 }
203             }
204             $tree = array_merge($folders, $files);
205         }
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         }
222         $list['list'] = $tree;
224         return $list;
225     }
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     }
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     }
261     /**
262      * S3 doesn't require login
263      *
264      * @return bool
265      */
266     public function check_login() {
267         return true;
268     }
270     /**
271      * S3 doesn't provide search
272      *
273      * @return bool
274      */
275     public function global_search() {
276         return false;
277     }
279     public static function get_type_option_names() {
280         return array('access_key', 'secret_key', 'endpoint', 'pluginname');
281     }
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     }
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     }
318     /**
319      * Is this repository accessing private data?
320      *
321      * @return bool
322      */
323     public function contains_private_data() {
324         return false;
325     }