MDL-58128 googledocs: Don't copy files
[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
3425813f
FM
188 /**
189 * List the files and folders.
190 *
191 * @param string $path path to browse.
192 * @param string $page page to browse.
193 * @return array of result.
194 */
6667f9e4 195 public function get_listing($path='', $page = '') {
3425813f
FM
196 if (empty($path)) {
197 $path = $this->build_node_path('root', get_string('pluginname', 'repository_googledocs'));
198 }
199
200 // We analyse the path to extract what to browse.
201 $trail = explode('/', $path);
202 $uri = array_pop($trail);
203 list($id, $name) = $this->explode_node_path($uri);
204
205 // Handle the special keyword 'search', which we defined in self::search() so that
206 // we could set up a breadcrumb in the search results. In any other case ID would be
207 // 'root' which is a special keyword set up by Google, or a parent (folder) ID.
208 if ($id === 'search') {
209 return $this->search($name);
210 }
211
212 // Query the Drive.
213 $q = "'" . str_replace("'", "\'", $id) . "' in parents";
214 $q .= ' AND trashed = false';
215 $results = $this->query($q, $path);
6667f9e4 216
217 $ret = array();
218 $ret['dynload'] = true;
989e14fe 219 $ret['defaultreturntype'] = $this->default_returntype();
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');
989e14fe 269 $fields = "files(id,name,mimeType,webContentLink,webViewLink,fileExtension,modifiedTime,size,thumbnailLink,iconLink)";
0e59638b 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 $gfiles = isset($response->files) ? $response->files : array();
288 foreach ($gfiles as $gfile) {
289 if ($gfile->mimeType == 'application/vnd.google-apps.folder') {
3425813f 290 // This is a folder.
0e59638b
DW
291 $folders[$gfile->name . $gfile->id] = array(
292 'title' => $gfile->name,
293 'path' => $this->build_node_path($gfile->id, $gfile->name, $path),
294 'date' => strtotime($gfile->modifiedTime),
663640f5 295 'thumbnail' => $OUTPUT->image_url(file_folder_icon(64))->out(false),
3425813f
FM
296 'thumbnail_height' => 64,
297 'thumbnail_width' => 64,
298 'children' => array()
299 );
300 } else {
301 // This is a file.
8ece1d70
DW
302 $link = isset($gfile->webViewLink) ? $gfile->webViewLink : '';
303 if (empty($link)) {
304 $link = isset($gfile->webContentLink) ? $gfile->webContentLink : '';
305 }
0e59638b
DW
306 if (isset($gfile->fileExtension)) {
307 // The file has an extension, therefore we can download it.
989e14fe 308 $source = json_encode(['id' => $gfile->id, 'exportformat' => 'download', 'link' => $link]);
0e59638b 309 $title = $gfile->name;
3425813f
FM
310 } else {
311 // The file is probably a Google Doc file, we get the corresponding export link.
312 // This should be improved by allowing the user to select the type of export they'd like.
0e59638b 313 $type = str_replace('application/vnd.google-apps.', '', $gfile->mimeType);
3425813f 314 $title = '';
0e59638b 315 $exporttype = '';
15a5c4b2
SB
316 $types = get_mimetypes_array();
317
3425813f
FM
318 switch ($type){
319 case 'document':
15a5c4b2 320 $ext = $config->documentformat;
989e14fe 321 $title = $gfile->name . '.'. $ext;
15a5c4b2
SB
322 if ($ext === 'rtf') {
323 // Moodle user 'text/rtf' as the MIME type for RTF files.
324 // Google uses 'application/rtf' for the same type of file.
325 // See https://developers.google.com/drive/v3/web/manage-downloads.
0e59638b 326 $exporttype = 'application/rtf';
15a5c4b2 327 } else {
0e59638b 328 $exporttype = $types[$ext]['type'];
15a5c4b2 329 }
3425813f
FM
330 break;
331 case 'presentation':
15a5c4b2 332 $ext = $config->presentationformat;
989e14fe 333 $title = $gfile->name . '.'. $ext;
0e59638b 334 $exporttype = $types[$ext]['type'];
3425813f
FM
335 break;
336 case 'spreadsheet':
15a5c4b2 337 $ext = $config->spreadsheetformat;
989e14fe 338 $title = $gfile->name . '.'. $ext;
0e59638b 339 $exporttype = $types[$ext]['type'];
15a5c4b2
SB
340 break;
341 case 'drawing':
342 $ext = $config->drawingformat;
989e14fe 343 $title = $gfile->name . '.'. $ext;
0e59638b 344 $exporttype = $types[$ext]['type'];
3425813f
FM
345 break;
346 }
347 // Skips invalid/unknown types.
0e59638b 348 if (empty($title)) {
3425813f
FM
349 continue;
350 }
989e14fe 351 $source = json_encode(['id' => $gfile->id, 'exportformat' => $exporttype, 'link' => $link]);
3425813f 352 }
0e59638b 353 // Adds the file to the file list. Using the itemId along with the name as key
3425813f 354 // of the array because Google Drive allows files with identical names.
0e59638b
DW
355 $thumb = '';
356 if (isset($gfile->thumbnailLink)) {
357 $thumb = $gfile->thumbnailLink;
358 } else if (isset($gfile->iconLink)) {
359 $thumb = $gfile->iconLink;
360 }
361 $files[$title . $gfile->id] = array(
3425813f
FM
362 'title' => $title,
363 'source' => $source,
0e59638b
DW
364 'date' => strtotime($gfile->modifiedTime),
365 'size' => isset($gfile->size) ? $gfile->size : null,
366 'thumbnail' => $thumb,
3425813f
FM
367 'thumbnail_height' => 64,
368 'thumbnail_width' => 64,
3425813f 369 );
3425813f
FM
370 }
371 }
372
373 // Filter and order the results.
374 $files = array_filter($files, array($this, 'filter'));
2f1e464a
PS
375 core_collator::ksort($files, core_collator::SORT_NATURAL);
376 core_collator::ksort($folders, core_collator::SORT_NATURAL);
3425813f
FM
377 return array_merge(array_values($folders), array_values($files));
378 }
379
380 /**
381 * Logout.
382 *
383 * @return string
384 */
4560fd1b 385 public function logout() {
0e59638b
DW
386 $client = $this->get_user_oauth_client();
387 $client->log_out();
6667f9e4 388 return parent::logout();
389 }
390
3425813f
FM
391 /**
392 * Get a file.
393 *
394 * @param string $reference reference of the file.
395 * @param string $file name to save the file to.
396 * @return string JSON encoded array of information about the file.
397 */
398 public function get_file($reference, $filename = '') {
eb459f71
PS
399 global $CFG;
400
0e59638b 401 $client = $this->get_user_oauth_client();
989e14fe 402 $base = 'https://www.googleapis.com/drive/v3';
0e59638b 403
989e14fe
DW
404 $source = json_decode($reference);
405
406 if ($source->exportformat == 'download') {
407 $params = ['alt' => 'media'];
408 $sourceurl = new moodle_url($base . '/files/' . $source->id, $params);
409 $source = $sourceurl->out(false);
410 } else {
411 $params = ['mimeType' => $source->exportformat];
412 $sourceurl = new moodle_url($base . '/files/' . $source->id . '/export', $params);
413 $source = $sourceurl->out(false);
414 }
415
416 // We use download_one and not the rest API because it has special timeouts etc.
0e59638b
DW
417 $path = $this->prepare_file($filename);
418 $options = ['filepath' => $path, 'timeout' => 15, 'followlocation' => true, 'maxredirs' => 5];
989e14fe 419 $result = $client->download_one($source, null, $options);
0e59638b
DW
420
421 if ($result) {
422 @chmod($path, $CFG->filepermissions);
423 return array(
424 'path' => $path,
425 'url' => $reference
426 );
ec7e998f 427 }
3425813f
FM
428 throw new repository_exception('cannotdownload', 'repository');
429 }
430
431 /**
432 * Prepare file reference information.
433 *
434 * We are using this method to clean up the source to make sure that it
435 * is a valid source.
436 *
437 * @param string $source of the file.
438 * @return string file reference.
439 */
440 public function get_file_reference($source) {
989e14fe
DW
441 // We could do some magic upgrade code here.
442 return $source;
41076c58 443 }
6667f9e4 444
3425813f
FM
445 /**
446 * What kind of files will be in this repository?
447 *
448 * @return array return '*' means this repository support any files, otherwise
449 * return mimetypes of files, it can be an array
450 */
41076c58 451 public function supported_filetypes() {
4560fd1b 452 return '*';
41076c58 453 }
3425813f
FM
454
455 /**
456 * Tells how the file can be picked from this repository.
457 *
458 * Maximum value is FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE.
459 *
460 * @return int
461 */
41076c58 462 public function supported_returntypes() {
989e14fe
DW
463 // We can only support references if the system account is connected.
464 if (!empty($this->issuer) && $this->issuer->is_system_account_connected()) {
465 $setting = get_config('googledocs', 'supportedreturntypes');
466 if ($setting == 'internal') {
467 return FILE_INTERNAL;
468 } else if ($setting == 'external') {
469 return FILE_REFERENCE;
470 } else {
471 return FILE_REFERENCE | FILE_INTERNAL;
472 }
473 } else {
474 return FILE_INTERNAL;
475 }
476 }
477
478 /**
479 * Which return type should be selected by default.
480 *
481 * @return int
482 */
483 public function default_returntype() {
484 $setting = get_config('googledocs', 'defaultreturntype');
485 $supported = get_config('googledocs', 'supportedreturntypes');
486 if (($setting == FILE_INTERNAL && $supported != 'external') || $supported == 'internal') {
487 return FILE_INTERNAL;
488 } else {
489 return FILE_REFERENCE;
490 }
41076c58 491 }
4560fd1b 492
3425813f
FM
493 /**
494 * Return names of the general options.
495 * By default: no general option name.
496 *
497 * @return array
498 */
4560fd1b 499 public static function get_type_option_names() {
0e59638b 500 return array('issuerid', 'pluginname',
15a5c4b2 501 'documentformat', 'drawingformat',
989e14fe
DW
502 'presentationformat', 'spreadsheetformat',
503 'defaultreturntype', 'supportedreturntypes');
4560fd1b
DP
504 }
505
0e59638b
DW
506 /**
507 * Store the access token.
508 */
509 public function callback() {
510 $client = $this->get_user_oauth_client();
989e14fe 511 // This will upgrade to an access token if we have an authorization code and save the access token in the session.
0e59638b
DW
512 $client->is_logged_in();
513 }
514
989e14fe
DW
515 /**
516 * Repository method to serve the referenced file
517 *
518 * @see send_stored_file
519 *
520 * @param stored_file $storedfile the file that contains the reference
521 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
522 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
523 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
524 * @param array $options additional options affecting the file serving
525 */
526 public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
989e14fe
DW
527 $source = json_decode($storedfile->get_reference());
528
529 if ($source->link) {
530 header('Location: ' . $source->link);
531 } else {
532 $details = 'File is missing source link';
533 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
534 }
535 }
536
537 /**
538 * List the permissions on a file.
539 * @param \core\oauth2\client $client Authenticated client.
540 * @param string $fileid The id of the file.
541 *
542 * @return array
543 */
544 protected function list_file_permissions(\repository_googledocs\rest $client, $fileid) {
545 $fields = "permissions(id,type,emailAddress,role,allowFileDiscovery,displayName)";
546 return $client->call('list_permissions', ['fileid' => $fileid]);
547 }
548
549 /**
550 * See if a folder exists within a folder
551 *
552 * @param \core\oauth2\client $client Authenticated client.
553 * @param string $foldername The folder we are looking for.
554 * @param string $parentid The parent folder we are looking in.
555 *
556 * @return string|boolean The file id if it exists or false.
557 */
558 protected function folder_exists_in_folder(\repository_googledocs\rest $client, $foldername, $parentid) {
559 $q = '\'' . addslashes($parentid) . '\' in parents and trashed = false and name = \'' . addslashes($foldername). '\'';
560 $fields = 'files(id, name)';
561 $params = [ 'q' => $q, 'fields' => $fields];
562 $response = $client->call('list', $params);
563 $missing = true;
564 foreach ($response->files as $child) {
565 if ($child->name == $foldername) {
566 return $child->id;
567 }
568 }
569 return false;
570 }
571
572 /**
573 * Create a folder within a folder
574 *
575 * @param \core\oauth2\client $client Authenticated client.
576 * @param string $foldername The folder we are creating.
577 * @param string $parentid The parent folder we are creating in.
578 *
579 * @return string The file id of the new folder.
580 */
581 protected function create_folder_in_folder(\repository_googledocs\rest $client, $foldername, $parentid) {
582 $fields = 'id';
583 $params = ['fields' => $fields];
584 $folder = ['mimeType' => 'application/vnd.google-apps.folder', 'name' => $foldername, 'parents' => [$parentid]];
585 $created = $client->call('create', $params, json_encode($folder));
586 if (empty($created->id)) {
587 $details = 'Cannot create folder:' . $foldername;
588 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
589 }
590 return $created->id;
591 }
592
593 /**
594 * Get capabilities for a file.
595 *
596 * @param \core\oauth2\client $client Authenticated client.
597 * @param string $fileid The file we are checking.
598 *
599 * @return stdClass The file info with capabilities.
600 */
601 protected function get_file_capabilities(\repository_googledocs\rest $client, $fileid) {
602 $fields = "id,capabilities,writersCanShare";
603 $params = [
604 'fileid' => $fileid,
605 'fields' => $fields
606 ];
607 return $client->call('get', $params);
608 }
609
8ece1d70
DW
610 /**
611 * Get simple file info for humans.
612 *
613 * @param \core\oauth2\client $client Authenticated client.
614 * @param string $fileid The file we are querying.
615 *
616 * @return stdClass
617 */
618 protected function get_file_summary(\repository_googledocs\rest $client, $fileid) {
619 $fields = "id,name,owners";
620 $params = [
621 'fileid' => $fileid,
622 'fields' => $fields
623 ];
624 return $client->call('get', $params);
625 }
626
989e14fe
DW
627 /**
628 * Update file owner.
629 *
630 * @param \core\oauth2\client $client Authenticated client.
631 * @param string $fileid The file we are updating.
632 *
633 * @return boolean Did it work?
634 */
635 protected function update_file_owner(\repository_googledocs\rest $client, $fileid, $owneremail) {
636 $updateowner = [
637 'emailAddress' => $owneremail,
638 'role' => 'owner',
639 'type' => 'user'
640 ];
641 $params = ['fileid' => $fileid, 'transferOwnership' => 'true'];
642 try {
643 $response = $client->call('create_permission', $params, json_encode($updateowner));
644 } catch (\core\oauth2\rest_exception $re) {
645 return false;
646 }
647 return !empty($response->id);
648 }
649
650 /**
651 * Copy a file and return the new file details. A side effect of the copy
652 * is that the owner will be the account authenticated with this oauth client.
653 *
654 * @param \core\oauth2\client $client Authenticated client.
655 * @param string $fileid The file we are copying.
656 *
657 * @return stdClass file details.
658 */
659 protected function copy_file(\repository_googledocs\rest $client, $fileid) {
660 $fields = "id,name,mimeType,webContentLink,webViewLink,size,thumbnailLink,iconLink";
661 $params = [
662 'fileid' => $fileid,
663 'fields' => $fields
664 ];
665 $fileinfo = $client->call('copy', $params, ' ');
666 if (empty($fileinfo->id)) {
667 $details = 'Cannot copy file:' . $fileid;
668 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
669 }
670 return $fileinfo;
671 }
672
673 /**
674 * Add a writer to the permissions on the file.
675 *
676 * @param \core\oauth2\client $client Authenticated client.
677 * @param string $fileid The file we are updating.
678 * @param string $email The email of the writer account to add.
679 * @return boolean
680 */
681 protected function add_writer_to_file($client, $fileid, $email) {
682 $updateeditor = [
683 'emailAddress' => $email,
684 'role' => 'writer',
685 'type' => 'user'
686 ];
8ece1d70 687 $params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false'];
989e14fe
DW
688 $response = $client->call('create_permission', $params, json_encode($updateeditor));
689 if (empty($response->id)) {
690 $details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
691 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
692 }
693 return true;
694 }
695
696 /**
697 * Move from root to folder
698 *
699 * @param \core\oauth2\client $client Authenticated client.
700 * @param string $fileid The file we are updating.
701 * @param string $folderid The id of the folder we are moving to
702 * @return boolean
703 */
704 protected function move_file_from_root_to_folder($client, $fileid, $folderid) {
705 // Set the parent.
706 $params = [
707 'fileid' => $fileid, 'addParents' => $folderid, 'removeParents' => 'root'
708 ];
709 $response = $client->call('update', $params, ' ');
710 if (empty($response->id)) {
711 $details = 'Cannot move the file to a folder: ' . $fileid;
712 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
713 }
714 return true;
715 }
716
717 /**
718 * Prevent writers from sharing.
719 *
720 * @param \core\oauth2\client $client Authenticated client.
721 * @param string $fileid The file we are updating.
722 * @return boolean
723 */
724 protected function prevent_writers_from_sharing_file($client, $fileid) {
725 // We don't want anyone but Moodle to change the sharing settings.
726 $params = [
727 'fileid' => $fileid
728 ];
729 $update = [
730 'writersCanShare' => false
731 ];
732 $response = $client->call('update', $params, json_encode($update));
733 if (empty($response->id)) {
734 $details = 'Cannot prevent writers from sharing document: ' . $fileid;
735 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
736 }
737 return true;
738 }
739
740 /**
741 * Allow anyone with the link to read the file.
742 *
743 * @param \core\oauth2\client $client Authenticated client.
744 * @param string $fileid The file we are updating.
745 * @return boolean
746 */
747 protected function set_file_sharing_anyone_with_link_can_read($client, $fileid) {
748 $updateread = [
749 'type' => 'anyone',
750 'role' => 'reader',
751 'allowFileDiscovery' => 'false'
752 ];
753 $params = ['fileid' => $fileid];
754 $response = $client->call('create_permission', $params, json_encode($updateread));
755 if (empty($response->id) || $response->id != 'anyoneWithLink') {
756 $details = 'Cannot update link sharing for the document: ' . $fileid;
757 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
758 }
759 return true;
760 }
761
762 /**
763 * Called when a file is selected as a "link".
764 * Invoked at MOODLE/repository/repository_ajax.php
765 *
766 * @param string $reference this reference is generated by
767 * repository::get_file_reference()
768 * @param context $context the target context for this new file.
769 * @return string $modifiedreference (final one before saving to DB)
770 */
771 public function reference_file_selected($reference, $context) {
772 // What we need to do here is transfer ownership to the system user (or copy)
773 // then set the permissions so anyone with the share link can view,
774 // finally update the reference to contain the share link if it was not
775 // already there (and point to new file id if we copied).
776 $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
777
778 if ($systemauth === false) {
779 $details = 'Cannot connect as system user';
780 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
781 }
782 $systemuserinfo = $systemauth->get_userinfo();
783 $systemuseremail = $systemuserinfo['email'];
784
785 $source = json_decode($reference);
786
787 $userauth = $this->get_user_oauth_client();
788 if ($userauth === false) {
789 $details = 'Cannot connect as current user';
790 throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
791 }
792 $userinfo = $userauth->get_userinfo();
793 $useremail = $userinfo['email'];
794
795 $userservice = new repository_googledocs\rest($userauth);
796 $systemservice = new repository_googledocs\rest($systemauth);
797
798 // Get the list of existing permissions so we can see if the owner is already the system account,
799 // and whether we need to update the link sharing options.
800 $permissions = $this->list_file_permissions($userservice, $source->id);
801
802 $readshareupdaterequired = true;
803 $ownerupdaterequired = true;
804 foreach ($permissions->permissions as $permission) {
989e14fe
DW
805 if ($permission->id == 'anyoneWithLink' &&
806 $permission->type == 'anyone' &&
807 $permission->role == 'reader' &&
808 $permission->allowFileDiscovery == false) {
809 $readshareupdaterequired = false;
810 }
811 }
812
8ece1d70
DW
813 // Add Moodle as writer.
814 $this->add_writer_to_file($userservice, $source->id, $systemuseremail);
815
989e14fe
DW
816 // Now move it to a sensible folder.
817 $contextlist = array_reverse($context->get_parent_contexts(true));
818
819 $parentid = 'root';
820 foreach ($contextlist as $context) {
821 // Make sure a folder exists here.
8ece1d70
DW
822 $foldername = $context->get_context_name();
823
989e14fe
DW
824 $folderid = $this->folder_exists_in_folder($systemservice, $foldername, $parentid);
825 if ($folderid !== false) {
826 $parentid = $folderid;
827 } else {
828 // Create it.
829 $parentid = $this->create_folder_in_folder($systemservice, $foldername, $parentid);
830 }
831 }
832
8ece1d70 833 $this->move_file_from_root_to_folder($systemservice, $source->id, $parentid);
989e14fe 834
8ece1d70
DW
835 if ($readshareupdaterequired) {
836 $this->set_file_sharing_anyone_with_link_can_read($systemservice, $source->id);
837 }
989e14fe 838
8ece1d70
DW
839 // We did not update the reference at all.
840 return $reference;
841 }
989e14fe 842
8ece1d70
DW
843 /**
844 * Get human readable file info from a the reference.
845 *
846 * @param string $reference
847 * @param int $filestatus
848 */
849 public function get_reference_details($reference, $filestatus = 0) {
850 if (empty($reference)) {
851 return get_string('unknownsource', 'repository');
989e14fe 852 }
8ece1d70
DW
853 $source = json_decode($reference);
854 $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
989e14fe 855
8ece1d70
DW
856 if ($systemauth === false) {
857 return '';
989e14fe 858 }
8ece1d70
DW
859 $systemservice = new repository_googledocs\rest($systemauth);
860 $info = $this->get_file_summary($systemservice, $source->id);
989e14fe 861
8ece1d70
DW
862 $owner = '';
863 if (!empty($info->owners[0]->displayName)) {
864 $owner = $info->owners[0]->displayName;
865 }
866 if ($owner) {
867 return get_string('owner', 'repository_googledocs', $owner);
868 } else {
869 return $info->name;
989e14fe
DW
870 }
871 }
872
3425813f
FM
873 /**
874 * Edit/Create Admin Settings Moodle form.
875 *
876 * @param moodleform $mform Moodle form (passed by reference).
877 * @param string $classname repository class name.
878 */
4560fd1b 879 public static function type_config_form($mform, $classname = 'repository') {
989e14fe
DW
880 $url = new moodle_url('/admin/tool/oauth2/issuers.php');
881 $url = $url->out();
3425813f 882
0e59638b 883 $mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_googledocs', $url));
4560fd1b
DP
884
885 parent::type_config_form($mform);
0e59638b
DW
886 $options = [];
887 $issuers = \core\oauth2\api::get_all_issuers();
888
889 foreach ($issuers as $issuer) {
890 $options[$issuer->get('id')] = s($issuer->get('name'));
891 }
989e14fe
DW
892
893 $strrequired = get_string('required');
894
0e59638b
DW
895 $mform->addElement('select', 'issuerid', get_string('issuer', 'repository_googledocs'), $options);
896 $mform->addHelpButton('issuerid', 'issuer', 'repository_googledocs');
897 $mform->addRule('issuerid', $strrequired, 'required', null, 'client');
4560fd1b 898
989e14fe
DW
899 $mform->addElement('static', null, '', get_string('fileoptions', 'repository_googledocs'));
900 $choices = [
901 'internal' => get_string('internal', 'repository_googledocs'),
902 'external' => get_string('external', 'repository_googledocs'),
903 'both' => get_string('both', 'repository_googledocs')
904 ];
905 $mform->addElement('select', 'supportedreturntypes', get_string('supportedreturntypes', 'repository_googledocs'), $choices);
906
907 $choices = [
908 FILE_INTERNAL => get_string('internal', 'repository_googledocs'),
909 FILE_REFERENCE => get_string('external', 'repository_googledocs'),
910 ];
911 $mform->addElement('select', 'defaultreturntype', get_string('defaultreturntype', 'repository_googledocs'), $choices);
15a5c4b2 912
0e59638b 913 $mform->addElement('static', null, '', get_string('importformat', 'repository_googledocs'));
15a5c4b2
SB
914
915 // Documents.
916 $docsformat = array();
917 $docsformat['html'] = 'html';
918 $docsformat['docx'] = 'docx';
919 $docsformat['odt'] = 'odt';
920 $docsformat['pdf'] = 'pdf';
921 $docsformat['rtf'] = 'rtf';
922 $docsformat['txt'] = 'txt';
923 core_collator::ksort($docsformat, core_collator::SORT_NATURAL);
924
925 $mform->addElement('select', 'documentformat', get_string('docsformat', 'repository_googledocs'), $docsformat);
926 $mform->setDefault('documentformat', $docsformat['rtf']);
927 $mform->setType('documentformat', PARAM_ALPHANUM);
928
929 // Drawing.
930 $drawingformat = array();
931 $drawingformat['jpeg'] = 'jpeg';
932 $drawingformat['png'] = 'png';
933 $drawingformat['svg'] = 'svg';
934 $drawingformat['pdf'] = 'pdf';
935 core_collator::ksort($drawingformat, core_collator::SORT_NATURAL);
936
937 $mform->addElement('select', 'drawingformat', get_string('drawingformat', 'repository_googledocs'), $drawingformat);
938 $mform->setDefault('drawingformat', $drawingformat['pdf']);
939 $mform->setType('drawingformat', PARAM_ALPHANUM);
940
941 // Presentation.
942 $presentationformat = array();
943 $presentationformat['pdf'] = 'pdf';
944 $presentationformat['pptx'] = 'pptx';
945 $presentationformat['txt'] = 'txt';
946 core_collator::ksort($presentationformat, core_collator::SORT_NATURAL);
947
989e14fe
DW
948 $str = get_string('presentationformat', 'repository_googledocs');
949 $mform->addElement('select', 'presentationformat', $str, $presentationformat);
15a5c4b2
SB
950 $mform->setDefault('presentationformat', $presentationformat['pptx']);
951 $mform->setType('presentationformat', PARAM_ALPHANUM);
952
953 // Spreadsheet.
954 $spreadsheetformat = array();
955 $spreadsheetformat['csv'] = 'csv';
956 $spreadsheetformat['ods'] = 'ods';
957 $spreadsheetformat['pdf'] = 'pdf';
958 $spreadsheetformat['xlsx'] = 'xlsx';
959 core_collator::ksort($spreadsheetformat, core_collator::SORT_NATURAL);
960
989e14fe
DW
961 $str = get_string('spreadsheetformat', 'repository_googledocs');
962 $mform->addElement('select', 'spreadsheetformat', $str, $spreadsheetformat);
15a5c4b2
SB
963 $mform->setDefault('spreadsheetformat', $spreadsheetformat['xlsx']);
964 $mform->setType('spreadsheetformat', PARAM_ALPHANUM);
4560fd1b 965 }
6667f9e4 966}
989e14fe 967
4560fd1b 968// Icon from: http://www.iconspedia.com/icon/google-2706.html.
989e14fe
DW
969function repository_googledocs_oauth2_system_scopes() {
970 return 'https://www.googleapis.com/auth/drive';
971}