Merge branch 'wip-MDL-33997-master' of git://github.com/marinaglancy/moodle
[moodle.git] / repository / equella / lib.php
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/>.
17 /**
18  * This plugin is used to access equella repositories.
19  *
20  * @since 2.3
21  * @package    repository_equella
22  * @copyright  2012 Dongsheng Cai {@link http://dongsheng.org}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 require_once($CFG->dirroot . '/repository/lib.php');
30 /**
31  * repository_equella class implements equella_client
32  *
33  * @since 2.3
34  * @package    repository_equella
35  * @copyright  2012 Dongsheng Cai {@link http://dongsheng.org}
36  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class repository_equella extends repository {
39     /** @var array mimetype filter */
40     private $mimetypes = array();
42     /**
43      * Constructor
44      *
45      * @param int $repositoryid repository instance id
46      * @param int|stdClass $context a context id or context object
47      * @param array $options repository options
48      */
49     public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
50         parent::__construct($repositoryid, $context, $options);
52         if (isset($this->options['mimetypes'])) {
53             $mt = $this->options['mimetypes'];
54             if (!empty($mt) && is_array($mt) && !in_array('*', $mt)) {
55                 $this->mimetypes = array_unique(array_map(array($this, 'to_mime_type'), $mt));
56             }
57         }
58     }
60     /**
61      * Display embedded equella interface
62      *
63      * @param string $path
64      * @param mixed $page
65      * @return array
66      */
67     public function get_listing($path = null, $page = null) {
68         global $COURSE;
69         $callbackurl = new moodle_url('/repository/equella/callback.php', array('repo_id'=>$this->id));
71         $mimetypesstr = '';
72         $restrict = '';
73         if (!empty($this->mimetypes)) {
74             $mimetypesstr = '&mimeTypes=' . implode(',', $this->mimetypes);
75             // We're restricting to a mime type, so we always restrict to selecting resources only.
76             $restrict = '&attachmentonly=true';
77         } else if ($this->get_option('equella_select_restriction') != 'none') {
78             // The option value matches the EQUELLA paramter name.
79             $restrict = '&' . $this->get_option('equella_select_restriction') . '=true';
80         }
82         $url = $this->get_option('equella_url')
83                 . '?method=lms'
84                 . '&returnurl='.urlencode($callbackurl)
85                 . '&returnprefix=tle'
86                 . '&template=standard'
87                 . '&token='.urlencode($this->getssotoken('write'))
88                 . '&courseId='.urlencode($COURSE->idnumber)
89                 . '&courseCode='.urlencode($COURSE->shortname)
90                 . '&action=searchThin'
91                 . '&forcePost=true'
92                 . '&cancelDisabled=true'
93                 . '&attachmentUuidUrls=true'
94                 . '&options='.urlencode($this->get_option('equella_options') . $mimetypesstr)
95                 . $restrict;
96         $list = array();
97         $list['object'] = array();
98         $list['object']['type'] = 'text/html';
99         $list['object']['src'] = $url;
100         $list['nologin']  = true;
101         $list['nosearch'] = true;
102         $list['norefresh'] = true;
103         return $list;
104     }
106     /**
107      * Supported equella file types
108      *
109      * @return int
110      */
111     public function supported_returntypes() {
112         return FILE_REFERENCE;
113     }
115     /**
116      * Prepare file reference information
117      *
118      * @param string $source
119      * @return string file referece
120      */
121     public function get_file_reference($source) {
122         return base64_encode($source);
123     }
125     /**
126      * Download a file, this function can be overridden by subclass. {@link curl}
127      *
128      * @param string $url the url of file
129      * @param string $filename save location
130      * @return string the location of the file
131      */
132     public function get_file($url, $filename = '') {
133         global $USER;
134         $cookiename = uniqid('', true) . '.cookie';
135         $dir = make_temp_directory('repository/equella/' . $USER->id);
136         $cookiepathname = $dir . '/' . $cookiename;
137         $path = $this->prepare_file($filename);
138         $fp = fopen($path, 'w');
139         $c = new curl(array('cookie'=>$cookiepathname));
140         $c->download(array(array('url'=>$url, 'file'=>$fp)), array('CURLOPT_FOLLOWLOCATION'=>true));
141         // Close file handler.
142         fclose($fp);
143         // Delete cookie jar.
144         unlink($cookiepathname);
145         return array('path'=>$path, 'url'=>$url);
146     }
148     /**
149      * Returns information about file in this repository by reference
150      * {@link repository::get_file_reference()}
151      * {@link repository::get_file()}
152      *
153      * Returns null if file not found or can not be accessed
154      *
155      * @param stdClass $reference file reference db record
156      * @return null|stdClass containing attribute 'filepath'
157      */
158     public function get_file_by_reference($reference) {
159         $ref = base64_decode($reference->reference);
160         $url = $this->appendtoken($ref);
162         if (!$url) {
163             // Occurs when the user isn't known..
164             return null;
165         }
167         // We use this cache to get the correct file size.
168         $cachedfilepath = cache_file::get($url, array('ttl' => 0));
169         if ($cachedfilepath === false) {
170             // Cache the file.
171             $path = $this->get_file($url);
172             $cachedfilepath = cache_file::create_from_file($url, $path['path']);
173         }
175         if ($cachedfilepath && is_readable($cachedfilepath)) {
176             return (object)array('filepath' => $cachedfilepath);
177         }
178         return null;
179     }
181     /**
182      * Repository method to serve the referenced file
183      *
184      * @param stored_file $storedfile the file that contains the reference
185      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
186      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
187      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
188      * @param array $options additional options affecting the file serving
189      */
190     public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
191         $reference = base64_decode($stored_file->get_reference());
192         $url = $this->appendtoken($reference);
193         if ($url) {
194             header('Location: ' . $url);
195         } else {
196             send_file_not_found();
197         }
198     }
200     /**
201      * Add Instance settings input to Moodle form
202      *
203      * @param moodleform $mform
204      */
205     public static function instance_config_form($mform) {
206         $mform->addElement('text', 'equella_url', get_string('equellaurl', 'repository_equella'));
207         $mform->setType('equella_url', PARAM_URL);
209         $strrequired = get_string('required');
210         $mform->addRule('equella_url', $strrequired, 'required', null, 'client');
212         $mform->addElement('text', 'equella_options', get_string('equellaoptions', 'repository_equella'));
213         $mform->setType('equella_options', PARAM_NOTAGS);
215         $choices = array(
216             'none' => get_string('restrictionnone', 'repository_equella'),
217             'itemonly' => get_string('restrictionitemsonly', 'repository_equella'),
218             'attachmentonly' => get_string('restrictionattachmentsonly', 'repository_equella'),
219         );
220         $mform->addElement('select', 'equella_select_restriction', get_string('selectrestriction', 'repository_equella'), $choices);
222         $mform->addElement('header', '',
223             get_string('group', 'repository_equella', get_string('groupdefault', 'repository_equella')));
224         $mform->addElement('text', 'equella_shareid', get_string('sharedid', 'repository_equella'));
225         $mform->setType('equella_shareid', PARAM_RAW);
226         $mform->addRule('equella_shareid', $strrequired, 'required', null, 'client');
228         $mform->addElement('text', 'equella_sharedsecret', get_string('sharedsecrets', 'repository_equella'));
229         $mform->setType('equella_sharedsecret', PARAM_RAW);
230         $mform->addRule('equella_sharedsecret', $strrequired, 'required', null, 'client');
232         foreach (self::get_all_editing_roles() as $role) {
233             $mform->addElement('header', '', get_string('group', 'repository_equella', format_string($role->name)));
234             $mform->addElement('text', "equella_{$role->shortname}_shareid", get_string('sharedid', 'repository_equella'));
235             $mform->setType("equella_{$role->shortname}_shareid", PARAM_RAW);
236             $mform->addElement('text', "equella_{$role->shortname}_sharedsecret",
237                 get_string('sharedsecrets', 'repository_equella'));
238             $mform->setType("equella_{$role->shortname}_sharedsecret", PARAM_RAW);
239         }
240     }
242     /**
243      * Names of the instance settings
244      *
245      * @return array
246      */
247     public static function get_instance_option_names() {
248         $rv = array('equella_url', 'equella_select_restriction', 'equella_options',
249             'equella_shareid', 'equella_sharedsecret'
250         );
252         foreach (self::get_all_editing_roles() as $role) {
253             array_push($rv, "equella_{$role->shortname}_shareid");
254             array_push($rv, "equella_{$role->shortname}_sharedsecret");
255         }
257         return $rv;
258     }
260     /**
261      * Generate equella token
262      *
263      * @param string $username
264      * @param string $shareid
265      * @param string $sharedsecret
266      * @return string
267      */
268     private static function getssotoken_raw($username, $shareid, $sharedsecret) {
269         $time = time() . '000';
270         return urlencode($username)
271             . ':'
272             . $shareid
273             . ':'
274             . $time
275             . ':'
276             . base64_encode(pack('H*', md5($username . $shareid . $time . $sharedsecret)));
277     }
279     /**
280      * Append token
281      *
282      * @param string $url
283      * @param $readwrite
284      * @return string
285      */
286     private function appendtoken($url, $readwrite = null) {
287         $ssotoken = $this->getssotoken($readwrite);
288         if (!$ssotoken) {
289             return false;
290         }
291         return $url . (strpos($url, '?') != false ? '&' : '?') . 'token=' . urlencode($ssotoken);
292     }
294     /**
295      * Generate equella sso token
296      *
297      * @param string $readwrite
298      * @return string
299      */
300     private function getssotoken($readwrite = 'read') {
301         global $USER;
303         if (empty($USER->username)) {
304             return false;
305         }
307         if ($readwrite == 'write') {
309             foreach (self::get_all_editing_roles() as $role) {
310                 if (user_has_role_assignment($USER->id, $role->id, $this->context->id)) {
311                     // See if the user has a role that is linked to an equella role.
312                     $shareid = $this->get_option("equella_{$role->shortname}_shareid");
313                     if (!empty($shareid)) {
314                         return $this->getssotoken_raw($USER->username, $shareid,
315                             $this->get_option("equella_{$role->shortname}_sharedsecret"));
316                     }
317                 }
318             }
319         }
320         // If we are only reading, use the unadorned shareid and secret.
321         $shareid = $this->get_option('equella_shareid');
322         if (!empty($shareid)) {
323             return $this->getssotoken_raw($USER->username, $shareid, $this->get_option('equella_sharedsecret'));
324         }
325     }
327     private static function get_all_editing_roles() {
328         return get_roles_with_capability('moodle/course:manageactivities', CAP_ALLOW);
329     }
331     /**
332      * Convert moodle mimetypes list to equella format
333      *
334      * @param string $value
335      * @return string
336      */
337     private static function to_mime_type($value) {
338         return mimeinfo('type', $value);
339     }