MDL-58128 googledocs: Upgrade repo config
[moodle.git] / repository / googledocs / lib.php
CommitLineData
6667f9e4 1<?php
10d53fd3
DC
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/>.
16
6667f9e4 17/**
3425813f 18 * This plugin is used to access Google Drive.
6667f9e4 19 *
5bcfd504 20 * @since Moodle 2.0
67233725 21 * @package repository_googledocs
61506a0a 22 * @copyright 2009 Dan Poltawski <talktodan@gmail.com>
d078f6d3 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6667f9e4 24 */
3425813f
FM
25
26defined('MOODLE_INTERNAL') || die();
27
67233725 28require_once($CFG->dirroot . '/repository/lib.php');
28cdc043 29require_once($CFG->libdir . '/google/lib.php');
6667f9e4 30
67233725
DC
31/**
32 * Google Docs Plugin
33 *
5bcfd504 34 * @since Moodle 2.0
67233725
DC
35 * @package repository_googledocs
36 * @copyright 2009 Dan Poltawski <talktodan@gmail.com>
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 */
6667f9e4 39class repository_googledocs extends repository {
6667f9e4 40
3425813f 41 /**
0e59638b
DW
42 * OAuth 2 client
43 * @var \core\oauth2\client
3425813f
FM
44 */
45 private $client = null;
6667f9e4 46
3425813f 47 /**
0e59638b
DW
48 * OAuth 2 Issuer
49 * @var \core\oauth2\issuer
3425813f 50 */
0e59638b 51 private $issuer = null;
3425813f
FM
52
53 /**
0e59638b 54 * Additional scopes required for drive.
3425813f 55 */
0e59638b 56 const SCOPES = 'https://www.googleapis.com/auth/drive';
3425813f
FM
57
58 /**
59 * Constructor.
60 *
61 * @param int $repositoryid repository instance id.
62 * @param int|stdClass $context a context id or context object.
63 * @param array $options repository options.
64 * @param int $readonly indicate this repo is readonly or not.
65 * @return void
66 */
67 public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
68 parent::__construct($repositoryid, $context, $options, $readonly = 0);
69
0e59638b 70 $this->issuer = \core\oauth2\api::get_issuer(get_config('googledocs', 'issuerid'));
6667f9e4 71 }
72
3425813f 73 /**
0e59638b 74 * Get a cached user authenticated oauth client.
3425813f 75 *
0e59638b 76 * @return \core\oauth2\client
3425813f 77 */
0e59638b
DW
78 protected function get_user_oauth_client() {
79 if ($this->client) {
80 return $this->client;
3425813f 81 }
0e59638b
DW
82 $returnurl = new moodle_url('/repository/repository_callback.php');
83 $returnurl->param('callback', 'yes');
84 $returnurl->param('repo_id', $this->id);
85 $returnurl->param('sesskey', sesskey());
3425813f 86
0e59638b 87 $this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, self::SCOPES);
3425813f 88
0e59638b 89 return $this->client;
3425813f
FM
90 }
91
92 /**
93 * Checks whether the user is authenticate or not.
94 *
95 * @return bool true when logged in.
96 */
6667f9e4 97 public function check_login() {
0e59638b
DW
98 $client = $this->get_user_oauth_client();
99 return $client->is_logged_in();
6667f9e4 100 }
101
3425813f
FM
102 /**
103 * Print or return the login form.
104 *
105 * @return void|array for ajax.
106 */
4560fd1b 107 public function print_login() {
0e59638b
DW
108 $client = $this->get_user_oauth_client();
109 $url = $client->get_login_url();
4560fd1b
DP
110
111 if ($this->options['ajax']) {
112 $popup = new stdClass();
113 $popup->type = 'popup';
114 $popup->url = $url->out(false);
115 return array('login' => array($popup));
116 } else {
117 echo '<a target="_blank" href="'.$url->out(false).'">'.get_string('login', 'repository').'</a>';
6667f9e4 118 }
119 }
120
3425813f 121 /**
0e59638b
DW
122 * Build the breadcrumb from a path.
123 *
124 * @param string $path to create a breadcrumb from.
125 * @return array containing name and path of each crumb.
126 */
3425813f
FM
127 protected function build_breadcrumb($path) {
128 $bread = explode('/', $path);
129 $crumbtrail = '';
130 foreach ($bread as $crumb) {
131 list($id, $name) = $this->explode_node_path($crumb);
132 $name = empty($name) ? $id : $name;
133 $breadcrumb[] = array(
134 'name' => $name,
135 'path' => $this->build_node_path($id, $name, $crumbtrail)
136 );
137 $tmp = end($breadcrumb);
138 $crumbtrail = $tmp['path'];
139 }
140 return $breadcrumb;
141 }
142
143 /**
0e59638b
DW
144 * Generates a safe path to a node.
145 *
146 * Typically, a node will be id|Name of the node.
147 *
148 * @param string $id of the node.
149 * @param string $name of the node, will be URL encoded.
150 * @param string $root to append the node on, must be a result of this function.
151 * @return string path to the node.
152 */
3425813f
FM
153 protected function build_node_path($id, $name = '', $root = '') {
154 $path = $id;
155 if (!empty($name)) {
156 $path .= '|' . urlencode($name);
157 }
158 if (!empty($root)) {
159 $path = trim($root, '/') . '/' . $path;
160 }
161 return $path;
162 }
163
164 /**
0e59638b
DW
165 * Returns information about a node in a path.
166 *
167 * @see self::build_node_path()
168 * @param string $node to extrat information from.
169 * @return array about the node.
170 */
3425813f
FM
171 protected function explode_node_path($node) {
172 if (strpos($node, '|') !== false) {
173 list($id, $name) = explode('|', $node, 2);
174 $name = urldecode($name);
175 } else {
176 $id = $node;
177 $name = '';
178 }
179 $id = urldecode($id);
180 return array(
181 0 => $id,
182 1 => $name,
183 'id' => $id,
184 'name' => $name
185 );
186 }
187
188
189 /**
190 * List the files and folders.
191 *
192 * @param string $path path to browse.
193 * @param string $page page to browse.
194 * @return array of result.
195 */
6667f9e4 196 public function get_listing($path='', $page = '') {
3425813f
FM
197 if (empty($path)) {
198 $path = $this->build_node_path('root', get_string('pluginname', 'repository_googledocs'));
199 }
200
201 // We analyse the path to extract what to browse.
202 $trail = explode('/', $path);
203 $uri = array_pop($trail);
204 list($id, $name) = $this->explode_node_path($uri);
205
206 // Handle the special keyword 'search', which we defined in self::search() so that
207 // we could set up a breadcrumb in the search results. In any other case ID would be
208 // 'root' which is a special keyword set up by Google, or a parent (folder) ID.
209 if ($id === 'search') {
210 return $this->search($name);
211 }
212
213 // Query the Drive.
214 $q = "'" . str_replace("'", "\'", $id) . "' in parents";
215 $q .= ' AND trashed = false';
216 $results = $this->query($q, $path);
6667f9e4 217
218 $ret = array();
219 $ret['dynload'] = true;
3425813f
FM
220 $ret['path'] = $this->build_breadcrumb($path);
221 $ret['list'] = $results;
6667f9e4 222 return $ret;
223 }
224
3425813f
FM
225 /**
226 * Search throughout the Google Drive.
227 *
0e59638b 228 * @param string $searchtext text to search for.
3425813f
FM
229 * @param int $page search page.
230 * @return array of results.
231 */
0e59638b 232 public function search($searchtext, $page = 0) {
3425813f 233 $path = $this->build_node_path('root', get_string('pluginname', 'repository_googledocs'));
0e59638b
DW
234 $str = get_string('searchfor', 'repository_googledocs', $searchtext);
235 $path = $this->build_node_path('search', $str, $path);
3425813f
FM
236
237 // Query the Drive.
0e59638b 238 $q = "fullText contains '" . str_replace("'", "\'", $searchtext) . "'";
3425813f
FM
239 $q .= ' AND trashed = false';
240 $results = $this->query($q, $path);
6667f9e4 241
242 $ret = array();
243 $ret['dynload'] = true;
3425813f
FM
244 $ret['path'] = $this->build_breadcrumb($path);
245 $ret['list'] = $results;
6667f9e4 246 return $ret;
247 }
248
3425813f
FM
249 /**
250 * Query Google Drive for files and folders using a search query.
251 *
252 * Documentation about the query format can be found here:
253 * https://developers.google.com/drive/search-parameters
254 *
255 * This returns a list of files and folders with their details as they should be
256 * formatted and returned by functions such as get_listing() or search().
257 *
258 * @param string $q search query as expected by the Google API.
259 * @param string $path parent path of the current files, will not be used for the query.
260 * @param int $page page.
261 * @return array of files and folders.
262 */
263 protected function query($q, $path = null, $page = 0) {
264 global $OUTPUT;
265
266 $files = array();
267 $folders = array();
15a5c4b2 268 $config = get_config('googledocs');
0e59638b
DW
269 $fields = "files(id,name,mimeType,webContentLink,fileExtension,modifiedTime,size,thumbnailLink,iconLink)";
270 $params = array('q' => $q, 'fields' => $fields, 'spaces' => 'drive');
3425813f
FM
271
272 try {
273 // Retrieving files and folders.
0e59638b
DW
274 $client = $this->get_user_oauth_client();
275 $service = new repository_googledocs\rest($client);
276
277 $response = $service->call('list', $params);
278 } catch (Exception $e) {
3425813f
FM
279 if ($e->getCode() == 403 && strpos($e->getMessage(), 'Access Not Configured') !== false) {
280 // This is raised when the service Drive API has not been enabled on Google APIs control panel.
281 throw new repository_exception('servicenotenabled', 'repository_googledocs');
282 } else {
283 throw $e;
284 }
285 }
286
0e59638b
DW
287 $base = 'https://www.googleapis.com/drive/v3';
288 $gfiles = isset($response->files) ? $response->files : array();
289 foreach ($gfiles as $gfile) {
290 if ($gfile->mimeType == 'application/vnd.google-apps.folder') {
3425813f 291 // This is a folder.
0e59638b
DW
292 $folders[$gfile->name . $gfile->id] = array(
293 'title' => $gfile->name,
294 'path' => $this->build_node_path($gfile->id, $gfile->name, $path),
295 'date' => strtotime($gfile->modifiedTime),
663640f5 296 'thumbnail' => $OUTPUT->image_url(file_folder_icon(64))->out(false),
3425813f
FM
297 'thumbnail_height' => 64,
298 'thumbnail_width' => 64,
299 'children' => array()
300 );
301 } else {
302 // This is a file.
0e59638b
DW
303 if (isset($gfile->fileExtension)) {
304 // The file has an extension, therefore we can download it.
305 $title = $gfile->name;
306 $params = ['alt' => 'media'];
307 $sourceurl = new moodle_url($base . '/files/' . $gfile->id, $params);
308 $source = $sourceurl->out(false);
3425813f
FM
309 } else {
310 // The file is probably a Google Doc file, we get the corresponding export link.
311 // This should be improved by allowing the user to select the type of export they'd like.
0e59638b 312 $type = str_replace('application/vnd.google-apps.', '', $gfile->mimeType);
3425813f 313 $title = '';
0e59638b 314 $exporttype = '';
15a5c4b2
SB
315 $types = get_mimetypes_array();
316
3425813f
FM
317 switch ($type){
318 case 'document':
15a5c4b2
SB
319 $ext = $config->documentformat;
320 $title = $item['title'] . '.'. $ext;
321 if ($ext === 'rtf') {
322 // Moodle user 'text/rtf' as the MIME type for RTF files.
323 // Google uses 'application/rtf' for the same type of file.
324 // See https://developers.google.com/drive/v3/web/manage-downloads.
0e59638b 325 $exporttype = 'application/rtf';
15a5c4b2 326 } else {
0e59638b 327 $exporttype = $types[$ext]['type'];
15a5c4b2 328 }
3425813f
FM
329 break;
330 case 'presentation':
15a5c4b2
SB
331 $ext = $config->presentationformat;
332 $title = $item['title'] . '.'. $ext;
0e59638b 333 $exporttype = $types[$ext]['type'];
3425813f
FM
334 break;
335 case 'spreadsheet':
15a5c4b2
SB
336 $ext = $config->spreadsheetformat;
337 $title = $item['title'] . '.'. $ext;
0e59638b 338 $exporttype = $types[$ext]['type'];
15a5c4b2
SB
339 break;
340 case 'drawing':
341 $ext = $config->drawingformat;
342 $title = $item['title'] . '.'. $ext;
0e59638b 343 $exporttype = $types[$ext]['type'];
3425813f
FM
344 break;
345 }
346 // Skips invalid/unknown types.
0e59638b 347 if (empty($title)) {
3425813f
FM
348 continue;
349 }
0e59638b
DW
350 $params = ['mimeType' => $exporttype];
351 $sourceurl = new moodle_url($base . '/files/' . $gfile->id . '/export', $params);
352 $source = $sourceurl->out(false);
3425813f 353 }
0e59638b 354 // Adds the file to the file list. Using the itemId along with the name as key
3425813f 355 // of the array because Google Drive allows files with identical names.
0e59638b
DW
356 $thumb = '';
357 if (isset($gfile->thumbnailLink)) {
358 $thumb = $gfile->thumbnailLink;
359 } else if (isset($gfile->iconLink)) {
360 $thumb = $gfile->iconLink;
361 }
362 $files[$title . $gfile->id] = array(
3425813f
FM
363 'title' => $title,
364 'source' => $source,
0e59638b
DW
365 'date' => strtotime($gfile->modifiedTime),
366 'size' => isset($gfile->size) ? $gfile->size : null,
367 'thumbnail' => $thumb,
3425813f
FM
368 'thumbnail_height' => 64,
369 'thumbnail_width' => 64,
3425813f 370 );
3425813f
FM
371 }
372 }
373
374 // Filter and order the results.
375 $files = array_filter($files, array($this, 'filter'));
2f1e464a
PS
376 core_collator::ksort($files, core_collator::SORT_NATURAL);
377 core_collator::ksort($folders, core_collator::SORT_NATURAL);
3425813f
FM
378 return array_merge(array_values($folders), array_values($files));
379 }
380
381 /**
382 * Logout.
383 *
384 * @return string
385 */
4560fd1b 386 public function logout() {
0e59638b
DW
387 $client = $this->get_user_oauth_client();
388 $client->log_out();
6667f9e4 389 return parent::logout();
390 }
391
3425813f
FM
392 /**
393 * Get a file.
394 *
395 * @param string $reference reference of the file.
396 * @param string $file name to save the file to.
397 * @return string JSON encoded array of information about the file.
398 */
399 public function get_file($reference, $filename = '') {
eb459f71
PS
400 global $CFG;
401
0e59638b
DW
402 $client = $this->get_user_oauth_client();
403
404 $path = $this->prepare_file($filename);
405 $options = ['filepath' => $path, 'timeout' => 15, 'followlocation' => true, 'maxredirs' => 5];
406 $result = $client->download_one($reference, null, $options);
407
408 if ($result) {
409 @chmod($path, $CFG->filepermissions);
410 return array(
411 'path' => $path,
412 'url' => $reference
413 );
ec7e998f 414 }
3425813f
FM
415 throw new repository_exception('cannotdownload', 'repository');
416 }
417
418 /**
419 * Prepare file reference information.
420 *
421 * We are using this method to clean up the source to make sure that it
422 * is a valid source.
423 *
424 * @param string $source of the file.
425 * @return string file reference.
426 */
427 public function get_file_reference($source) {
428 return clean_param($source, PARAM_URL);
41076c58 429 }
6667f9e4 430
3425813f
FM
431 /**
432 * What kind of files will be in this repository?
433 *
434 * @return array return '*' means this repository support any files, otherwise
435 * return mimetypes of files, it can be an array
436 */
41076c58 437 public function supported_filetypes() {
4560fd1b 438 return '*';
41076c58 439 }
3425813f
FM
440
441 /**
442 * Tells how the file can be picked from this repository.
443 *
444 * Maximum value is FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE.
445 *
446 * @return int
447 */
41076c58
DC
448 public function supported_returntypes() {
449 return FILE_INTERNAL;
450 }
4560fd1b 451
3425813f
FM
452 /**
453 * Return names of the general options.
454 * By default: no general option name.
455 *
456 * @return array
457 */
4560fd1b 458 public static function get_type_option_names() {
0e59638b 459 return array('issuerid', 'pluginname',
15a5c4b2
SB
460 'documentformat', 'drawingformat',
461 'presentationformat', 'spreadsheetformat');
4560fd1b
DP
462 }
463
0e59638b
DW
464 /**
465 * Store the access token.
466 */
467 public function callback() {
468 $client = $this->get_user_oauth_client();
469 // This will upgrade to an access token if we have an authorization code.
470 $client->is_logged_in();
471 }
472
3425813f
FM
473 /**
474 * Edit/Create Admin Settings Moodle form.
475 *
476 * @param moodleform $mform Moodle form (passed by reference).
477 * @param string $classname repository class name.
478 */
4560fd1b 479 public static function type_config_form($mform, $classname = 'repository') {
0e59638b 480 $url = (string)new moodle_url('/admin/tool/oauth2/issuers.php');
3425813f 481
0e59638b 482 $mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_googledocs', $url));
4560fd1b
DP
483
484 parent::type_config_form($mform);
0e59638b
DW
485 $options = [];
486 $issuers = \core\oauth2\api::get_all_issuers();
487
488 foreach ($issuers as $issuer) {
489 $options[$issuer->get('id')] = s($issuer->get('name'));
490 }
491 $mform->addElement('select', 'issuerid', get_string('issuer', 'repository_googledocs'), $options);
492 $mform->addHelpButton('issuerid', 'issuer', 'repository_googledocs');
493 $mform->addRule('issuerid', $strrequired, 'required', null, 'client');
4560fd1b
DP
494
495 $strrequired = get_string('required');
15a5c4b2 496
0e59638b 497 $mform->addElement('static', null, '', get_string('importformat', 'repository_googledocs'));
15a5c4b2
SB
498
499 // Documents.
500 $docsformat = array();
501 $docsformat['html'] = 'html';
502 $docsformat['docx'] = 'docx';
503 $docsformat['odt'] = 'odt';
504 $docsformat['pdf'] = 'pdf';
505 $docsformat['rtf'] = 'rtf';
506 $docsformat['txt'] = 'txt';
507 core_collator::ksort($docsformat, core_collator::SORT_NATURAL);
508
509 $mform->addElement('select', 'documentformat', get_string('docsformat', 'repository_googledocs'), $docsformat);
510 $mform->setDefault('documentformat', $docsformat['rtf']);
511 $mform->setType('documentformat', PARAM_ALPHANUM);
512
513 // Drawing.
514 $drawingformat = array();
515 $drawingformat['jpeg'] = 'jpeg';
516 $drawingformat['png'] = 'png';
517 $drawingformat['svg'] = 'svg';
518 $drawingformat['pdf'] = 'pdf';
519 core_collator::ksort($drawingformat, core_collator::SORT_NATURAL);
520
521 $mform->addElement('select', 'drawingformat', get_string('drawingformat', 'repository_googledocs'), $drawingformat);
522 $mform->setDefault('drawingformat', $drawingformat['pdf']);
523 $mform->setType('drawingformat', PARAM_ALPHANUM);
524
525 // Presentation.
526 $presentationformat = array();
527 $presentationformat['pdf'] = 'pdf';
528 $presentationformat['pptx'] = 'pptx';
529 $presentationformat['txt'] = 'txt';
530 core_collator::ksort($presentationformat, core_collator::SORT_NATURAL);
531
532 $mform->addElement('select', 'presentationformat', get_string('presentationformat', 'repository_googledocs'), $presentationformat);
533 $mform->setDefault('presentationformat', $presentationformat['pptx']);
534 $mform->setType('presentationformat', PARAM_ALPHANUM);
535
536 // Spreadsheet.
537 $spreadsheetformat = array();
538 $spreadsheetformat['csv'] = 'csv';
539 $spreadsheetformat['ods'] = 'ods';
540 $spreadsheetformat['pdf'] = 'pdf';
541 $spreadsheetformat['xlsx'] = 'xlsx';
542 core_collator::ksort($spreadsheetformat, core_collator::SORT_NATURAL);
543
544 $mform->addElement('select', 'spreadsheetformat', get_string('spreadsheetformat', 'repository_googledocs'), $spreadsheetformat);
545 $mform->setDefault('spreadsheetformat', $spreadsheetformat['xlsx']);
546 $mform->setType('spreadsheetformat', PARAM_ALPHANUM);
4560fd1b 547 }
6667f9e4 548}
4560fd1b 549// Icon from: http://www.iconspedia.com/icon/google-2706.html.