1 |
efrain |
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/>.
|
|
|
16 |
|
|
|
17 |
/**
|
|
|
18 |
* This plugin is used to access Google Drive.
|
|
|
19 |
*
|
|
|
20 |
* @since Moodle 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 . '/filebrowser/file_browser.php');
|
|
|
30 |
|
|
|
31 |
use repository_googledocs\helper;
|
|
|
32 |
use repository_googledocs\googledocs_content_search;
|
|
|
33 |
|
|
|
34 |
/**
|
|
|
35 |
* Google Docs Plugin
|
|
|
36 |
*
|
|
|
37 |
* @since Moodle 2.0
|
|
|
38 |
* @package repository_googledocs
|
|
|
39 |
* @copyright 2009 Dan Poltawski <talktodan@gmail.com>
|
|
|
40 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
41 |
*/
|
|
|
42 |
class repository_googledocs extends repository {
|
|
|
43 |
|
|
|
44 |
/**
|
|
|
45 |
* OAuth 2 client
|
|
|
46 |
* @var \core\oauth2\client
|
|
|
47 |
*/
|
|
|
48 |
private $client = null;
|
|
|
49 |
|
|
|
50 |
/**
|
|
|
51 |
* OAuth 2 Issuer
|
|
|
52 |
* @var \core\oauth2\issuer
|
|
|
53 |
*/
|
|
|
54 |
private $issuer = null;
|
|
|
55 |
|
|
|
56 |
/**
|
|
|
57 |
* Additional scopes required for drive.
|
|
|
58 |
*/
|
|
|
59 |
const SCOPES = 'https://www.googleapis.com/auth/drive';
|
|
|
60 |
|
|
|
61 |
/** @var string Defines the path node identifier for the repository root. */
|
|
|
62 |
const REPOSITORY_ROOT_ID = 'repository_root';
|
|
|
63 |
|
|
|
64 |
/** @var string Defines the path node identifier for the my drive root. */
|
|
|
65 |
const MY_DRIVE_ROOT_ID = 'root';
|
|
|
66 |
|
|
|
67 |
/** @var string Defines the path node identifier for the shared drives root. */
|
|
|
68 |
const SHARED_DRIVES_ROOT_ID = 'shared_drives_root';
|
|
|
69 |
|
|
|
70 |
/** @var string Defines the path node identifier for the content search root. */
|
|
|
71 |
const SEARCH_ROOT_ID = 'search';
|
|
|
72 |
|
|
|
73 |
/**
|
|
|
74 |
* Constructor.
|
|
|
75 |
*
|
|
|
76 |
* @param int $repositoryid repository instance id.
|
|
|
77 |
* @param int|stdClass $context a context id or context object.
|
|
|
78 |
* @param array $options repository options.
|
|
|
79 |
* @param int $readonly indicate this repo is readonly or not.
|
|
|
80 |
* @return void
|
|
|
81 |
*/
|
|
|
82 |
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
|
|
|
83 |
parent::__construct($repositoryid, $context, $options, $readonly = 0);
|
|
|
84 |
|
|
|
85 |
try {
|
|
|
86 |
$this->issuer = \core\oauth2\api::get_issuer(get_config('googledocs', 'issuerid'));
|
|
|
87 |
} catch (dml_missing_record_exception $e) {
|
|
|
88 |
$this->disabled = true;
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
if ($this->issuer && !$this->issuer->get('enabled')) {
|
|
|
92 |
$this->disabled = true;
|
|
|
93 |
}
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
/**
|
|
|
97 |
* Get a cached user authenticated oauth client.
|
|
|
98 |
*
|
|
|
99 |
* @param moodle_url $overrideurl - Use this url instead of the repo callback.
|
|
|
100 |
* @return \core\oauth2\client
|
|
|
101 |
*/
|
|
|
102 |
protected function get_user_oauth_client($overrideurl = false) {
|
|
|
103 |
if ($this->client) {
|
|
|
104 |
return $this->client;
|
|
|
105 |
}
|
|
|
106 |
if ($overrideurl) {
|
|
|
107 |
$returnurl = $overrideurl;
|
|
|
108 |
} else {
|
|
|
109 |
$returnurl = new moodle_url('/repository/repository_callback.php');
|
|
|
110 |
$returnurl->param('callback', 'yes');
|
|
|
111 |
$returnurl->param('repo_id', $this->id);
|
|
|
112 |
$returnurl->param('sesskey', sesskey());
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
$this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, self::SCOPES, true);
|
|
|
116 |
|
|
|
117 |
return $this->client;
|
|
|
118 |
}
|
|
|
119 |
|
|
|
120 |
/**
|
|
|
121 |
* Checks whether the user is authenticate or not.
|
|
|
122 |
*
|
|
|
123 |
* @return bool true when logged in.
|
|
|
124 |
*/
|
|
|
125 |
public function check_login() {
|
|
|
126 |
$client = $this->get_user_oauth_client();
|
|
|
127 |
return $client->is_logged_in();
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
/**
|
|
|
131 |
* Print or return the login form.
|
|
|
132 |
*
|
|
|
133 |
* @return void|array for ajax.
|
|
|
134 |
*/
|
|
|
135 |
public function print_login() {
|
|
|
136 |
$client = $this->get_user_oauth_client();
|
|
|
137 |
$url = $client->get_login_url();
|
|
|
138 |
|
|
|
139 |
if ($this->options['ajax']) {
|
|
|
140 |
$popup = new stdClass();
|
|
|
141 |
$popup->type = 'popup';
|
|
|
142 |
$popup->url = $url->out(false);
|
|
|
143 |
return array('login' => array($popup));
|
|
|
144 |
} else {
|
|
|
145 |
echo '<a target="_blank" href="'.$url->out(false).'">'.get_string('login', 'repository').'</a>';
|
|
|
146 |
}
|
|
|
147 |
}
|
|
|
148 |
|
|
|
149 |
/**
|
|
|
150 |
* Print the login in a popup.
|
|
|
151 |
*
|
|
|
152 |
* @param array|null $attr Custom attributes to be applied to popup div.
|
|
|
153 |
*/
|
|
|
154 |
public function print_login_popup($attr = null) {
|
|
|
155 |
global $OUTPUT, $PAGE;
|
|
|
156 |
|
|
|
157 |
$client = $this->get_user_oauth_client(false);
|
|
|
158 |
$url = new moodle_url($client->get_login_url());
|
|
|
159 |
$state = $url->get_param('state') . '&reloadparent=true';
|
|
|
160 |
$url->param('state', $state);
|
|
|
161 |
|
|
|
162 |
$PAGE->set_pagelayout('embedded');
|
|
|
163 |
echo $OUTPUT->header();
|
|
|
164 |
|
|
|
165 |
$repositoryname = get_string('pluginname', 'repository_googledocs');
|
|
|
166 |
|
|
|
167 |
$button = new single_button(
|
|
|
168 |
$url,
|
|
|
169 |
get_string('logintoaccount', 'repository', $repositoryname),
|
|
|
170 |
'post',
|
|
|
171 |
single_button::BUTTON_PRIMARY
|
|
|
172 |
);
|
|
|
173 |
$button->add_action(new popup_action('click', $url, 'Login'));
|
|
|
174 |
$button->class = 'mdl-align';
|
|
|
175 |
$button = $OUTPUT->render($button);
|
|
|
176 |
echo html_writer::div($button, '', $attr);
|
|
|
177 |
|
|
|
178 |
echo $OUTPUT->footer();
|
|
|
179 |
}
|
|
|
180 |
|
|
|
181 |
/**
|
|
|
182 |
* Build the breadcrumb from a path.
|
|
|
183 |
*
|
|
|
184 |
* @deprecated since Moodle 3.11.
|
|
|
185 |
* @param string $path to create a breadcrumb from.
|
|
|
186 |
* @return array containing name and path of each crumb.
|
|
|
187 |
*/
|
|
|
188 |
protected function build_breadcrumb($path) {
|
|
|
189 |
debugging('The function build_breadcrumb() is deprecated, please use get_navigation() from the ' .
|
|
|
190 |
'googledocs repository content classes instead.', DEBUG_DEVELOPER);
|
|
|
191 |
|
|
|
192 |
$bread = explode('/', $path);
|
|
|
193 |
$crumbtrail = '';
|
|
|
194 |
foreach ($bread as $crumb) {
|
|
|
195 |
list($id, $name) = $this->explode_node_path($crumb);
|
|
|
196 |
$name = empty($name) ? $id : $name;
|
|
|
197 |
$breadcrumb[] = array(
|
|
|
198 |
'name' => $name,
|
|
|
199 |
'path' => $this->build_node_path($id, $name, $crumbtrail)
|
|
|
200 |
);
|
|
|
201 |
$tmp = end($breadcrumb);
|
|
|
202 |
$crumbtrail = $tmp['path'];
|
|
|
203 |
}
|
|
|
204 |
return $breadcrumb;
|
|
|
205 |
}
|
|
|
206 |
|
|
|
207 |
/**
|
|
|
208 |
* Generates a safe path to a node.
|
|
|
209 |
*
|
|
|
210 |
* Typically, a node will be id|Name of the node.
|
|
|
211 |
*
|
|
|
212 |
* @deprecated since Moodle 3.11.
|
|
|
213 |
* @param string $id of the node.
|
|
|
214 |
* @param string $name of the node, will be URL encoded.
|
|
|
215 |
* @param string $root to append the node on, must be a result of this function.
|
|
|
216 |
* @return string path to the node.
|
|
|
217 |
*/
|
|
|
218 |
protected function build_node_path($id, $name = '', $root = '') {
|
|
|
219 |
debugging('The function build_node_path() is deprecated, please use ' .
|
|
|
220 |
'\repository_googledocs\helper::build_node_path() instead.', DEBUG_DEVELOPER);
|
|
|
221 |
|
|
|
222 |
$path = $id;
|
|
|
223 |
if (!empty($name)) {
|
|
|
224 |
$path .= '|' . urlencode($name);
|
|
|
225 |
}
|
|
|
226 |
if (!empty($root)) {
|
|
|
227 |
$path = trim($root, '/') . '/' . $path;
|
|
|
228 |
}
|
|
|
229 |
return $path;
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
/**
|
|
|
233 |
* Returns information about a node in a path.
|
|
|
234 |
*
|
|
|
235 |
* @deprecated since Moodle 3.11.
|
|
|
236 |
* @see self::build_node_path()
|
|
|
237 |
* @param string $node to extrat information from.
|
|
|
238 |
* @return array about the node.
|
|
|
239 |
*/
|
|
|
240 |
protected function explode_node_path($node) {
|
|
|
241 |
debugging('The function explode_node_path() is deprecated, please use ' .
|
|
|
242 |
'\repository_googledocs\helper::explode_node_path() instead.', DEBUG_DEVELOPER);
|
|
|
243 |
|
|
|
244 |
if (strpos($node, '|') !== false) {
|
|
|
245 |
list($id, $name) = explode('|', $node, 2);
|
|
|
246 |
$name = urldecode($name);
|
|
|
247 |
} else {
|
|
|
248 |
$id = $node;
|
|
|
249 |
$name = '';
|
|
|
250 |
}
|
|
|
251 |
$id = urldecode($id);
|
|
|
252 |
return array(
|
|
|
253 |
|
|
|
254 |
1 => $name,
|
|
|
255 |
'id' => $id,
|
|
|
256 |
'name' => $name
|
|
|
257 |
);
|
|
|
258 |
}
|
|
|
259 |
|
|
|
260 |
/**
|
|
|
261 |
* List the files and folders.
|
|
|
262 |
*
|
|
|
263 |
* @param string $path path to browse.
|
|
|
264 |
* @param string $page page to browse.
|
|
|
265 |
* @return array of result.
|
|
|
266 |
*/
|
|
|
267 |
public function get_listing($path='', $page = '') {
|
|
|
268 |
if (empty($path)) {
|
|
|
269 |
$pluginname = get_string('pluginname', 'repository_googledocs');
|
|
|
270 |
$path = helper::build_node_path('repository_root', $pluginname);
|
|
|
271 |
}
|
|
|
272 |
|
|
|
273 |
if (!$this->issuer->get('enabled')) {
|
|
|
274 |
// Empty list of files for disabled repository.
|
|
|
275 |
return [
|
|
|
276 |
'dynload' => false,
|
|
|
277 |
'list' => [],
|
|
|
278 |
'nologin' => true,
|
|
|
279 |
];
|
|
|
280 |
}
|
|
|
281 |
|
|
|
282 |
// We analyse the path to extract what to browse.
|
|
|
283 |
$trail = explode('/', $path);
|
|
|
284 |
$uri = array_pop($trail);
|
|
|
285 |
list($id, $name) = helper::explode_node_path($uri);
|
|
|
286 |
$service = new repository_googledocs\rest($this->get_user_oauth_client());
|
|
|
287 |
|
|
|
288 |
// Define the content class object and query which will be used to get the contents for this path.
|
|
|
289 |
if ($id === self::SEARCH_ROOT_ID) {
|
|
|
290 |
// The special keyword 'search' is the ID of the node. This is possible as we can set up a breadcrumb in
|
|
|
291 |
// the search results. Therefore, we should use the content search object to get the results from the
|
|
|
292 |
// previously performed search.
|
|
|
293 |
$contentobj = new googledocs_content_search($service, $path);
|
|
|
294 |
// We need to deconstruct the node name in order to obtain the search term and use it as a query.
|
|
|
295 |
$query = str_replace(get_string('searchfor', 'repository_googledocs'), '', $name);
|
|
|
296 |
$query = trim(str_replace("'", "", $query));
|
|
|
297 |
} else {
|
|
|
298 |
// Otherwise, return and use the appropriate (based on the path) content browser object.
|
|
|
299 |
$contentobj = helper::get_browser($service, $path);
|
|
|
300 |
// Use the node ID as a query.
|
|
|
301 |
$query = $id;
|
|
|
302 |
}
|
|
|
303 |
|
|
|
304 |
return [
|
|
|
305 |
'dynload' => true,
|
|
|
306 |
'defaultreturntype' => $this->default_returntype(),
|
|
|
307 |
'path' => $contentobj->get_navigation(),
|
|
|
308 |
'list' => $contentobj->get_content_nodes($query, [$this, 'filter']),
|
|
|
309 |
'manage' => 'https://drive.google.com/',
|
|
|
310 |
];
|
|
|
311 |
}
|
|
|
312 |
|
|
|
313 |
/**
|
|
|
314 |
* Search throughout the Google Drive.
|
|
|
315 |
*
|
|
|
316 |
* @param string $searchtext text to search for.
|
|
|
317 |
* @param int $page search page.
|
|
|
318 |
* @return array of results.
|
|
|
319 |
*/
|
|
|
320 |
public function search($searchtext, $page = 0) {
|
|
|
321 |
// Construct the path to the repository root.
|
|
|
322 |
$pluginname = get_string('pluginname', 'repository_googledocs');
|
|
|
323 |
$rootpath = helper::build_node_path(self::REPOSITORY_ROOT_ID, $pluginname);
|
|
|
324 |
// Construct the path to the search results node.
|
|
|
325 |
// Currently, when constructing the search node name, the search term is concatenated to the lang string.
|
|
|
326 |
// This was done deliberately so that we can easily and accurately obtain the search term from the search node
|
|
|
327 |
// name later when navigating to the search results through the breadcrumb navigation.
|
|
|
328 |
$name = get_string('searchfor', 'repository_googledocs') . " '{$searchtext}'";
|
|
|
329 |
$path = helper::build_node_path(self::SEARCH_ROOT_ID, $name, $rootpath);
|
|
|
330 |
|
|
|
331 |
$service = new repository_googledocs\rest($this->get_user_oauth_client());
|
|
|
332 |
$searchobj = new googledocs_content_search($service, $path);
|
|
|
333 |
|
|
|
334 |
return [
|
|
|
335 |
'dynload' => true,
|
|
|
336 |
'path' => $searchobj->get_navigation(),
|
|
|
337 |
'list' => $searchobj->get_content_nodes($searchtext, [$this, 'filter']),
|
|
|
338 |
'manage' => 'https://drive.google.com/',
|
|
|
339 |
];
|
|
|
340 |
}
|
|
|
341 |
|
|
|
342 |
/**
|
|
|
343 |
* Query Google Drive for files and folders using a search query.
|
|
|
344 |
*
|
|
|
345 |
* Documentation about the query format can be found here:
|
|
|
346 |
* https://developers.google.com/drive/search-parameters
|
|
|
347 |
*
|
|
|
348 |
* This returns a list of files and folders with their details as they should be
|
|
|
349 |
* formatted and returned by functions such as get_listing() or search().
|
|
|
350 |
*
|
|
|
351 |
* @deprecated since Moodle 3.11.
|
|
|
352 |
* @param string $q search query as expected by the Google API.
|
|
|
353 |
* @param string $path parent path of the current files, will not be used for the query.
|
|
|
354 |
* @param int $page page.
|
|
|
355 |
* @return array of files and folders.
|
|
|
356 |
*/
|
|
|
357 |
protected function query($q, $path = null, $page = 0) {
|
|
|
358 |
debugging('The function query() is deprecated, please use get_content_nodes() from the ' .
|
|
|
359 |
'googledocs repository content classes instead.', DEBUG_DEVELOPER);
|
|
|
360 |
|
|
|
361 |
global $OUTPUT;
|
|
|
362 |
|
|
|
363 |
$files = array();
|
|
|
364 |
$folders = array();
|
|
|
365 |
$config = get_config('googledocs');
|
|
|
366 |
$fields = "files(id,name,mimeType,webContentLink,webViewLink,fileExtension,modifiedTime,size,thumbnailLink,iconLink)";
|
|
|
367 |
$params = array('q' => $q, 'fields' => $fields, 'spaces' => 'drive');
|
|
|
368 |
|
|
|
369 |
try {
|
|
|
370 |
// Retrieving files and folders.
|
|
|
371 |
$client = $this->get_user_oauth_client();
|
|
|
372 |
$service = new repository_googledocs\rest($client);
|
|
|
373 |
|
|
|
374 |
$response = $service->call('list', $params);
|
|
|
375 |
} catch (Exception $e) {
|
|
|
376 |
if ($e->getCode() == 403 && strpos($e->getMessage(), 'Access Not Configured') !== false) {
|
|
|
377 |
// This is raised when the service Drive API has not been enabled on Google APIs control panel.
|
|
|
378 |
throw new repository_exception('servicenotenabled', 'repository_googledocs');
|
|
|
379 |
} else {
|
|
|
380 |
throw $e;
|
|
|
381 |
}
|
|
|
382 |
}
|
|
|
383 |
|
|
|
384 |
$gfiles = isset($response->files) ? $response->files : array();
|
|
|
385 |
foreach ($gfiles as $gfile) {
|
|
|
386 |
if ($gfile->mimeType == 'application/vnd.google-apps.folder') {
|
|
|
387 |
// This is a folder.
|
|
|
388 |
$folders[$gfile->name . $gfile->id] = array(
|
|
|
389 |
'title' => $gfile->name,
|
|
|
390 |
'path' => $this->build_node_path($gfile->id, $gfile->name, $path),
|
|
|
391 |
'date' => strtotime($gfile->modifiedTime),
|
|
|
392 |
'thumbnail' => $OUTPUT->image_url(file_folder_icon())->out(false),
|
|
|
393 |
'thumbnail_height' => 64,
|
|
|
394 |
'thumbnail_width' => 64,
|
|
|
395 |
'children' => array()
|
|
|
396 |
);
|
|
|
397 |
} else {
|
|
|
398 |
// This is a file.
|
|
|
399 |
$link = isset($gfile->webViewLink) ? $gfile->webViewLink : '';
|
|
|
400 |
if (empty($link)) {
|
|
|
401 |
$link = isset($gfile->webContentLink) ? $gfile->webContentLink : '';
|
|
|
402 |
}
|
|
|
403 |
if (isset($gfile->fileExtension)) {
|
|
|
404 |
// The file has an extension, therefore we can download it.
|
|
|
405 |
$source = json_encode([
|
|
|
406 |
'id' => $gfile->id,
|
|
|
407 |
'name' => $gfile->name,
|
|
|
408 |
'exportformat' => 'download',
|
|
|
409 |
'link' => $link
|
|
|
410 |
]);
|
|
|
411 |
$title = $gfile->name;
|
|
|
412 |
} else {
|
|
|
413 |
// The file is probably a Google Doc file, we get the corresponding export link.
|
|
|
414 |
// This should be improved by allowing the user to select the type of export they'd like.
|
|
|
415 |
$type = str_replace('application/vnd.google-apps.', '', $gfile->mimeType);
|
|
|
416 |
$title = '';
|
|
|
417 |
$exporttype = '';
|
|
|
418 |
$types = get_mimetypes_array();
|
|
|
419 |
|
|
|
420 |
switch ($type){
|
|
|
421 |
case 'document':
|
|
|
422 |
$ext = $config->documentformat;
|
|
|
423 |
$title = $gfile->name . '.gdoc';
|
|
|
424 |
if ($ext === 'rtf') {
|
|
|
425 |
// Moodle user 'text/rtf' as the MIME type for RTF files.
|
|
|
426 |
// Google uses 'application/rtf' for the same type of file.
|
|
|
427 |
// See https://developers.google.com/drive/v3/web/manage-downloads.
|
|
|
428 |
$exporttype = 'application/rtf';
|
|
|
429 |
} else {
|
|
|
430 |
$exporttype = $types[$ext]['type'];
|
|
|
431 |
}
|
|
|
432 |
break;
|
|
|
433 |
case 'presentation':
|
|
|
434 |
$ext = $config->presentationformat;
|
|
|
435 |
$title = $gfile->name . '.gslides';
|
|
|
436 |
$exporttype = $types[$ext]['type'];
|
|
|
437 |
break;
|
|
|
438 |
case 'spreadsheet':
|
|
|
439 |
$ext = $config->spreadsheetformat;
|
|
|
440 |
$title = $gfile->name . '.gsheet';
|
|
|
441 |
$exporttype = $types[$ext]['type'];
|
|
|
442 |
break;
|
|
|
443 |
case 'drawing':
|
|
|
444 |
$ext = $config->drawingformat;
|
|
|
445 |
$title = $gfile->name . '.'. $ext;
|
|
|
446 |
$exporttype = $types[$ext]['type'];
|
|
|
447 |
break;
|
|
|
448 |
}
|
|
|
449 |
// Skips invalid/unknown types.
|
|
|
450 |
if (empty($title)) {
|
|
|
451 |
continue;
|
|
|
452 |
}
|
|
|
453 |
$source = json_encode([
|
|
|
454 |
'id' => $gfile->id,
|
|
|
455 |
'exportformat' => $exporttype,
|
|
|
456 |
'link' => $link,
|
|
|
457 |
'name' => $gfile->name
|
|
|
458 |
]);
|
|
|
459 |
}
|
|
|
460 |
// Adds the file to the file list. Using the itemId along with the name as key
|
|
|
461 |
// of the array because Google Drive allows files with identical names.
|
|
|
462 |
$thumb = '';
|
|
|
463 |
if (isset($gfile->thumbnailLink)) {
|
|
|
464 |
$thumb = $gfile->thumbnailLink;
|
|
|
465 |
} else if (isset($gfile->iconLink)) {
|
|
|
466 |
$thumb = $gfile->iconLink;
|
|
|
467 |
}
|
|
|
468 |
$files[$title . $gfile->id] = array(
|
|
|
469 |
'title' => $title,
|
|
|
470 |
'source' => $source,
|
|
|
471 |
'date' => strtotime($gfile->modifiedTime),
|
|
|
472 |
'size' => isset($gfile->size) ? $gfile->size : null,
|
|
|
473 |
'thumbnail' => $thumb,
|
|
|
474 |
'thumbnail_height' => 64,
|
|
|
475 |
'thumbnail_width' => 64,
|
|
|
476 |
);
|
|
|
477 |
}
|
|
|
478 |
}
|
|
|
479 |
|
|
|
480 |
// Filter and order the results.
|
|
|
481 |
$files = array_filter($files, array($this, 'filter'));
|
|
|
482 |
core_collator::ksort($files, core_collator::SORT_NATURAL);
|
|
|
483 |
core_collator::ksort($folders, core_collator::SORT_NATURAL);
|
|
|
484 |
return array_merge(array_values($folders), array_values($files));
|
|
|
485 |
}
|
|
|
486 |
|
|
|
487 |
/**
|
|
|
488 |
* Logout.
|
|
|
489 |
*
|
|
|
490 |
* @return string
|
|
|
491 |
*/
|
|
|
492 |
public function logout() {
|
|
|
493 |
$client = $this->get_user_oauth_client();
|
|
|
494 |
$client->log_out();
|
|
|
495 |
return parent::logout();
|
|
|
496 |
}
|
|
|
497 |
|
|
|
498 |
/**
|
|
|
499 |
* Get a file.
|
|
|
500 |
*
|
|
|
501 |
* @param string $reference reference of the file.
|
|
|
502 |
* @param string $file name to save the file to.
|
|
|
503 |
* @return string JSON encoded array of information about the file.
|
|
|
504 |
*/
|
|
|
505 |
public function get_file($reference, $filename = '') {
|
|
|
506 |
global $CFG;
|
|
|
507 |
|
|
|
508 |
if (!$this->issuer->get('enabled')) {
|
|
|
509 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
510 |
}
|
|
|
511 |
|
|
|
512 |
$source = json_decode($reference);
|
|
|
513 |
|
|
|
514 |
$client = null;
|
|
|
515 |
if (!empty($source->usesystem)) {
|
|
|
516 |
$client = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
517 |
} else {
|
|
|
518 |
$client = $this->get_user_oauth_client();
|
|
|
519 |
}
|
|
|
520 |
|
|
|
521 |
$base = 'https://www.googleapis.com/drive/v3';
|
|
|
522 |
|
|
|
523 |
$newfilename = false;
|
|
|
524 |
if ($source->exportformat == 'download') {
|
|
|
525 |
$params = ['alt' => 'media'];
|
|
|
526 |
$sourceurl = new moodle_url($base . '/files/' . $source->id, $params);
|
|
|
527 |
$source = $sourceurl->out(false);
|
|
|
528 |
} else {
|
|
|
529 |
$params = ['mimeType' => $source->exportformat];
|
|
|
530 |
$sourceurl = new moodle_url($base . '/files/' . $source->id . '/export', $params);
|
|
|
531 |
$types = get_mimetypes_array();
|
|
|
532 |
$checktype = $source->exportformat;
|
|
|
533 |
if ($checktype == 'application/rtf') {
|
|
|
534 |
$checktype = 'text/rtf';
|
|
|
535 |
}
|
|
|
536 |
// Determine the relevant default import format config for the given file.
|
|
|
537 |
switch ($source->googledoctype) {
|
|
|
538 |
case 'document':
|
|
|
539 |
$importformatconfig = get_config('googledocs', 'documentformat');
|
|
|
540 |
break;
|
|
|
541 |
case 'presentation':
|
|
|
542 |
$importformatconfig = get_config('googledocs', 'presentationformat');
|
|
|
543 |
break;
|
|
|
544 |
case 'spreadsheet':
|
|
|
545 |
$importformatconfig = get_config('googledocs', 'spreadsheetformat');
|
|
|
546 |
break;
|
|
|
547 |
case 'drawing':
|
|
|
548 |
$importformatconfig = get_config('googledocs', 'drawingformat');
|
|
|
549 |
break;
|
|
|
550 |
default:
|
|
|
551 |
$importformatconfig = null;
|
|
|
552 |
}
|
|
|
553 |
|
|
|
554 |
foreach ($types as $extension => $info) {
|
|
|
555 |
if ($info['type'] == $checktype && $extension === $importformatconfig) {
|
|
|
556 |
$newfilename = $source->name . '.' . $extension;
|
|
|
557 |
break;
|
|
|
558 |
}
|
|
|
559 |
}
|
|
|
560 |
$source = $sourceurl->out(false);
|
|
|
561 |
}
|
|
|
562 |
|
|
|
563 |
// We use download_one and not the rest API because it has special timeouts etc.
|
|
|
564 |
$path = $this->prepare_file($filename);
|
|
|
565 |
$options = ['filepath' => $path, 'timeout' => 15, 'followlocation' => true, 'maxredirs' => 5];
|
|
|
566 |
$success = $client->download_one($source, null, $options);
|
|
|
567 |
|
|
|
568 |
if ($success) {
|
|
|
569 |
@chmod($path, $CFG->filepermissions);
|
|
|
570 |
|
|
|
571 |
$result = [
|
|
|
572 |
'path' => $path,
|
|
|
573 |
'url' => $reference,
|
|
|
574 |
];
|
|
|
575 |
if (!empty($newfilename)) {
|
|
|
576 |
$result['newfilename'] = $newfilename;
|
|
|
577 |
}
|
|
|
578 |
return $result;
|
|
|
579 |
}
|
|
|
580 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
581 |
}
|
|
|
582 |
|
|
|
583 |
/**
|
|
|
584 |
* Prepare file reference information.
|
|
|
585 |
*
|
|
|
586 |
* We are using this method to clean up the source to make sure that it
|
|
|
587 |
* is a valid source.
|
|
|
588 |
*
|
|
|
589 |
* @param string $source of the file.
|
|
|
590 |
* @return string file reference.
|
|
|
591 |
*/
|
|
|
592 |
public function get_file_reference($source) {
|
|
|
593 |
// We could do some magic upgrade code here.
|
|
|
594 |
return $source;
|
|
|
595 |
}
|
|
|
596 |
|
|
|
597 |
/**
|
|
|
598 |
* What kind of files will be in this repository?
|
|
|
599 |
*
|
|
|
600 |
* @return array return '*' means this repository support any files, otherwise
|
|
|
601 |
* return mimetypes of files, it can be an array
|
|
|
602 |
*/
|
|
|
603 |
public function supported_filetypes() {
|
|
|
604 |
return '*';
|
|
|
605 |
}
|
|
|
606 |
|
|
|
607 |
/**
|
|
|
608 |
* Tells how the file can be picked from this repository.
|
|
|
609 |
*
|
|
|
610 |
* @return int
|
|
|
611 |
*/
|
|
|
612 |
public function supported_returntypes() {
|
|
|
613 |
// We can only support references if the system account is connected.
|
|
|
614 |
if (!empty($this->issuer) && $this->issuer->is_system_account_connected()) {
|
|
|
615 |
$setting = get_config('googledocs', 'supportedreturntypes');
|
|
|
616 |
if ($setting == 'internal') {
|
|
|
617 |
return FILE_INTERNAL;
|
|
|
618 |
} else if ($setting == 'external') {
|
|
|
619 |
return FILE_CONTROLLED_LINK;
|
|
|
620 |
} else {
|
|
|
621 |
return FILE_CONTROLLED_LINK | FILE_INTERNAL;
|
|
|
622 |
}
|
|
|
623 |
} else {
|
|
|
624 |
return FILE_INTERNAL;
|
|
|
625 |
}
|
|
|
626 |
}
|
|
|
627 |
|
|
|
628 |
/**
|
|
|
629 |
* Which return type should be selected by default.
|
|
|
630 |
*
|
|
|
631 |
* @return int
|
|
|
632 |
*/
|
|
|
633 |
public function default_returntype() {
|
|
|
634 |
$setting = get_config('googledocs', 'defaultreturntype');
|
|
|
635 |
$supported = get_config('googledocs', 'supportedreturntypes');
|
|
|
636 |
if (($setting == FILE_INTERNAL && $supported != 'external') || $supported == 'internal') {
|
|
|
637 |
return FILE_INTERNAL;
|
|
|
638 |
} else {
|
|
|
639 |
return FILE_CONTROLLED_LINK;
|
|
|
640 |
}
|
|
|
641 |
}
|
|
|
642 |
|
|
|
643 |
/**
|
|
|
644 |
* Return names of the general options.
|
|
|
645 |
* By default: no general option name.
|
|
|
646 |
*
|
|
|
647 |
* @return array
|
|
|
648 |
*/
|
|
|
649 |
public static function get_type_option_names() {
|
|
|
650 |
return array('issuerid', 'pluginname',
|
|
|
651 |
'documentformat', 'drawingformat',
|
|
|
652 |
'presentationformat', 'spreadsheetformat',
|
|
|
653 |
'defaultreturntype', 'supportedreturntypes');
|
|
|
654 |
}
|
|
|
655 |
|
|
|
656 |
/**
|
|
|
657 |
* Store the access token.
|
|
|
658 |
*/
|
|
|
659 |
public function callback() {
|
|
|
660 |
$client = $this->get_user_oauth_client();
|
|
|
661 |
// This will upgrade to an access token if we have an authorization code and save the access token in the session.
|
|
|
662 |
$client->is_logged_in();
|
|
|
663 |
}
|
|
|
664 |
|
|
|
665 |
/**
|
|
|
666 |
* Repository method to serve the referenced file
|
|
|
667 |
*
|
|
|
668 |
* @see send_stored_file
|
|
|
669 |
*
|
|
|
670 |
* @param stored_file $storedfile the file that contains the reference
|
|
|
671 |
* @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
|
|
|
672 |
* @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
|
|
|
673 |
* @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
|
|
|
674 |
* @param array $options additional options affecting the file serving
|
|
|
675 |
*/
|
|
|
676 |
public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
|
|
|
677 |
if (!$this->issuer->get('enabled')) {
|
|
|
678 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
679 |
}
|
|
|
680 |
|
|
|
681 |
$source = json_decode($storedfile->get_reference());
|
|
|
682 |
|
|
|
683 |
$fb = get_file_browser();
|
|
|
684 |
$context = context::instance_by_id($storedfile->get_contextid(), MUST_EXIST);
|
|
|
685 |
$info = $fb->get_file_info($context,
|
|
|
686 |
$storedfile->get_component(),
|
|
|
687 |
$storedfile->get_filearea(),
|
|
|
688 |
$storedfile->get_itemid(),
|
|
|
689 |
$storedfile->get_filepath(),
|
|
|
690 |
$storedfile->get_filename());
|
|
|
691 |
|
|
|
692 |
if (empty($options['offline']) && !empty($info) && $info->is_writable() && !empty($source->usesystem)) {
|
|
|
693 |
// Add the current user as an OAuth writer.
|
|
|
694 |
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
695 |
|
|
|
696 |
if ($systemauth === false) {
|
|
|
697 |
$details = 'Cannot connect as system user';
|
|
|
698 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
699 |
}
|
|
|
700 |
$systemservice = new repository_googledocs\rest($systemauth);
|
|
|
701 |
|
|
|
702 |
// Get the user oauth so we can get the account to add.
|
|
|
703 |
$url = moodle_url::make_pluginfile_url($storedfile->get_contextid(),
|
|
|
704 |
$storedfile->get_component(),
|
|
|
705 |
$storedfile->get_filearea(),
|
|
|
706 |
$storedfile->get_itemid(),
|
|
|
707 |
$storedfile->get_filepath(),
|
|
|
708 |
$storedfile->get_filename(),
|
|
|
709 |
$forcedownload);
|
|
|
710 |
$url->param('sesskey', sesskey());
|
|
|
711 |
$param = ($options['embed'] == true) ? false : $url;
|
|
|
712 |
$userauth = $this->get_user_oauth_client($param);
|
|
|
713 |
if (!$userauth->is_logged_in()) {
|
|
|
714 |
if ($options['embed'] == true) {
|
|
|
715 |
// Due to Same-origin policy, we cannot redirect to googledocs login page.
|
|
|
716 |
// If the requested file is embed and the user is not logged in, add option to log in using a popup.
|
|
|
717 |
$this->print_login_popup(['style' => 'margin-top: 250px']);
|
|
|
718 |
exit;
|
|
|
719 |
}
|
|
|
720 |
redirect($userauth->get_login_url());
|
|
|
721 |
}
|
|
|
722 |
if ($userauth === false) {
|
|
|
723 |
$details = 'Cannot connect as current user';
|
|
|
724 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
725 |
}
|
|
|
726 |
$userinfo = $userauth->get_userinfo();
|
|
|
727 |
$useremail = $userinfo['email'];
|
|
|
728 |
|
|
|
729 |
$this->add_temp_writer_to_file($systemservice, $source->id, $useremail);
|
|
|
730 |
}
|
|
|
731 |
|
|
|
732 |
if (!empty($options['offline'])) {
|
|
|
733 |
$downloaded = $this->get_file($storedfile->get_reference(), $storedfile->get_filename());
|
|
|
734 |
|
|
|
735 |
$filename = $storedfile->get_filename();
|
|
|
736 |
if (isset($downloaded['newfilename'])) {
|
|
|
737 |
$filename = $downloaded['newfilename'];
|
|
|
738 |
}
|
|
|
739 |
send_file($downloaded['path'], $filename, $lifetime, $filter, false, $forcedownload, '', false, $options);
|
|
|
740 |
} else if ($source->link) {
|
|
|
741 |
// Do not use redirect() here because is not compatible with webservice/pluginfile.php.
|
|
|
742 |
header('Location: ' . $source->link);
|
|
|
743 |
} else {
|
|
|
744 |
$details = 'File is missing source link';
|
|
|
745 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
746 |
}
|
|
|
747 |
}
|
|
|
748 |
|
|
|
749 |
/**
|
|
|
750 |
* See if a folder exists within a folder
|
|
|
751 |
*
|
|
|
752 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
753 |
* @param string $foldername The folder we are looking for.
|
|
|
754 |
* @param string $parentid The parent folder we are looking in.
|
|
|
755 |
* @return string|boolean The file id if it exists or false.
|
|
|
756 |
*/
|
|
|
757 |
protected function folder_exists_in_folder(\repository_googledocs\rest $client, $foldername, $parentid) {
|
|
|
758 |
$q = '\'' . addslashes($parentid) . '\' in parents and trashed = false and name = \'' . addslashes($foldername). '\'';
|
|
|
759 |
$fields = 'files(id, name)';
|
|
|
760 |
$params = [ 'q' => $q, 'fields' => $fields];
|
|
|
761 |
$response = $client->call('list', $params);
|
|
|
762 |
$missing = true;
|
|
|
763 |
foreach ($response->files as $child) {
|
|
|
764 |
if ($child->name == $foldername) {
|
|
|
765 |
return $child->id;
|
|
|
766 |
}
|
|
|
767 |
}
|
|
|
768 |
return false;
|
|
|
769 |
}
|
|
|
770 |
|
|
|
771 |
/**
|
|
|
772 |
* Create a folder within a folder
|
|
|
773 |
*
|
|
|
774 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
775 |
* @param string $foldername The folder we are creating.
|
|
|
776 |
* @param string $parentid The parent folder we are creating in.
|
|
|
777 |
*
|
|
|
778 |
* @return string The file id of the new folder.
|
|
|
779 |
*/
|
|
|
780 |
protected function create_folder_in_folder(\repository_googledocs\rest $client, $foldername, $parentid) {
|
|
|
781 |
$fields = 'id';
|
|
|
782 |
$params = ['fields' => $fields];
|
|
|
783 |
$folder = ['mimeType' => 'application/vnd.google-apps.folder', 'name' => $foldername, 'parents' => [$parentid]];
|
|
|
784 |
$created = $client->call('create', $params, json_encode($folder));
|
|
|
785 |
if (empty($created->id)) {
|
|
|
786 |
$details = 'Cannot create folder:' . $foldername;
|
|
|
787 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
788 |
}
|
|
|
789 |
return $created->id;
|
|
|
790 |
}
|
|
|
791 |
|
|
|
792 |
/**
|
|
|
793 |
* Get simple file info for humans.
|
|
|
794 |
*
|
|
|
795 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
796 |
* @param string $fileid The file we are querying.
|
|
|
797 |
*
|
|
|
798 |
* @return stdClass
|
|
|
799 |
*/
|
|
|
800 |
protected function get_file_summary(\repository_googledocs\rest $client, $fileid) {
|
|
|
801 |
$fields = "id,name,owners,parents";
|
|
|
802 |
$params = [
|
|
|
803 |
'fileid' => $fileid,
|
|
|
804 |
'fields' => $fields
|
|
|
805 |
];
|
|
|
806 |
return $client->call('get', $params);
|
|
|
807 |
}
|
|
|
808 |
|
|
|
809 |
/**
|
|
|
810 |
* Copy a file and return the new file details. A side effect of the copy
|
|
|
811 |
* is that the owner will be the account authenticated with this oauth client.
|
|
|
812 |
*
|
|
|
813 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
814 |
* @param string $fileid The file we are copying.
|
|
|
815 |
* @param string $name The original filename (don't change it).
|
|
|
816 |
*
|
|
|
817 |
* @return stdClass file details.
|
|
|
818 |
*/
|
|
|
819 |
protected function copy_file(\repository_googledocs\rest $client, $fileid, $name) {
|
|
|
820 |
$fields = "id,name,mimeType,webContentLink,webViewLink,size,thumbnailLink,iconLink";
|
|
|
821 |
$params = [
|
|
|
822 |
'fileid' => $fileid,
|
|
|
823 |
'fields' => $fields,
|
|
|
824 |
];
|
|
|
825 |
// Keep the original name (don't put copy at the end of it).
|
|
|
826 |
$copyinfo = [];
|
|
|
827 |
if (!empty($name)) {
|
|
|
828 |
$copyinfo = [ 'name' => $name ];
|
|
|
829 |
}
|
|
|
830 |
$fileinfo = $client->call('copy', $params, json_encode($copyinfo));
|
|
|
831 |
if (empty($fileinfo->id)) {
|
|
|
832 |
$details = 'Cannot copy file:' . $fileid;
|
|
|
833 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
834 |
}
|
|
|
835 |
return $fileinfo;
|
|
|
836 |
}
|
|
|
837 |
|
|
|
838 |
/**
|
|
|
839 |
* Add a writer to the permissions on the file (temporary).
|
|
|
840 |
*
|
|
|
841 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
842 |
* @param string $fileid The file we are updating.
|
|
|
843 |
* @param string $email The email of the writer account to add.
|
|
|
844 |
* @return boolean
|
|
|
845 |
*/
|
|
|
846 |
protected function add_temp_writer_to_file(\repository_googledocs\rest $client, $fileid, $email) {
|
|
|
847 |
// Expires in 7 days.
|
|
|
848 |
$expires = new DateTime();
|
|
|
849 |
$expires->add(new DateInterval("P7D"));
|
|
|
850 |
|
|
|
851 |
$updateeditor = [
|
|
|
852 |
'emailAddress' => $email,
|
|
|
853 |
'role' => 'writer',
|
|
|
854 |
'type' => 'user',
|
|
|
855 |
'expirationTime' => $expires->format(DateTime::RFC3339)
|
|
|
856 |
];
|
|
|
857 |
$params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false'];
|
|
|
858 |
$response = $client->call('create_permission', $params, json_encode($updateeditor));
|
|
|
859 |
if (empty($response->id)) {
|
|
|
860 |
$details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
|
|
|
861 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
862 |
}
|
|
|
863 |
return true;
|
|
|
864 |
}
|
|
|
865 |
|
|
|
866 |
|
|
|
867 |
/**
|
|
|
868 |
* Add a writer to the permissions on the file.
|
|
|
869 |
*
|
|
|
870 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
871 |
* @param string $fileid The file we are updating.
|
|
|
872 |
* @param string $email The email of the writer account to add.
|
|
|
873 |
* @return boolean
|
|
|
874 |
*/
|
|
|
875 |
protected function add_writer_to_file(\repository_googledocs\rest $client, $fileid, $email) {
|
|
|
876 |
$updateeditor = [
|
|
|
877 |
'emailAddress' => $email,
|
|
|
878 |
'role' => 'writer',
|
|
|
879 |
'type' => 'user'
|
|
|
880 |
];
|
|
|
881 |
$params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false'];
|
|
|
882 |
$response = $client->call('create_permission', $params, json_encode($updateeditor));
|
|
|
883 |
if (empty($response->id)) {
|
|
|
884 |
$details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
|
|
|
885 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
886 |
}
|
|
|
887 |
return true;
|
|
|
888 |
}
|
|
|
889 |
|
|
|
890 |
/**
|
|
|
891 |
* Move from root to folder
|
|
|
892 |
*
|
|
|
893 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
894 |
* @param string $fileid The file we are updating.
|
|
|
895 |
* @param string $folderid The id of the folder we are moving to
|
|
|
896 |
* @return boolean
|
|
|
897 |
*/
|
|
|
898 |
protected function move_file_from_root_to_folder(\repository_googledocs\rest $client, $fileid, $folderid) {
|
|
|
899 |
// Set the parent.
|
|
|
900 |
$params = [
|
|
|
901 |
'fileid' => $fileid, 'addParents' => $folderid, 'removeParents' => 'root'
|
|
|
902 |
];
|
|
|
903 |
$response = $client->call('update', $params, ' ');
|
|
|
904 |
if (empty($response->id)) {
|
|
|
905 |
$details = 'Cannot move the file to a folder: ' . $fileid;
|
|
|
906 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
907 |
}
|
|
|
908 |
return true;
|
|
|
909 |
}
|
|
|
910 |
|
|
|
911 |
/**
|
|
|
912 |
* Prevent writers from sharing.
|
|
|
913 |
*
|
|
|
914 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
915 |
* @param string $fileid The file we are updating.
|
|
|
916 |
* @return boolean
|
|
|
917 |
*/
|
|
|
918 |
protected function prevent_writers_from_sharing_file(\repository_googledocs\rest $client, $fileid) {
|
|
|
919 |
// We don't want anyone but Moodle to change the sharing settings.
|
|
|
920 |
$params = [
|
|
|
921 |
'fileid' => $fileid
|
|
|
922 |
];
|
|
|
923 |
$update = [
|
|
|
924 |
'writersCanShare' => false
|
|
|
925 |
];
|
|
|
926 |
$response = $client->call('update', $params, json_encode($update));
|
|
|
927 |
if (empty($response->id)) {
|
|
|
928 |
$details = 'Cannot prevent writers from sharing document: ' . $fileid;
|
|
|
929 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
930 |
}
|
|
|
931 |
return true;
|
|
|
932 |
}
|
|
|
933 |
|
|
|
934 |
/**
|
|
|
935 |
* Allow anyone with the link to read the file.
|
|
|
936 |
*
|
|
|
937 |
* @param \repository_googledocs\rest $client Authenticated client.
|
|
|
938 |
* @param string $fileid The file we are updating.
|
|
|
939 |
* @return boolean
|
|
|
940 |
*/
|
|
|
941 |
protected function set_file_sharing_anyone_with_link_can_read(\repository_googledocs\rest $client, $fileid) {
|
|
|
942 |
$updateread = [
|
|
|
943 |
'type' => 'anyone',
|
|
|
944 |
'role' => 'reader',
|
|
|
945 |
'allowFileDiscovery' => 'false'
|
|
|
946 |
];
|
|
|
947 |
$params = ['fileid' => $fileid];
|
|
|
948 |
$response = $client->call('create_permission', $params, json_encode($updateread));
|
|
|
949 |
if (empty($response->id) || $response->id != 'anyoneWithLink') {
|
|
|
950 |
$details = 'Cannot update link sharing for the document: ' . $fileid;
|
|
|
951 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
952 |
}
|
|
|
953 |
return true;
|
|
|
954 |
}
|
|
|
955 |
|
|
|
956 |
/**
|
|
|
957 |
* Called when a file is selected as a "link".
|
|
|
958 |
* Invoked at MOODLE/repository/repository_ajax.php
|
|
|
959 |
*
|
|
|
960 |
* This is called at the point the reference files are being copied from the draft area to the real area
|
|
|
961 |
* (when the file has really really been selected.
|
|
|
962 |
*
|
|
|
963 |
* @param string $reference this reference is generated by
|
|
|
964 |
* repository::get_file_reference()
|
|
|
965 |
* @param context $context the target context for this new file.
|
|
|
966 |
* @param string $component the target component for this new file.
|
|
|
967 |
* @param string $filearea the target filearea for this new file.
|
|
|
968 |
* @param string $itemid the target itemid for this new file.
|
|
|
969 |
* @return string updated reference (final one before it's saved to db).
|
|
|
970 |
*/
|
|
|
971 |
public function reference_file_selected($reference, $context, $component, $filearea, $itemid) {
|
|
|
972 |
global $CFG, $SITE;
|
|
|
973 |
|
|
|
974 |
// What we need to do here is transfer ownership to the system user (or copy)
|
|
|
975 |
// then set the permissions so anyone with the share link can view,
|
|
|
976 |
// finally update the reference to contain the share link if it was not
|
|
|
977 |
// already there (and point to new file id if we copied).
|
|
|
978 |
|
|
|
979 |
// Get the details from the reference.
|
|
|
980 |
$source = json_decode($reference);
|
|
|
981 |
if (!empty($source->usesystem)) {
|
|
|
982 |
// If we already copied this file to the system account - we are done.
|
|
|
983 |
return $reference;
|
|
|
984 |
}
|
|
|
985 |
|
|
|
986 |
// Check this issuer is enabled.
|
|
|
987 |
if ($this->disabled) {
|
|
|
988 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
989 |
}
|
|
|
990 |
|
|
|
991 |
// Get a system oauth client and a user oauth client.
|
|
|
992 |
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
993 |
|
|
|
994 |
if ($systemauth === false) {
|
|
|
995 |
$details = 'Cannot connect as system user';
|
|
|
996 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
997 |
}
|
|
|
998 |
// Get the system user email so we can share the file with this user.
|
|
|
999 |
$systemuserinfo = $systemauth->get_userinfo();
|
|
|
1000 |
$systemuseremail = $systemuserinfo['email'];
|
|
|
1001 |
|
|
|
1002 |
$userauth = $this->get_user_oauth_client();
|
|
|
1003 |
if ($userauth === false) {
|
|
|
1004 |
$details = 'Cannot connect as current user';
|
|
|
1005 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
1006 |
}
|
|
|
1007 |
|
|
|
1008 |
$userservice = new repository_googledocs\rest($userauth);
|
|
|
1009 |
$systemservice = new repository_googledocs\rest($systemauth);
|
|
|
1010 |
|
|
|
1011 |
// Add Moodle as writer.
|
|
|
1012 |
$this->add_writer_to_file($userservice, $source->id, $systemuseremail);
|
|
|
1013 |
|
|
|
1014 |
// Now move it to a sensible folder.
|
|
|
1015 |
$contextlist = array_reverse($context->get_parent_contexts(true));
|
|
|
1016 |
|
|
|
1017 |
$cache = cache::make('repository_googledocs', 'folder');
|
|
|
1018 |
$parentid = 'root';
|
|
|
1019 |
$fullpath = 'root';
|
|
|
1020 |
$allfolders = [];
|
|
|
1021 |
foreach ($contextlist as $context) {
|
|
|
1022 |
// Prepare human readable context folders names, making sure they are still unique within the site.
|
|
|
1023 |
$prevlang = force_current_language($CFG->lang);
|
|
|
1024 |
$foldername = $context->get_context_name();
|
|
|
1025 |
force_current_language($prevlang);
|
|
|
1026 |
|
|
|
1027 |
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
|
|
1028 |
// Append the site short name to the root folder.
|
|
|
1029 |
$foldername .= ' ('.$SITE->shortname.')';
|
|
|
1030 |
// Append the relevant object id.
|
|
|
1031 |
} else if ($context->instanceid) {
|
|
|
1032 |
$foldername .= ' (id '.$context->instanceid.')';
|
|
|
1033 |
} else {
|
|
|
1034 |
// This does not really happen but just in case.
|
|
|
1035 |
$foldername .= ' (ctx '.$context->id.')';
|
|
|
1036 |
}
|
|
|
1037 |
|
|
|
1038 |
$foldername = clean_param($foldername, PARAM_PATH);
|
|
|
1039 |
$allfolders[] = $foldername;
|
|
|
1040 |
}
|
|
|
1041 |
|
|
|
1042 |
$allfolders[] = clean_param($component, PARAM_PATH);
|
|
|
1043 |
$allfolders[] = clean_param($filearea, PARAM_PATH);
|
|
|
1044 |
$allfolders[] = clean_param($itemid, PARAM_PATH);
|
|
|
1045 |
|
|
|
1046 |
// Variable $allfolders is the full path we want to put the file in - so walk it and create each folder.
|
|
|
1047 |
|
|
|
1048 |
foreach ($allfolders as $foldername) {
|
|
|
1049 |
// Make sure a folder exists here.
|
|
|
1050 |
$fullpath .= '/' . $foldername;
|
|
|
1051 |
|
|
|
1052 |
$folderid = $cache->get($fullpath);
|
|
|
1053 |
if (empty($folderid)) {
|
|
|
1054 |
$folderid = $this->folder_exists_in_folder($systemservice, $foldername, $parentid);
|
|
|
1055 |
}
|
|
|
1056 |
if ($folderid !== false) {
|
|
|
1057 |
$cache->set($fullpath, $folderid);
|
|
|
1058 |
$parentid = $folderid;
|
|
|
1059 |
} else {
|
|
|
1060 |
// Create it.
|
|
|
1061 |
$parentid = $this->create_folder_in_folder($systemservice, $foldername, $parentid);
|
|
|
1062 |
$cache->set($fullpath, $parentid);
|
|
|
1063 |
}
|
|
|
1064 |
}
|
|
|
1065 |
|
|
|
1066 |
// Copy the file so we get a snapshot file owned by Moodle.
|
|
|
1067 |
$newsource = $this->copy_file($systemservice, $source->id, $source->name);
|
|
|
1068 |
// Move the copied file to the correct folder.
|
|
|
1069 |
$this->move_file_from_root_to_folder($systemservice, $newsource->id, $parentid);
|
|
|
1070 |
|
|
|
1071 |
// Set the sharing options.
|
|
|
1072 |
$this->set_file_sharing_anyone_with_link_can_read($systemservice, $newsource->id);
|
|
|
1073 |
$this->prevent_writers_from_sharing_file($systemservice, $newsource->id);
|
|
|
1074 |
|
|
|
1075 |
// Update the returned reference so that the stored_file in moodle points to the newly copied file.
|
|
|
1076 |
$source->id = $newsource->id;
|
|
|
1077 |
$source->link = isset($newsource->webViewLink) ? $newsource->webViewLink : '';
|
|
|
1078 |
$source->usesystem = true;
|
|
|
1079 |
if (empty($source->link)) {
|
|
|
1080 |
$source->link = isset($newsource->webContentLink) ? $newsource->webContentLink : '';
|
|
|
1081 |
}
|
|
|
1082 |
$reference = json_encode($source);
|
|
|
1083 |
|
|
|
1084 |
return $reference;
|
|
|
1085 |
}
|
|
|
1086 |
|
|
|
1087 |
/**
|
|
|
1088 |
* Get human readable file info from a the reference.
|
|
|
1089 |
*
|
|
|
1090 |
* @param string $reference
|
|
|
1091 |
* @param int $filestatus
|
|
|
1092 |
*/
|
|
|
1093 |
public function get_reference_details($reference, $filestatus = 0) {
|
|
|
1094 |
if ($this->disabled) {
|
|
|
1095 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
1096 |
}
|
|
|
1097 |
if (empty($reference)) {
|
|
|
1098 |
return get_string('unknownsource', 'repository');
|
|
|
1099 |
}
|
|
|
1100 |
$source = json_decode($reference);
|
|
|
1101 |
if (empty($source->usesystem)) {
|
|
|
1102 |
return '';
|
|
|
1103 |
}
|
|
|
1104 |
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
1105 |
|
|
|
1106 |
if ($systemauth === false) {
|
|
|
1107 |
return '';
|
|
|
1108 |
}
|
|
|
1109 |
$systemservice = new repository_googledocs\rest($systemauth);
|
|
|
1110 |
$info = $this->get_file_summary($systemservice, $source->id);
|
|
|
1111 |
|
|
|
1112 |
$owner = '';
|
|
|
1113 |
if (!empty($info->owners[0]->displayName)) {
|
|
|
1114 |
$owner = $info->owners[0]->displayName;
|
|
|
1115 |
}
|
|
|
1116 |
if ($owner) {
|
|
|
1117 |
return get_string('owner', 'repository_googledocs', $owner);
|
|
|
1118 |
} else {
|
|
|
1119 |
return $info->name;
|
|
|
1120 |
}
|
|
|
1121 |
}
|
|
|
1122 |
|
|
|
1123 |
/**
|
|
|
1124 |
* Edit/Create Admin Settings Moodle form.
|
|
|
1125 |
*
|
|
|
1126 |
* @param moodleform $mform Moodle form (passed by reference).
|
|
|
1127 |
* @param string $classname repository class name.
|
|
|
1128 |
*/
|
|
|
1129 |
public static function type_config_form($mform, $classname = 'repository') {
|
|
|
1130 |
$url = new moodle_url('/admin/tool/oauth2/issuers.php');
|
|
|
1131 |
$url = $url->out();
|
|
|
1132 |
|
|
|
1133 |
$mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_googledocs', $url));
|
|
|
1134 |
|
|
|
1135 |
parent::type_config_form($mform);
|
|
|
1136 |
$options = [];
|
|
|
1137 |
$issuers = \core\oauth2\api::get_all_issuers();
|
|
|
1138 |
|
|
|
1139 |
foreach ($issuers as $issuer) {
|
|
|
1140 |
$options[$issuer->get('id')] = s($issuer->get('name'));
|
|
|
1141 |
}
|
|
|
1142 |
|
|
|
1143 |
$strrequired = get_string('required');
|
|
|
1144 |
|
|
|
1145 |
$mform->addElement('select', 'issuerid', get_string('issuer', 'repository_googledocs'), $options);
|
|
|
1146 |
$mform->addHelpButton('issuerid', 'issuer', 'repository_googledocs');
|
|
|
1147 |
$mform->addRule('issuerid', $strrequired, 'required', null, 'client');
|
|
|
1148 |
|
|
|
1149 |
$mform->addElement('static', null, '', get_string('fileoptions', 'repository_googledocs'));
|
|
|
1150 |
$choices = [
|
|
|
1151 |
'internal' => get_string('internal', 'repository_googledocs'),
|
|
|
1152 |
'external' => get_string('external', 'repository_googledocs'),
|
|
|
1153 |
'both' => get_string('both', 'repository_googledocs')
|
|
|
1154 |
];
|
|
|
1155 |
$mform->addElement('select', 'supportedreturntypes', get_string('supportedreturntypes', 'repository_googledocs'), $choices);
|
|
|
1156 |
|
|
|
1157 |
$choices = [
|
|
|
1158 |
FILE_INTERNAL => get_string('internal', 'repository_googledocs'),
|
|
|
1159 |
FILE_CONTROLLED_LINK => get_string('external', 'repository_googledocs'),
|
|
|
1160 |
];
|
|
|
1161 |
$mform->addElement('select', 'defaultreturntype', get_string('defaultreturntype', 'repository_googledocs'), $choices);
|
|
|
1162 |
|
|
|
1163 |
$mform->addElement('static', null, '', get_string('importformat', 'repository_googledocs'));
|
|
|
1164 |
|
|
|
1165 |
// Documents.
|
|
|
1166 |
$docsformat = array();
|
|
|
1167 |
$docsformat['html'] = 'html';
|
|
|
1168 |
$docsformat['docx'] = 'docx';
|
|
|
1169 |
$docsformat['odt'] = 'odt';
|
|
|
1170 |
$docsformat['pdf'] = 'pdf';
|
|
|
1171 |
$docsformat['rtf'] = 'rtf';
|
|
|
1172 |
$docsformat['txt'] = 'txt';
|
|
|
1173 |
core_collator::ksort($docsformat, core_collator::SORT_NATURAL);
|
|
|
1174 |
|
|
|
1175 |
$mform->addElement('select', 'documentformat', get_string('docsformat', 'repository_googledocs'), $docsformat);
|
|
|
1176 |
$mform->setDefault('documentformat', $docsformat['rtf']);
|
|
|
1177 |
$mform->setType('documentformat', PARAM_ALPHANUM);
|
|
|
1178 |
|
|
|
1179 |
// Drawing.
|
|
|
1180 |
$drawingformat = array();
|
|
|
1181 |
$drawingformat['jpeg'] = 'jpeg';
|
|
|
1182 |
$drawingformat['png'] = 'png';
|
|
|
1183 |
$drawingformat['svg'] = 'svg';
|
|
|
1184 |
$drawingformat['pdf'] = 'pdf';
|
|
|
1185 |
core_collator::ksort($drawingformat, core_collator::SORT_NATURAL);
|
|
|
1186 |
|
|
|
1187 |
$mform->addElement('select', 'drawingformat', get_string('drawingformat', 'repository_googledocs'), $drawingformat);
|
|
|
1188 |
$mform->setDefault('drawingformat', $drawingformat['pdf']);
|
|
|
1189 |
$mform->setType('drawingformat', PARAM_ALPHANUM);
|
|
|
1190 |
|
|
|
1191 |
// Presentation.
|
|
|
1192 |
$presentationformat = array();
|
|
|
1193 |
$presentationformat['pdf'] = 'pdf';
|
|
|
1194 |
$presentationformat['pptx'] = 'pptx';
|
|
|
1195 |
$presentationformat['txt'] = 'txt';
|
|
|
1196 |
core_collator::ksort($presentationformat, core_collator::SORT_NATURAL);
|
|
|
1197 |
|
|
|
1198 |
$str = get_string('presentationformat', 'repository_googledocs');
|
|
|
1199 |
$mform->addElement('select', 'presentationformat', $str, $presentationformat);
|
|
|
1200 |
$mform->setDefault('presentationformat', $presentationformat['pptx']);
|
|
|
1201 |
$mform->setType('presentationformat', PARAM_ALPHANUM);
|
|
|
1202 |
|
|
|
1203 |
// Spreadsheet.
|
|
|
1204 |
$spreadsheetformat = array();
|
|
|
1205 |
$spreadsheetformat['csv'] = 'csv';
|
|
|
1206 |
$spreadsheetformat['ods'] = 'ods';
|
|
|
1207 |
$spreadsheetformat['pdf'] = 'pdf';
|
|
|
1208 |
$spreadsheetformat['xlsx'] = 'xlsx';
|
|
|
1209 |
core_collator::ksort($spreadsheetformat, core_collator::SORT_NATURAL);
|
|
|
1210 |
|
|
|
1211 |
$str = get_string('spreadsheetformat', 'repository_googledocs');
|
|
|
1212 |
$mform->addElement('select', 'spreadsheetformat', $str, $spreadsheetformat);
|
|
|
1213 |
$mform->setDefault('spreadsheetformat', $spreadsheetformat['xlsx']);
|
|
|
1214 |
$mform->setType('spreadsheetformat', PARAM_ALPHANUM);
|
|
|
1215 |
}
|
|
|
1216 |
}
|
|
|
1217 |
|
|
|
1218 |
/**
|
|
|
1219 |
* Callback to get the required scopes for system account.
|
|
|
1220 |
*
|
|
|
1221 |
* @param \core\oauth2\issuer $issuer
|
|
|
1222 |
* @return string
|
|
|
1223 |
*/
|
|
|
1224 |
function repository_googledocs_oauth2_system_scopes(\core\oauth2\issuer $issuer) {
|
|
|
1225 |
if ($issuer->get('id') == get_config('googledocs', 'issuerid')) {
|
|
|
1226 |
return 'https://www.googleapis.com/auth/drive';
|
|
|
1227 |
}
|
|
|
1228 |
return '';
|
|
|
1229 |
}
|