~ubuntu-branches/ubuntu/trusty/moodle/trusty-proposed

« back to all changes in this revision

Viewing changes to repository/googledocs/lib.php

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2013-07-19 08:52:46 UTC
  • mfrom: (1.1.10)
  • Revision ID: package-import@ubuntu.com-20130719085246-yebwditc2exoap2r
Tags: 2.5.1-1
* New upstream version: 2.5.1.
  - Fixes security issues:
    CVE-2013-2242 CVE-2013-2243 CVE-2013-2244 CVE-2013-2245
    CVE-2013-2246
* Depend on apache2 instead of obsolete apache2-mpm-prefork.
* Use packaged libphp-phpmailer (closes: #429339), adodb,
  HTMLPurifier, PclZip.
* Update debconf translations, thanks Salvatore Merone, Pietro Tollot,
  Joe Hansen, Yuri Kozlov, Holger Wansing, Américo Monteiro,
  Adriano Rafael Gomes, victory, Michał Kułach.
  (closes: #716972, #716986, #717080, #717108, #717278)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
<?php
2
 
 
3
2
// This file is part of Moodle - http://moodle.org/
4
3
//
5
4
// Moodle is free software: you can redistribute it and/or modify
16
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17
16
 
18
17
/**
 
18
 * This plugin is used to access Google Drive.
 
19
 *
 
20
 * @since 2.0
 
21
 * @package    repository_googledocs
 
22
 * @copyright  2009 Dan Poltawski <talktodan@gmail.com>
 
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 
24
 */
 
25
 
 
26
defined('MOODLE_INTERNAL') || die();
 
27
 
 
28
require_once($CFG->dirroot . '/repository/lib.php');
 
29
require_once($CFG->libdir . '/textlib.class.php');
 
30
require_once($CFG->libdir . '/google/Google_Client.php');
 
31
require_once($CFG->libdir . '/google/contrib/Google_DriveService.php');
 
32
 
 
33
/**
19
34
 * Google Docs Plugin
20
35
 *
21
36
 * @since 2.0
22
 
 * @package    repository
23
 
 * @subpackage googledocs
 
37
 * @package    repository_googledocs
24
38
 * @copyright  2009 Dan Poltawski <talktodan@gmail.com>
25
39
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
40
 */
27
 
 
28
 
require_once($CFG->libdir.'/googleapi.php');
29
 
 
30
41
class repository_googledocs extends repository {
31
 
    private $subauthtoken = '';
32
 
 
33
 
    public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
34
 
        global $USER;
35
 
        parent::__construct($repositoryid, $context, $options);
36
 
 
37
 
        // TODO: I wish there was somewhere we could explicitly put this outside of constructor..
38
 
        $googletoken = optional_param('token', false, PARAM_RAW);
39
 
        if($googletoken){
40
 
            $gauth = new google_authsub(false, $googletoken); // will throw exception if fails
41
 
            google_docs::set_sesskey($gauth->get_sessiontoken(), $USER->id);
42
 
        }
 
42
 
 
43
    /**
 
44
     * Google Client.
 
45
     * @var Google_Client
 
46
     */
 
47
    private $client = null;
 
48
 
 
49
    /**
 
50
     * Google Drive Service.
 
51
     * @var Google_DriveService
 
52
     */
 
53
    private $service = null;
 
54
 
 
55
    /**
 
56
     * Session key to store the accesstoken.
 
57
     * @var string
 
58
     */
 
59
    const SESSIONKEY = 'googledrive_accesstoken';
 
60
 
 
61
    /**
 
62
     * URI to the callback file for OAuth.
 
63
     * @var string
 
64
     */
 
65
    const CALLBACKURL = '/admin/oauth2callback.php';
 
66
 
 
67
    /**
 
68
     * Constructor.
 
69
     *
 
70
     * @param int $repositoryid repository instance id.
 
71
     * @param int|stdClass $context a context id or context object.
 
72
     * @param array $options repository options.
 
73
     * @param int $readonly indicate this repo is readonly or not.
 
74
     * @return void
 
75
     */
 
76
    public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
 
77
        parent::__construct($repositoryid, $context, $options, $readonly = 0);
 
78
 
 
79
        $callbackurl = new moodle_url(self::CALLBACKURL);
 
80
 
 
81
        $this->client = new Google_Client();
 
82
        $this->client->setClientId(get_config('googledocs', 'clientid'));
 
83
        $this->client->setClientSecret(get_config('googledocs', 'secret'));
 
84
        $this->client->setScopes(array('https://www.googleapis.com/auth/drive.readonly'));
 
85
        $this->client->setRedirectUri($callbackurl->out(false));
 
86
        $this->service = new Google_DriveService($this->client);
 
87
 
43
88
        $this->check_login();
44
89
    }
45
90
 
 
91
    /**
 
92
     * Returns the access token if any.
 
93
     *
 
94
     * @return string|null access token.
 
95
     */
 
96
    protected function get_access_token() {
 
97
        global $SESSION;
 
98
        if (isset($SESSION->{self::SESSIONKEY})) {
 
99
            return $SESSION->{self::SESSIONKEY};
 
100
        }
 
101
        return null;
 
102
    }
 
103
 
 
104
    /**
 
105
     * Store the access token in the session.
 
106
     *
 
107
     * @param string $token token to store.
 
108
     * @return void
 
109
     */
 
110
    protected function store_access_token($token) {
 
111
        global $SESSION;
 
112
        $SESSION->{self::SESSIONKEY} = $token;
 
113
    }
 
114
 
 
115
    /**
 
116
     * Callback method during authentication.
 
117
     *
 
118
     * @return void
 
119
     */
 
120
    public function callback() {
 
121
        if ($code = optional_param('oauth2code', null, PARAM_RAW)) {
 
122
            $this->client->authenticate($code);
 
123
            $this->store_access_token($this->client->getAccessToken());
 
124
        }
 
125
    }
 
126
 
 
127
    /**
 
128
     * Checks whether the user is authenticate or not.
 
129
     *
 
130
     * @return bool true when logged in.
 
131
     */
46
132
    public function check_login() {
47
 
        global $USER;
48
 
 
49
 
        $sesskey = google_docs::get_sesskey($USER->id);
50
 
 
51
 
        if($sesskey){
52
 
            try{
53
 
                $gauth = new google_authsub($sesskey);
54
 
                $this->subauthtoken = $sesskey;
55
 
                return true;
56
 
            }catch(Exception $e){
57
 
                // sesskey is not valid, delete store and re-auth
58
 
                google_docs::delete_sesskey($USER->id);
59
 
            }
 
133
        if ($token = $this->get_access_token()) {
 
134
            $this->client->setAccessToken($token);
 
135
            return true;
60
136
        }
61
 
 
62
137
        return false;
63
138
    }
64
139
 
65
 
    public function print_login($ajax = true){
66
 
        global $CFG;
67
 
        if($ajax){
68
 
            $ret = array();
69
 
            $popup_btn = new stdClass();
70
 
            $popup_btn->type = 'popup';
71
 
            $returnurl = $CFG->wwwroot.'/repository/repository_callback.php?callback=yes&repo_id='.$this->id;
72
 
            $popup_btn->url = google_authsub::login_url($returnurl, google_docs::REALM);
73
 
            $ret['login'] = array($popup_btn);
74
 
            return $ret;
75
 
        }
76
 
    }
77
 
 
 
140
    /**
 
141
     * Print or return the login form.
 
142
     *
 
143
     * @return void|array for ajax.
 
144
     */
 
145
    public function print_login() {
 
146
        $returnurl = new moodle_url('/repository/repository_callback.php');
 
147
        $returnurl->param('callback', 'yes');
 
148
        $returnurl->param('repo_id', $this->id);
 
149
        $returnurl->param('sesskey', sesskey());
 
150
 
 
151
        $url = new moodle_url($this->client->createAuthUrl());
 
152
        $url->param('state', $returnurl->out_as_local_url(false));
 
153
        if ($this->options['ajax']) {
 
154
            $popup = new stdClass();
 
155
            $popup->type = 'popup';
 
156
            $popup->url = $url->out(false);
 
157
            return array('login' => array($popup));
 
158
        } else {
 
159
            echo '<a target="_blank" href="'.$url->out(false).'">'.get_string('login', 'repository').'</a>';
 
160
        }
 
161
    }
 
162
 
 
163
    /**
 
164
    * Build the breadcrumb from a path.
 
165
    *
 
166
    * @param string $path to create a breadcrumb from.
 
167
    * @return array containing name and path of each crumb.
 
168
    */
 
169
    protected function build_breadcrumb($path) {
 
170
        $bread = explode('/', $path);
 
171
        $crumbtrail = '';
 
172
        foreach ($bread as $crumb) {
 
173
            list($id, $name) = $this->explode_node_path($crumb);
 
174
            $name = empty($name) ? $id : $name;
 
175
            $breadcrumb[] = array(
 
176
                'name' => $name,
 
177
                'path' => $this->build_node_path($id, $name, $crumbtrail)
 
178
            );
 
179
            $tmp = end($breadcrumb);
 
180
            $crumbtrail = $tmp['path'];
 
181
        }
 
182
        return $breadcrumb;
 
183
    }
 
184
 
 
185
    /**
 
186
    * Generates a safe path to a node.
 
187
    *
 
188
    * Typically, a node will be id|Name of the node.
 
189
    *
 
190
    * @param string $id of the node.
 
191
    * @param string $name of the node, will be URL encoded.
 
192
    * @param string $root to append the node on, must be a result of this function.
 
193
    * @return string path to the node.
 
194
    */
 
195
    protected function build_node_path($id, $name = '', $root = '') {
 
196
        $path = $id;
 
197
        if (!empty($name)) {
 
198
            $path .= '|' . urlencode($name);
 
199
        }
 
200
        if (!empty($root)) {
 
201
            $path = trim($root, '/') . '/' . $path;
 
202
        }
 
203
        return $path;
 
204
    }
 
205
 
 
206
    /**
 
207
    * Returns information about a node in a path.
 
208
    *
 
209
    * @see self::build_node_path()
 
210
    * @param string $node to extrat information from.
 
211
    * @return array about the node.
 
212
    */
 
213
    protected function explode_node_path($node) {
 
214
        if (strpos($node, '|') !== false) {
 
215
            list($id, $name) = explode('|', $node, 2);
 
216
            $name = urldecode($name);
 
217
        } else {
 
218
            $id = $node;
 
219
            $name = '';
 
220
        }
 
221
        $id = urldecode($id);
 
222
        return array(
 
223
            0 => $id,
 
224
            1 => $name,
 
225
            'id' => $id,
 
226
            'name' => $name
 
227
        );
 
228
    }
 
229
 
 
230
 
 
231
    /**
 
232
     * List the files and folders.
 
233
     *
 
234
     * @param  string $path path to browse.
 
235
     * @param  string $page page to browse.
 
236
     * @return array of result.
 
237
     */
78
238
    public function get_listing($path='', $page = '') {
79
 
        $gdocs = new google_docs(new google_authsub($this->subauthtoken));
80
 
 
81
 
        $ret = array();
82
 
        $ret['dynload'] = true;
83
 
        $ret['list'] = $gdocs->get_file_list();
84
 
        return $ret;
85
 
    }
86
 
 
87
 
    public function search($query){
88
 
        $gdocs = new google_docs(new google_authsub($this->subauthtoken));
89
 
 
90
 
        $ret = array();
91
 
        $ret['dynload'] = true;
92
 
        $ret['list'] = $gdocs->get_file_list($query);
93
 
        return $ret;
94
 
    }
95
 
 
96
 
    public function logout(){
97
 
        global $USER;
98
 
 
99
 
        $token = google_docs::get_sesskey($USER->id);
100
 
 
101
 
        $gauth = new google_authsub($token);
102
 
        // revoke token from google
103
 
        $gauth->revoke_session_token();
104
 
 
105
 
        google_docs::delete_sesskey($USER->id);
106
 
        $this->subauthtoken = '';
107
 
 
 
239
        if (empty($path)) {
 
240
            $path = $this->build_node_path('root', get_string('pluginname', 'repository_googledocs'));
 
241
        }
 
242
 
 
243
        // We analyse the path to extract what to browse.
 
244
        $trail = explode('/', $path);
 
245
        $uri = array_pop($trail);
 
246
        list($id, $name) = $this->explode_node_path($uri);
 
247
 
 
248
        // Handle the special keyword 'search', which we defined in self::search() so that
 
249
        // we could set up a breadcrumb in the search results. In any other case ID would be
 
250
        // 'root' which is a special keyword set up by Google, or a parent (folder) ID.
 
251
        if ($id === 'search') {
 
252
            return $this->search($name);
 
253
        }
 
254
 
 
255
        // Query the Drive.
 
256
        $q = "'" . str_replace("'", "\'", $id) . "' in parents";
 
257
        $q .= ' AND trashed = false';
 
258
        $results = $this->query($q, $path);
 
259
 
 
260
        $ret = array();
 
261
        $ret['dynload'] = true;
 
262
        $ret['path'] = $this->build_breadcrumb($path);
 
263
        $ret['list'] = $results;
 
264
        return $ret;
 
265
    }
 
266
 
 
267
    /**
 
268
     * Search throughout the Google Drive.
 
269
     *
 
270
     * @param string $search_text text to search for.
 
271
     * @param int $page search page.
 
272
     * @return array of results.
 
273
     */
 
274
    public function search($search_text, $page = 0) {
 
275
        $path = $this->build_node_path('root', get_string('pluginname', 'repository_googledocs'));
 
276
        $path = $this->build_node_path('search', $search_text, $path);
 
277
 
 
278
        // Query the Drive.
 
279
        $q = "fullText contains '" . str_replace("'", "\'", $search_text) . "'";
 
280
        $q .= ' AND trashed = false';
 
281
        $results = $this->query($q, $path);
 
282
 
 
283
        $ret = array();
 
284
        $ret['dynload'] = true;
 
285
        $ret['path'] = $this->build_breadcrumb($path);
 
286
        $ret['list'] = $results;
 
287
        return $ret;
 
288
    }
 
289
 
 
290
    /**
 
291
     * Query Google Drive for files and folders using a search query.
 
292
     *
 
293
     * Documentation about the query format can be found here:
 
294
     *   https://developers.google.com/drive/search-parameters
 
295
     *
 
296
     * This returns a list of files and folders with their details as they should be
 
297
     * formatted and returned by functions such as get_listing() or search().
 
298
     *
 
299
     * @param string $q search query as expected by the Google API.
 
300
     * @param string $path parent path of the current files, will not be used for the query.
 
301
     * @param int $page page.
 
302
     * @return array of files and folders.
 
303
     */
 
304
    protected function query($q, $path = null, $page = 0) {
 
305
        global $OUTPUT;
 
306
 
 
307
        $files = array();
 
308
        $folders = array();
 
309
        $fields = "items(id,title,mimeType,downloadUrl,fileExtension,exportLinks,modifiedDate,fileSize,thumbnailLink)";
 
310
        $params = array('q' => $q, 'fields' => $fields);
 
311
 
 
312
        try {
 
313
            // Retrieving files and folders.
 
314
            $response = $this->service->files->listFiles($params);
 
315
        } catch (Google_ServiceException $e) {
 
316
            if ($e->getCode() == 403 && strpos($e->getMessage(), 'Access Not Configured') !== false) {
 
317
                // This is raised when the service Drive API has not been enabled on Google APIs control panel.
 
318
                throw new repository_exception('servicenotenabled', 'repository_googledocs');
 
319
            } else {
 
320
                throw $e;
 
321
            }
 
322
        }
 
323
 
 
324
        $items = isset($response['items']) ? $response['items'] : array();
 
325
        foreach ($items as $item) {
 
326
            if ($item['mimeType'] == 'application/vnd.google-apps.folder') {
 
327
                // This is a folder.
 
328
                $folders[$item['title'] . $item['id']] = array(
 
329
                    'title' => $item['title'],
 
330
                    'path' => $this->build_node_path($item['id'], $item['title'], $path),
 
331
                    'date' => strtotime($item['modifiedDate']),
 
332
                    'thumbnail' => $OUTPUT->pix_url(file_folder_icon(64))->out(false),
 
333
                    'thumbnail_height' => 64,
 
334
                    'thumbnail_width' => 64,
 
335
                    'children' => array()
 
336
                );
 
337
            } else {
 
338
                // This is a file.
 
339
                if (isset($item['fileExtension'])) {
 
340
                    // The file has an extension, therefore there is a download link.
 
341
                    $title = $item['title'];
 
342
                    $source = $item['downloadUrl'];
 
343
                } else {
 
344
                    // The file is probably a Google Doc file, we get the corresponding export link.
 
345
                    // This should be improved by allowing the user to select the type of export they'd like.
 
346
                    $type = str_replace('application/vnd.google-apps.', '', $item['mimeType']);
 
347
                    $title = '';
 
348
                    $exportType = '';
 
349
                    switch ($type){
 
350
                        case 'document':
 
351
                            $title = $item['title'] . '.rtf';
 
352
                            $exportType = 'application/rtf';
 
353
                            break;
 
354
                        case 'presentation':
 
355
                            $title = $item['title'] . '.pptx';
 
356
                            $exportType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
 
357
                            break;
 
358
                        case 'spreadsheet':
 
359
                            $title = $item['title'] . '.xlsx';
 
360
                            $exportType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
 
361
                            break;
 
362
                    }
 
363
                    // Skips invalid/unknown types.
 
364
                    if (empty($title) || !isset($item['exportLinks'][$exportType])) {
 
365
                        continue;
 
366
                    }
 
367
                    $source = $item['exportLinks'][$exportType];
 
368
                }
 
369
                // Adds the file to the file list. Using the itemId along with the title as key
 
370
                // of the array because Google Drive allows files with identical names.
 
371
                $files[$title . $item['id']] = array(
 
372
                    'title' => $title,
 
373
                    'source' => $source,
 
374
                    'date' => strtotime($item['modifiedDate']),
 
375
                    'size' => isset($item['fileSize']) ? $item['fileSize'] : null,
 
376
                    'thumbnail' => $OUTPUT->pix_url(file_extension_icon($title, 64))->out(false),
 
377
                    'thumbnail_height' => 64,
 
378
                    'thumbnail_width' => 64,
 
379
                    // Do not use real thumbnails as they wouldn't work if the user disabled 3rd party
 
380
                    // plugins in his browser, or if they're not logged in their Google account.
 
381
                );
 
382
 
 
383
                // Sometimes the real thumbnails can't be displayed, for example if 3rd party cookies are disabled
 
384
                // or if the user is not logged in Google anymore. But this restriction does not seem to be applied
 
385
                // to a small subset of files.
 
386
                $extension = strtolower(pathinfo($title, PATHINFO_EXTENSION));
 
387
                if (isset($item['thumbnailLink']) && in_array($extension, array('jpg', 'png', 'txt', 'pdf'))) {
 
388
                    $files[$title . $item['id']]['realthumbnail'] = $item['thumbnailLink'];
 
389
                }
 
390
            }
 
391
        }
 
392
 
 
393
        // Filter and order the results.
 
394
        $files = array_filter($files, array($this, 'filter'));
 
395
        collatorlib::ksort($files, collatorlib::SORT_NATURAL);
 
396
        collatorlib::ksort($folders, collatorlib::SORT_NATURAL);
 
397
        return array_merge(array_values($folders), array_values($files));
 
398
    }
 
399
 
 
400
    /**
 
401
     * Logout.
 
402
     *
 
403
     * @return string
 
404
     */
 
405
    public function logout() {
 
406
        $this->store_access_token(null);
108
407
        return parent::logout();
109
408
    }
110
409
 
111
 
    public function get_file($url, $file) {
112
 
        if (empty($url)) {
113
 
           throw new repository_exception('cannotdownload', 'repository');
 
410
    /**
 
411
     * Get a file.
 
412
     *
 
413
     * @param string $reference reference of the file.
 
414
     * @param string $file name to save the file to.
 
415
     * @return string JSON encoded array of information about the file.
 
416
     */
 
417
    public function get_file($reference, $filename = '') {
 
418
        $request = new Google_HttpRequest($reference);
 
419
        $httpRequest = Google_Client::$io->authenticatedRequest($request);
 
420
        if ($httpRequest->getResponseHttpCode() == 200) {
 
421
            $path = $this->prepare_file($filename);
 
422
            $content = $httpRequest->getResponseBody();
 
423
            if (file_put_contents($path, $content) !== false) {
 
424
                return array(
 
425
                    'path' => $path,
 
426
                    'url' => $reference
 
427
                );
 
428
            }
114
429
        }
115
 
        $path = $this->prepare_file($file);
116
 
 
117
 
        $fp = fopen($path, 'w');
118
 
        $gdocs = new google_docs(new google_authsub($this->subauthtoken));
119
 
        $gdocs->download_file($url, $fp);
120
 
 
121
 
        return array('path'=>$path, 'url'=>$url);
122
 
    }
123
 
 
 
430
        throw new repository_exception('cannotdownload', 'repository');
 
431
    }
 
432
 
 
433
    /**
 
434
     * Prepare file reference information.
 
435
     *
 
436
     * We are using this method to clean up the source to make sure that it
 
437
     * is a valid source.
 
438
     *
 
439
     * @param string $source of the file.
 
440
     * @return string file reference.
 
441
     */
 
442
    public function get_file_reference($source) {
 
443
        return clean_param($source, PARAM_URL);
 
444
    }
 
445
 
 
446
    /**
 
447
     * What kind of files will be in this repository?
 
448
     *
 
449
     * @return array return '*' means this repository support any files, otherwise
 
450
     *               return mimetypes of files, it can be an array
 
451
     */
124
452
    public function supported_filetypes() {
125
 
       return array('document');
 
453
        return '*';
126
454
    }
 
455
 
 
456
    /**
 
457
     * Tells how the file can be picked from this repository.
 
458
     *
 
459
     * Maximum value is FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE.
 
460
     *
 
461
     * @return int
 
462
     */
127
463
    public function supported_returntypes() {
128
464
        return FILE_INTERNAL;
129
465
    }
 
466
 
 
467
    /**
 
468
     * Return names of the general options.
 
469
     * By default: no general option name.
 
470
     *
 
471
     * @return array
 
472
     */
 
473
    public static function get_type_option_names() {
 
474
        return array('clientid', 'secret', 'pluginname');
 
475
    }
 
476
 
 
477
    /**
 
478
     * Edit/Create Admin Settings Moodle form.
 
479
     *
 
480
     * @param moodleform $mform Moodle form (passed by reference).
 
481
     * @param string $classname repository class name.
 
482
     */
 
483
    public static function type_config_form($mform, $classname = 'repository') {
 
484
 
 
485
        $callbackurl = new moodle_url(self::CALLBACKURL);
 
486
 
 
487
        $a = new stdClass;
 
488
        $a->docsurl = get_docs_url('Google_OAuth_2.0_setup');
 
489
        $a->callbackurl = $callbackurl->out(false);
 
490
 
 
491
        $mform->addElement('static', null, '', get_string('oauthinfo', 'repository_googledocs', $a));
 
492
 
 
493
        parent::type_config_form($mform);
 
494
        $mform->addElement('text', 'clientid', get_string('clientid', 'repository_googledocs'));
 
495
        $mform->setType('clientid', PARAM_RAW_TRIMMED);
 
496
        $mform->addElement('text', 'secret', get_string('secret', 'repository_googledocs'));
 
497
        $mform->setType('secret', PARAM_RAW_TRIMMED);
 
498
 
 
499
        $strrequired = get_string('required');
 
500
        $mform->addRule('clientid', $strrequired, 'required', null, 'client');
 
501
        $mform->addRule('secret', $strrequired, 'required', null, 'client');
 
502
    }
130
503
}
131
 
//Icon from: http://www.iconspedia.com/icon/google-2706.html
 
504
// Icon from: http://www.iconspedia.com/icon/google-2706.html.