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 |
* Microsoft Live Skydrive Repository Plugin
|
|
|
19 |
*
|
|
|
20 |
* @package repository_onedrive
|
|
|
21 |
* @copyright 2012 Lancaster University Network Services Ltd
|
|
|
22 |
* @author Dan Poltawski <dan.poltawski@luns.net.uk>
|
|
|
23 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
24 |
*/
|
|
|
25 |
|
|
|
26 |
defined('MOODLE_INTERNAL') || die();
|
|
|
27 |
|
|
|
28 |
/**
|
|
|
29 |
* Microsoft onedrive repository plugin.
|
|
|
30 |
*
|
|
|
31 |
* @package repository_onedrive
|
|
|
32 |
* @copyright 2012 Lancaster University Network Services Ltd
|
|
|
33 |
* @author Dan Poltawski <dan.poltawski@luns.net.uk>
|
|
|
34 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
35 |
*/
|
|
|
36 |
class repository_onedrive extends repository {
|
|
|
37 |
/**
|
|
|
38 |
* OAuth 2 client
|
|
|
39 |
* @var \core\oauth2\client
|
|
|
40 |
*/
|
|
|
41 |
private $client = null;
|
|
|
42 |
|
|
|
43 |
/**
|
|
|
44 |
* OAuth 2 Issuer
|
|
|
45 |
* @var \core\oauth2\issuer
|
|
|
46 |
*/
|
|
|
47 |
private $issuer = null;
|
|
|
48 |
|
|
|
49 |
/**
|
|
|
50 |
* Additional scopes required for drive.
|
|
|
51 |
*/
|
|
|
52 |
const SCOPES = 'files.readwrite.all';
|
|
|
53 |
|
|
|
54 |
/**
|
|
|
55 |
* Constructor.
|
|
|
56 |
*
|
|
|
57 |
* @param int $repositoryid repository instance id.
|
|
|
58 |
* @param int|stdClass $context a context id or context object.
|
|
|
59 |
* @param array $options repository options.
|
|
|
60 |
* @param int $readonly indicate this repo is readonly or not.
|
|
|
61 |
* @return void
|
|
|
62 |
*/
|
|
|
63 |
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
|
|
|
64 |
parent::__construct($repositoryid, $context, $options, $readonly = 0);
|
|
|
65 |
|
|
|
66 |
try {
|
|
|
67 |
$this->issuer = \core\oauth2\api::get_issuer(get_config('onedrive', 'issuerid'));
|
|
|
68 |
} catch (dml_missing_record_exception $e) {
|
|
|
69 |
$this->disabled = true;
|
|
|
70 |
}
|
|
|
71 |
|
|
|
72 |
if ($this->issuer && !$this->issuer->get('enabled')) {
|
|
|
73 |
$this->disabled = true;
|
|
|
74 |
}
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
/**
|
|
|
78 |
* Get a cached user authenticated oauth client.
|
|
|
79 |
*
|
|
|
80 |
* @param moodle_url $overrideurl - Use this url instead of the repo callback.
|
|
|
81 |
* @return \core\oauth2\client
|
|
|
82 |
*/
|
|
|
83 |
protected function get_user_oauth_client($overrideurl = false) {
|
|
|
84 |
if ($this->client) {
|
|
|
85 |
return $this->client;
|
|
|
86 |
}
|
|
|
87 |
if ($overrideurl) {
|
|
|
88 |
$returnurl = $overrideurl;
|
|
|
89 |
} else {
|
|
|
90 |
$returnurl = new moodle_url('/repository/repository_callback.php');
|
|
|
91 |
$returnurl->param('callback', 'yes');
|
|
|
92 |
$returnurl->param('repo_id', $this->id);
|
|
|
93 |
$returnurl->param('sesskey', sesskey());
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
$this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, self::SCOPES, true);
|
|
|
97 |
|
|
|
98 |
return $this->client;
|
|
|
99 |
}
|
|
|
100 |
|
|
|
101 |
/**
|
|
|
102 |
* Checks whether the user is authenticate or not.
|
|
|
103 |
*
|
|
|
104 |
* @return bool true when logged in.
|
|
|
105 |
*/
|
|
|
106 |
public function check_login() {
|
|
|
107 |
$client = $this->get_user_oauth_client();
|
|
|
108 |
return $client->is_logged_in();
|
|
|
109 |
}
|
|
|
110 |
|
|
|
111 |
/**
|
|
|
112 |
* Print or return the login form.
|
|
|
113 |
*
|
|
|
114 |
* @return void|array for ajax.
|
|
|
115 |
*/
|
|
|
116 |
public function print_login() {
|
|
|
117 |
$client = $this->get_user_oauth_client();
|
|
|
118 |
$url = $client->get_login_url();
|
|
|
119 |
|
|
|
120 |
if ($this->options['ajax']) {
|
|
|
121 |
$popup = new stdClass();
|
|
|
122 |
$popup->type = 'popup';
|
|
|
123 |
$popup->url = $url->out(false);
|
|
|
124 |
return array('login' => array($popup));
|
|
|
125 |
} else {
|
|
|
126 |
echo '<a target="_blank" href="'.$url->out(false).'">'.get_string('login', 'repository').'</a>';
|
|
|
127 |
}
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
/**
|
|
|
131 |
* Print the login in a popup.
|
|
|
132 |
*
|
|
|
133 |
* @param array|null $attr Custom attributes to be applied to popup div.
|
|
|
134 |
*/
|
|
|
135 |
public function print_login_popup($attr = null) {
|
|
|
136 |
global $OUTPUT, $PAGE;
|
|
|
137 |
|
|
|
138 |
$client = $this->get_user_oauth_client(false);
|
|
|
139 |
$url = new moodle_url($client->get_login_url());
|
|
|
140 |
$state = $url->get_param('state') . '&reloadparent=true';
|
|
|
141 |
$url->param('state', $state);
|
|
|
142 |
|
|
|
143 |
$PAGE->set_pagelayout('embedded');
|
|
|
144 |
echo $OUTPUT->header();
|
|
|
145 |
|
|
|
146 |
$repositoryname = get_string('pluginname', 'repository_onedrive');
|
|
|
147 |
|
|
|
148 |
$button = new single_button(
|
|
|
149 |
$url,
|
|
|
150 |
get_string('logintoaccount', 'repository', $repositoryname),
|
|
|
151 |
'post',
|
|
|
152 |
single_button::BUTTON_PRIMARY
|
|
|
153 |
);
|
|
|
154 |
$button->add_action(new popup_action('click', $url, 'Login'));
|
|
|
155 |
$button->class = 'mdl-align';
|
|
|
156 |
$button = $OUTPUT->render($button);
|
|
|
157 |
echo html_writer::div($button, '', $attr);
|
|
|
158 |
|
|
|
159 |
echo $OUTPUT->footer();
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
/**
|
|
|
163 |
* Build the breadcrumb from a path.
|
|
|
164 |
*
|
|
|
165 |
* @param string $path to create a breadcrumb from.
|
|
|
166 |
* @return array containing name and path of each crumb.
|
|
|
167 |
*/
|
|
|
168 |
protected function build_breadcrumb($path) {
|
|
|
169 |
$bread = explode('/', $path);
|
|
|
170 |
$crumbtrail = '';
|
|
|
171 |
foreach ($bread as $crumb) {
|
|
|
172 |
list($id, $name) = $this->explode_node_path($crumb);
|
|
|
173 |
$name = empty($name) ? $id : $name;
|
|
|
174 |
$breadcrumb[] = array(
|
|
|
175 |
'name' => $name,
|
|
|
176 |
'path' => $this->build_node_path($id, $name, $crumbtrail)
|
|
|
177 |
);
|
|
|
178 |
$tmp = end($breadcrumb);
|
|
|
179 |
$crumbtrail = $tmp['path'];
|
|
|
180 |
}
|
|
|
181 |
return $breadcrumb;
|
|
|
182 |
}
|
|
|
183 |
|
|
|
184 |
/**
|
|
|
185 |
* Generates a safe path to a node.
|
|
|
186 |
*
|
|
|
187 |
* Typically, a node will be id|Name of the node.
|
|
|
188 |
*
|
|
|
189 |
* @param string $id of the node.
|
|
|
190 |
* @param string $name of the node, will be URL encoded.
|
|
|
191 |
* @param string $root to append the node on, must be a result of this function.
|
|
|
192 |
* @return string path to the node.
|
|
|
193 |
*/
|
|
|
194 |
protected function build_node_path($id, $name = '', $root = '') {
|
|
|
195 |
$path = $id;
|
|
|
196 |
if (!empty($name)) {
|
|
|
197 |
$path .= '|' . urlencode($name);
|
|
|
198 |
}
|
|
|
199 |
if (!empty($root)) {
|
|
|
200 |
$path = trim($root, '/') . '/' . $path;
|
|
|
201 |
}
|
|
|
202 |
return $path;
|
|
|
203 |
}
|
|
|
204 |
|
|
|
205 |
/**
|
|
|
206 |
* Returns information about a node in a path.
|
|
|
207 |
*
|
|
|
208 |
* @see self::build_node_path()
|
|
|
209 |
* @param string $node to extrat information from.
|
|
|
210 |
* @return array about the node.
|
|
|
211 |
*/
|
|
|
212 |
protected function explode_node_path($node) {
|
|
|
213 |
if (strpos($node, '|') !== false) {
|
|
|
214 |
list($id, $name) = explode('|', $node, 2);
|
|
|
215 |
$name = urldecode($name);
|
|
|
216 |
} else {
|
|
|
217 |
$id = $node;
|
|
|
218 |
$name = '';
|
|
|
219 |
}
|
|
|
220 |
$id = urldecode($id);
|
|
|
221 |
return array(
|
|
|
222 |
|
|
|
223 |
1 => $name,
|
|
|
224 |
'id' => $id,
|
|
|
225 |
'name' => $name
|
|
|
226 |
);
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
/**
|
|
|
230 |
* List the files and folders.
|
|
|
231 |
*
|
|
|
232 |
* @param string $path path to browse.
|
|
|
233 |
* @param string $page page to browse.
|
|
|
234 |
* @return array of result.
|
|
|
235 |
*/
|
|
|
236 |
public function get_listing($path='', $page = '') {
|
|
|
237 |
if (empty($path)) {
|
|
|
238 |
$path = $this->build_node_path('root', get_string('pluginname', 'repository_onedrive'));
|
|
|
239 |
}
|
|
|
240 |
|
|
|
241 |
if ($this->disabled) {
|
|
|
242 |
// Empty list of files for disabled repository.
|
|
|
243 |
return ['dynload' => false, 'list' => [], 'nologin' => true];
|
|
|
244 |
}
|
|
|
245 |
|
|
|
246 |
// We analyse the path to extract what to browse.
|
|
|
247 |
$trail = explode('/', $path);
|
|
|
248 |
$uri = array_pop($trail);
|
|
|
249 |
list($id, $name) = $this->explode_node_path($uri);
|
|
|
250 |
|
|
|
251 |
// Handle the special keyword 'search', which we defined in self::search() so that
|
|
|
252 |
// we could set up a breadcrumb in the search results. In any other case ID would be
|
|
|
253 |
// 'root' which is a special keyword, or a parent (folder) ID.
|
|
|
254 |
if ($id === 'search') {
|
|
|
255 |
$q = $name;
|
|
|
256 |
$id = 'root';
|
|
|
257 |
|
|
|
258 |
// Append the active path for search.
|
|
|
259 |
$str = get_string('searchfor', 'repository_onedrive', $searchtext);
|
|
|
260 |
$path = $this->build_node_path('search', $str, $path);
|
|
|
261 |
}
|
|
|
262 |
|
|
|
263 |
// Query the Drive.
|
|
|
264 |
$parent = $id;
|
|
|
265 |
if ($parent != 'root') {
|
|
|
266 |
$parent = 'items/' . $parent;
|
|
|
267 |
}
|
|
|
268 |
$q = '';
|
|
|
269 |
$results = $this->query($q, $path, $parent);
|
|
|
270 |
|
|
|
271 |
$ret = [];
|
|
|
272 |
$ret['dynload'] = true;
|
|
|
273 |
$ret['path'] = $this->build_breadcrumb($path);
|
|
|
274 |
$ret['list'] = $results;
|
|
|
275 |
$ret['manage'] = 'https://www.office.com/';
|
|
|
276 |
return $ret;
|
|
|
277 |
}
|
|
|
278 |
|
|
|
279 |
/**
|
|
|
280 |
* Search throughout the OneDrive
|
|
|
281 |
*
|
|
|
282 |
* @param string $searchtext text to search for.
|
|
|
283 |
* @param int $page search page.
|
|
|
284 |
* @return array of results.
|
|
|
285 |
*/
|
|
|
286 |
public function search($searchtext, $page = 0) {
|
|
|
287 |
$path = $this->build_node_path('root', get_string('pluginname', 'repository_onedrive'));
|
|
|
288 |
$str = get_string('searchfor', 'repository_onedrive', $searchtext);
|
|
|
289 |
$path = $this->build_node_path('search', $str, $path);
|
|
|
290 |
|
|
|
291 |
// Query the Drive.
|
|
|
292 |
$parent = 'root';
|
|
|
293 |
$results = $this->query($searchtext, $path, 'root');
|
|
|
294 |
|
|
|
295 |
$ret = [];
|
|
|
296 |
$ret['dynload'] = true;
|
|
|
297 |
$ret['path'] = $this->build_breadcrumb($path);
|
|
|
298 |
$ret['list'] = $results;
|
|
|
299 |
$ret['manage'] = 'https://www.office.com/';
|
|
|
300 |
return $ret;
|
|
|
301 |
}
|
|
|
302 |
|
|
|
303 |
/**
|
|
|
304 |
* Query OneDrive for files and folders using a search query.
|
|
|
305 |
*
|
|
|
306 |
* Documentation about the query format can be found here:
|
|
|
307 |
* https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/driveitem
|
|
|
308 |
* https://developer.microsoft.com/en-us/graph/docs/overview/query_parameters
|
|
|
309 |
*
|
|
|
310 |
* This returns a list of files and folders with their details as they should be
|
|
|
311 |
* formatted and returned by functions such as get_listing() or search().
|
|
|
312 |
*
|
|
|
313 |
* @param string $q search query as expected by the Graph API.
|
|
|
314 |
* @param string $path parent path of the current files, will not be used for the query.
|
|
|
315 |
* @param string $parent Parent id.
|
|
|
316 |
* @param int $page page.
|
|
|
317 |
* @return array of files and folders.
|
|
|
318 |
* @throws Exception
|
|
|
319 |
* @throws repository_exception
|
|
|
320 |
*/
|
|
|
321 |
protected function query($q, $path = null, $parent = null, $page = 0) {
|
|
|
322 |
global $OUTPUT;
|
|
|
323 |
|
|
|
324 |
$files = [];
|
|
|
325 |
$folders = [];
|
|
|
326 |
$fields = "folder,id,lastModifiedDateTime,name,size,webUrl,thumbnails";
|
|
|
327 |
$params = ['$select' => $fields, '$expand' => 'thumbnails', 'parent' => $parent];
|
|
|
328 |
|
|
|
329 |
try {
|
|
|
330 |
// Retrieving files and folders.
|
|
|
331 |
$client = $this->get_user_oauth_client();
|
|
|
332 |
$service = new repository_onedrive\rest($client);
|
|
|
333 |
|
|
|
334 |
if (!empty($q)) {
|
|
|
335 |
$params['search'] = urlencode($q);
|
|
|
336 |
|
|
|
337 |
// MS does not return thumbnails on a search.
|
|
|
338 |
unset($params['$expand']);
|
|
|
339 |
$response = $service->call('search', $params);
|
|
|
340 |
} else {
|
|
|
341 |
$response = $service->call('list', $params);
|
|
|
342 |
}
|
|
|
343 |
} catch (Exception $e) {
|
|
|
344 |
if ($e->getCode() == 403 && strpos($e->getMessage(), 'Access Not Configured') !== false) {
|
|
|
345 |
throw new repository_exception('servicenotenabled', 'repository_onedrive');
|
|
|
346 |
} else if (strpos($e->getMessage(), 'mysite not found') !== false) {
|
|
|
347 |
throw new repository_exception('mysitenotfound', 'repository_onedrive');
|
|
|
348 |
}
|
|
|
349 |
}
|
|
|
350 |
|
|
|
351 |
$remotefiles = isset($response->value) ? $response->value : [];
|
|
|
352 |
foreach ($remotefiles as $remotefile) {
|
|
|
353 |
if (!empty($remotefile->folder)) {
|
|
|
354 |
// This is a folder.
|
|
|
355 |
$folders[$remotefile->id] = [
|
|
|
356 |
'title' => $remotefile->name,
|
|
|
357 |
'path' => $this->build_node_path($remotefile->id, $remotefile->name, $path),
|
|
|
358 |
'date' => strtotime($remotefile->lastModifiedDateTime),
|
|
|
359 |
'thumbnail' => $OUTPUT->image_url(file_folder_icon())->out(false),
|
|
|
360 |
'thumbnail_height' => 64,
|
|
|
361 |
'thumbnail_width' => 64,
|
|
|
362 |
'children' => []
|
|
|
363 |
];
|
|
|
364 |
} else {
|
|
|
365 |
// We can download all other file types.
|
|
|
366 |
$title = $remotefile->name;
|
|
|
367 |
$source = json_encode([
|
|
|
368 |
'id' => $remotefile->id,
|
|
|
369 |
'name' => $remotefile->name,
|
|
|
370 |
'link' => $remotefile->webUrl
|
|
|
371 |
]);
|
|
|
372 |
|
|
|
373 |
$thumb = '';
|
|
|
374 |
$thumbwidth = 0;
|
|
|
375 |
$thumbheight = 0;
|
|
|
376 |
$extendedinfoerr = false;
|
|
|
377 |
|
|
|
378 |
if (empty($remotefile->thumbnails)) {
|
|
|
379 |
// Try and get it directly from the item.
|
|
|
380 |
$params = ['fileid' => $remotefile->id, '$select' => $fields, '$expand' => 'thumbnails'];
|
|
|
381 |
try {
|
|
|
382 |
$response = $service->call('get', $params);
|
|
|
383 |
$remotefile = $response;
|
|
|
384 |
} catch (Exception $e) {
|
|
|
385 |
// This is not a failure condition - we just could not get extended info about the file.
|
|
|
386 |
$extendedinfoerr = true;
|
|
|
387 |
}
|
|
|
388 |
}
|
|
|
389 |
|
|
|
390 |
if (!empty($remotefile->thumbnails)) {
|
|
|
391 |
$thumbs = $remotefile->thumbnails;
|
|
|
392 |
if (count($thumbs)) {
|
|
|
393 |
$first = reset($thumbs);
|
|
|
394 |
if (!empty($first->medium) && !empty($first->medium->url)) {
|
|
|
395 |
$thumb = $first->medium->url;
|
|
|
396 |
$thumbwidth = min($first->medium->width, 64);
|
|
|
397 |
$thumbheight = min($first->medium->height, 64);
|
|
|
398 |
}
|
|
|
399 |
}
|
|
|
400 |
}
|
|
|
401 |
|
|
|
402 |
$files[$remotefile->id] = [
|
|
|
403 |
'title' => $title,
|
|
|
404 |
'source' => $source,
|
|
|
405 |
'date' => strtotime($remotefile->lastModifiedDateTime),
|
|
|
406 |
'size' => isset($remotefile->size) ? $remotefile->size : null,
|
|
|
407 |
'thumbnail' => $thumb,
|
|
|
408 |
'thumbnail_height' => $thumbwidth,
|
|
|
409 |
'thumbnail_width' => $thumbheight,
|
|
|
410 |
];
|
|
|
411 |
}
|
|
|
412 |
}
|
|
|
413 |
|
|
|
414 |
// Filter and order the results.
|
|
|
415 |
$files = array_filter($files, [$this, 'filter']);
|
|
|
416 |
core_collator::ksort($files, core_collator::SORT_NATURAL);
|
|
|
417 |
core_collator::ksort($folders, core_collator::SORT_NATURAL);
|
|
|
418 |
return array_merge(array_values($folders), array_values($files));
|
|
|
419 |
}
|
|
|
420 |
|
|
|
421 |
/**
|
|
|
422 |
* Logout.
|
|
|
423 |
*
|
|
|
424 |
* @return string
|
|
|
425 |
*/
|
|
|
426 |
public function logout() {
|
|
|
427 |
$client = $this->get_user_oauth_client();
|
|
|
428 |
$client->log_out();
|
|
|
429 |
return parent::logout();
|
|
|
430 |
}
|
|
|
431 |
|
|
|
432 |
/**
|
|
|
433 |
* Get a file.
|
|
|
434 |
*
|
|
|
435 |
* @param string $reference reference of the file.
|
|
|
436 |
* @param string $filename filename to save the file to.
|
|
|
437 |
* @return string JSON encoded array of information about the file.
|
|
|
438 |
*/
|
|
|
439 |
public function get_file($reference, $filename = '') {
|
|
|
440 |
global $CFG;
|
|
|
441 |
|
|
|
442 |
if ($this->disabled) {
|
|
|
443 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
444 |
}
|
|
|
445 |
$sourceinfo = json_decode($reference);
|
|
|
446 |
|
|
|
447 |
$client = null;
|
|
|
448 |
if (!empty($sourceinfo->usesystem)) {
|
|
|
449 |
$client = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
450 |
} else {
|
|
|
451 |
$client = $this->get_user_oauth_client();
|
|
|
452 |
}
|
|
|
453 |
|
|
|
454 |
$base = 'https://graph.microsoft.com/v1.0/';
|
|
|
455 |
|
|
|
456 |
$sourceurl = new moodle_url($base . 'me/drive/items/' . $sourceinfo->id . '/content');
|
|
|
457 |
$source = $sourceurl->out(false);
|
|
|
458 |
|
|
|
459 |
// We use download_one and not the rest API because it has special timeouts etc.
|
|
|
460 |
$path = $this->prepare_file($filename);
|
|
|
461 |
$options = ['filepath' => $path, 'timeout' => 15, 'followlocation' => true, 'maxredirs' => 5];
|
|
|
462 |
$result = $client->download_one($source, null, $options);
|
|
|
463 |
|
|
|
464 |
if ($result) {
|
|
|
465 |
@chmod($path, $CFG->filepermissions);
|
|
|
466 |
return array(
|
|
|
467 |
'path' => $path,
|
|
|
468 |
'url' => $reference
|
|
|
469 |
);
|
|
|
470 |
}
|
|
|
471 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
472 |
}
|
|
|
473 |
|
|
|
474 |
/**
|
|
|
475 |
* Prepare file reference information.
|
|
|
476 |
*
|
|
|
477 |
* We are using this method to clean up the source to make sure that it
|
|
|
478 |
* is a valid source.
|
|
|
479 |
*
|
|
|
480 |
* @param string $source of the file.
|
|
|
481 |
* @return string file reference.
|
|
|
482 |
*/
|
|
|
483 |
public function get_file_reference($source) {
|
|
|
484 |
// We could do some magic upgrade code here.
|
|
|
485 |
return $source;
|
|
|
486 |
}
|
|
|
487 |
|
|
|
488 |
/**
|
|
|
489 |
* What kind of files will be in this repository?
|
|
|
490 |
*
|
|
|
491 |
* @return array return '*' means this repository support any files, otherwise
|
|
|
492 |
* return mimetypes of files, it can be an array
|
|
|
493 |
*/
|
|
|
494 |
public function supported_filetypes() {
|
|
|
495 |
return '*';
|
|
|
496 |
}
|
|
|
497 |
|
|
|
498 |
/**
|
|
|
499 |
* Tells how the file can be picked from this repository.
|
|
|
500 |
*
|
|
|
501 |
* @return int
|
|
|
502 |
*/
|
|
|
503 |
public function supported_returntypes() {
|
|
|
504 |
// We can only support references if the system account is connected.
|
|
|
505 |
if (!empty($this->issuer) && $this->issuer->is_system_account_connected()) {
|
|
|
506 |
$setting = get_config('onedrive', 'supportedreturntypes');
|
|
|
507 |
if ($setting == 'internal') {
|
|
|
508 |
return FILE_INTERNAL;
|
|
|
509 |
} else if ($setting == 'external') {
|
|
|
510 |
return FILE_CONTROLLED_LINK;
|
|
|
511 |
} else {
|
|
|
512 |
return FILE_CONTROLLED_LINK | FILE_INTERNAL;
|
|
|
513 |
}
|
|
|
514 |
} else {
|
|
|
515 |
return FILE_INTERNAL;
|
|
|
516 |
}
|
|
|
517 |
}
|
|
|
518 |
|
|
|
519 |
/**
|
|
|
520 |
* Which return type should be selected by default.
|
|
|
521 |
*
|
|
|
522 |
* @return int
|
|
|
523 |
*/
|
|
|
524 |
public function default_returntype() {
|
|
|
525 |
$setting = get_config('onedrive', 'defaultreturntype');
|
|
|
526 |
$supported = get_config('onedrive', 'supportedreturntypes');
|
|
|
527 |
if (($setting == FILE_INTERNAL && $supported != 'external') || $supported == 'internal') {
|
|
|
528 |
return FILE_INTERNAL;
|
|
|
529 |
} else {
|
|
|
530 |
return FILE_CONTROLLED_LINK;
|
|
|
531 |
}
|
|
|
532 |
}
|
|
|
533 |
|
|
|
534 |
/**
|
|
|
535 |
* Return names of the general options.
|
|
|
536 |
* By default: no general option name.
|
|
|
537 |
*
|
|
|
538 |
* @return array
|
|
|
539 |
*/
|
|
|
540 |
public static function get_type_option_names() {
|
|
|
541 |
return array('issuerid', 'pluginname', 'defaultreturntype', 'supportedreturntypes');
|
|
|
542 |
}
|
|
|
543 |
|
|
|
544 |
/**
|
|
|
545 |
* Store the access token.
|
|
|
546 |
*/
|
|
|
547 |
public function callback() {
|
|
|
548 |
$client = $this->get_user_oauth_client();
|
|
|
549 |
// This will upgrade to an access token if we have an authorization code and save the access token in the session.
|
|
|
550 |
$client->is_logged_in();
|
|
|
551 |
}
|
|
|
552 |
|
|
|
553 |
/**
|
|
|
554 |
* Repository method to serve the referenced file
|
|
|
555 |
*
|
|
|
556 |
* @see send_stored_file
|
|
|
557 |
*
|
|
|
558 |
* @param stored_file $storedfile the file that contains the reference
|
|
|
559 |
* @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
|
|
|
560 |
* @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
|
|
|
561 |
* @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
|
|
|
562 |
* @param array $options additional options affecting the file serving
|
|
|
563 |
*/
|
|
|
564 |
public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
|
|
|
565 |
if ($this->disabled) {
|
|
|
566 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
567 |
}
|
|
|
568 |
|
|
|
569 |
$source = json_decode($storedfile->get_reference());
|
|
|
570 |
|
|
|
571 |
$fb = get_file_browser();
|
|
|
572 |
$context = context::instance_by_id($storedfile->get_contextid(), MUST_EXIST);
|
|
|
573 |
$info = $fb->get_file_info($context,
|
|
|
574 |
$storedfile->get_component(),
|
|
|
575 |
$storedfile->get_filearea(),
|
|
|
576 |
$storedfile->get_itemid(),
|
|
|
577 |
$storedfile->get_filepath(),
|
|
|
578 |
$storedfile->get_filename());
|
|
|
579 |
|
|
|
580 |
if (empty($options['offline']) && !empty($info) && $info->is_writable() && !empty($source->usesystem)) {
|
|
|
581 |
// Add the current user as an OAuth writer.
|
|
|
582 |
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
583 |
|
|
|
584 |
if ($systemauth === false) {
|
|
|
585 |
$details = 'Cannot connect as system user';
|
|
|
586 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
587 |
}
|
|
|
588 |
$systemservice = new repository_onedrive\rest($systemauth);
|
|
|
589 |
|
|
|
590 |
// Get the user oauth so we can get the account to add.
|
|
|
591 |
$url = moodle_url::make_pluginfile_url($storedfile->get_contextid(),
|
|
|
592 |
$storedfile->get_component(),
|
|
|
593 |
$storedfile->get_filearea(),
|
|
|
594 |
$storedfile->get_itemid(),
|
|
|
595 |
$storedfile->get_filepath(),
|
|
|
596 |
$storedfile->get_filename(),
|
|
|
597 |
$forcedownload);
|
|
|
598 |
$url->param('sesskey', sesskey());
|
|
|
599 |
$param = ($options['embed'] == true) ? false : $url;
|
|
|
600 |
$userauth = $this->get_user_oauth_client($param);
|
|
|
601 |
|
|
|
602 |
if (!$userauth->is_logged_in()) {
|
|
|
603 |
if ($options['embed'] == true) {
|
|
|
604 |
// Due to Same-origin policy, we cannot redirect to onedrive login page.
|
|
|
605 |
// If the requested file is embed and the user is not logged in, add option to log in using a popup.
|
|
|
606 |
$this->print_login_popup(['style' => 'margin-top: 250px']);
|
|
|
607 |
exit;
|
|
|
608 |
}
|
|
|
609 |
redirect($userauth->get_login_url());
|
|
|
610 |
}
|
|
|
611 |
if ($userauth === false) {
|
|
|
612 |
$details = 'Cannot connect as current user';
|
|
|
613 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
614 |
}
|
|
|
615 |
$userinfo = $userauth->get_userinfo();
|
|
|
616 |
$useremail = $userinfo['email'];
|
|
|
617 |
|
|
|
618 |
$this->add_temp_writer_to_file($systemservice, $source->id, $useremail);
|
|
|
619 |
}
|
|
|
620 |
|
|
|
621 |
if (!empty($options['offline'])) {
|
|
|
622 |
$downloaded = $this->get_file($storedfile->get_reference(), $storedfile->get_filename());
|
|
|
623 |
$filename = $storedfile->get_filename();
|
|
|
624 |
send_file($downloaded['path'], $filename, $lifetime, $filter, false, $forcedownload, '', false, $options);
|
|
|
625 |
} else if ($source->link) {
|
|
|
626 |
// Do not use redirect() here because is not compatible with webservice/pluginfile.php.
|
|
|
627 |
header('Location: ' . $source->link);
|
|
|
628 |
} else {
|
|
|
629 |
$details = 'File is missing source link';
|
|
|
630 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
631 |
}
|
|
|
632 |
}
|
|
|
633 |
|
|
|
634 |
/**
|
|
|
635 |
* See if a folder exists within a folder
|
|
|
636 |
*
|
|
|
637 |
* @param \repository_onedrive\rest $client Authenticated client.
|
|
|
638 |
* @param string $fullpath
|
|
|
639 |
* @return string|boolean The file id if it exists or false.
|
|
|
640 |
*/
|
|
|
641 |
protected function get_file_id_by_path(\repository_onedrive\rest $client, $fullpath) {
|
|
|
642 |
$fields = "id";
|
|
|
643 |
try {
|
|
|
644 |
$response = $client->call('get_file_by_path', ['fullpath' => $fullpath, '$select' => $fields]);
|
|
|
645 |
} catch (\core\oauth2\rest_exception $re) {
|
|
|
646 |
return false;
|
|
|
647 |
}
|
|
|
648 |
return $response->id;
|
|
|
649 |
}
|
|
|
650 |
|
|
|
651 |
/**
|
|
|
652 |
* Delete a file by full path.
|
|
|
653 |
*
|
|
|
654 |
* @param \repository_onedrive\rest $client Authenticated client.
|
|
|
655 |
* @param string $fullpath
|
|
|
656 |
* @return boolean
|
|
|
657 |
*/
|
|
|
658 |
protected function delete_file_by_path(\repository_onedrive\rest $client, $fullpath) {
|
|
|
659 |
try {
|
|
|
660 |
$response = $client->call('delete_file_by_path', ['fullpath' => $fullpath]);
|
|
|
661 |
} catch (\core\oauth2\rest_exception $re) {
|
|
|
662 |
return false;
|
|
|
663 |
}
|
|
|
664 |
return true;
|
|
|
665 |
}
|
|
|
666 |
|
|
|
667 |
/**
|
|
|
668 |
* Create a folder within a folder
|
|
|
669 |
*
|
|
|
670 |
* @param \repository_onedrive\rest $client Authenticated client.
|
|
|
671 |
* @param string $foldername The folder we are creating.
|
|
|
672 |
* @param string $parentid The parent folder we are creating in.
|
|
|
673 |
*
|
|
|
674 |
* @return string The file id of the new folder.
|
|
|
675 |
*/
|
|
|
676 |
protected function create_folder_in_folder(\repository_onedrive\rest $client, $foldername, $parentid) {
|
|
|
677 |
$params = ['parentid' => $parentid];
|
|
|
678 |
$folder = [ 'name' => $foldername, 'folder' => [ 'childCount' => 0 ]];
|
|
|
679 |
$created = $client->call('create_folder', $params, json_encode($folder));
|
|
|
680 |
if (empty($created->id)) {
|
|
|
681 |
$details = 'Cannot create folder:' . $foldername;
|
|
|
682 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
683 |
}
|
|
|
684 |
return $created->id;
|
|
|
685 |
}
|
|
|
686 |
|
|
|
687 |
/**
|
|
|
688 |
* Get simple file info for humans.
|
|
|
689 |
*
|
|
|
690 |
* @param \repository_onedrive\rest $client Authenticated client.
|
|
|
691 |
* @param string $fileid The file we are querying.
|
|
|
692 |
*
|
|
|
693 |
* @return stdClass
|
|
|
694 |
*/
|
|
|
695 |
protected function get_file_summary(\repository_onedrive\rest $client, $fileid) {
|
|
|
696 |
$fields = "folder,id,lastModifiedDateTime,name,size,webUrl,createdByUser";
|
|
|
697 |
$response = $client->call('get', ['fileid' => $fileid, '$select' => $fields]);
|
|
|
698 |
return $response;
|
|
|
699 |
}
|
|
|
700 |
|
|
|
701 |
/**
|
|
|
702 |
* Add a writer to the permissions on the file (temporary).
|
|
|
703 |
*
|
|
|
704 |
* @param \repository_onedrive\rest $client Authenticated client.
|
|
|
705 |
* @param string $fileid The file we are updating.
|
|
|
706 |
* @param string $email The email of the writer account to add.
|
|
|
707 |
* @return boolean
|
|
|
708 |
*/
|
|
|
709 |
protected function add_temp_writer_to_file(\repository_onedrive\rest $client, $fileid, $email) {
|
|
|
710 |
// Expires in 7 days.
|
|
|
711 |
$expires = new DateTime();
|
|
|
712 |
$expires->add(new DateInterval("P7D"));
|
|
|
713 |
|
|
|
714 |
$updateeditor = [
|
|
|
715 |
'recipients' => [[ 'email' => $email ]],
|
|
|
716 |
'roles' => ['write'],
|
|
|
717 |
'requireSignIn' => true,
|
|
|
718 |
'sendInvitation' => false
|
|
|
719 |
];
|
|
|
720 |
$params = ['fileid' => $fileid];
|
|
|
721 |
$response = $client->call('create_permission', $params, json_encode($updateeditor));
|
|
|
722 |
if (empty($response->value[0]->id)) {
|
|
|
723 |
$details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
|
|
|
724 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
725 |
}
|
|
|
726 |
// Store the permission id in the DB. Scheduled task will remove this permission after 7 days.
|
|
|
727 |
if ($access = repository_onedrive\access::get_record(['permissionid' => $response->value[0]->id, 'itemid' => $fileid ])) {
|
|
|
728 |
// Update the timemodified.
|
|
|
729 |
$access->update();
|
|
|
730 |
} else {
|
|
|
731 |
$record = (object) [ 'permissionid' => $response->value[0]->id, 'itemid' => $fileid ];
|
|
|
732 |
$access = new repository_onedrive\access(0, $record);
|
|
|
733 |
$access->create();
|
|
|
734 |
}
|
|
|
735 |
return true;
|
|
|
736 |
}
|
|
|
737 |
|
|
|
738 |
/**
|
|
|
739 |
* Allow anyone with the link to read the file.
|
|
|
740 |
*
|
|
|
741 |
* @param \repository_onedrive\rest $client Authenticated client.
|
|
|
742 |
* @param string $fileid The file we are updating.
|
|
|
743 |
* @return boolean
|
|
|
744 |
*/
|
|
|
745 |
protected function set_file_sharing_anyone_with_link_can_read(\repository_onedrive\rest $client, $fileid) {
|
|
|
746 |
|
|
|
747 |
$type = (isset($this->options['embed']) && $this->options['embed'] == true) ? 'embed' : 'view';
|
|
|
748 |
$updateread = [
|
|
|
749 |
'type' => $type,
|
|
|
750 |
'scope' => 'anonymous'
|
|
|
751 |
];
|
|
|
752 |
$params = ['fileid' => $fileid];
|
|
|
753 |
$response = $client->call('create_link', $params, json_encode($updateread));
|
|
|
754 |
if (empty($response->link)) {
|
|
|
755 |
$details = 'Cannot update link sharing for the document: ' . $fileid;
|
|
|
756 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
757 |
}
|
|
|
758 |
return $response->link->webUrl;
|
|
|
759 |
}
|
|
|
760 |
|
|
|
761 |
/**
|
|
|
762 |
* Given a filename, use the core_filetypes registered types to guess a mimetype.
|
|
|
763 |
*
|
|
|
764 |
* If no mimetype is known, return 'application/unknown';
|
|
|
765 |
*
|
|
|
766 |
* @param string $filename
|
|
|
767 |
* @return string $mimetype
|
|
|
768 |
*/
|
|
|
769 |
protected function get_mimetype_from_filename($filename) {
|
|
|
770 |
$mimetype = 'application/unknown';
|
|
|
771 |
$types = core_filetypes::get_types();
|
|
|
772 |
$fileextension = '.bin';
|
|
|
773 |
if (strpos($filename, '.') !== false) {
|
|
|
774 |
$fileextension = substr($filename, strrpos($filename, '.') + 1);
|
|
|
775 |
}
|
|
|
776 |
|
|
|
777 |
if (isset($types[$fileextension])) {
|
|
|
778 |
$mimetype = $types[$fileextension]['type'];
|
|
|
779 |
}
|
|
|
780 |
return $mimetype;
|
|
|
781 |
}
|
|
|
782 |
|
|
|
783 |
/**
|
|
|
784 |
* Upload a file to onedrive.
|
|
|
785 |
*
|
|
|
786 |
* @param \repository_onedrive\rest $service Authenticated client.
|
|
|
787 |
* @param \curl $curl Curl client to perform the put operation (with no auth headers).
|
|
|
788 |
* @param \curl $authcurl Curl client that will send authentication headers
|
|
|
789 |
* @param string $filepath The local path to the file to upload
|
|
|
790 |
* @param string $mimetype The new mimetype
|
|
|
791 |
* @param string $parentid The folder to put it.
|
|
|
792 |
* @param string $filename The name of the new file
|
|
|
793 |
* @return string $fileid
|
|
|
794 |
*/
|
|
|
795 |
protected function upload_file(\repository_onedrive\rest $service, \curl $curl, \curl $authcurl,
|
|
|
796 |
$filepath, $mimetype, $parentid, $filename) {
|
|
|
797 |
// Start an upload session.
|
|
|
798 |
// Docs https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/item_createuploadsession link.
|
|
|
799 |
|
|
|
800 |
$params = ['parentid' => $parentid, 'filename' => urlencode($filename)];
|
|
|
801 |
$behaviour = [ 'item' => [ "@microsoft.graph.conflictBehavior" => "rename" ] ];
|
|
|
802 |
$created = $service->call('create_upload', $params, json_encode($behaviour));
|
|
|
803 |
if (empty($created->uploadUrl)) {
|
|
|
804 |
$details = 'Cannot begin upload session:' . $parentid;
|
|
|
805 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
806 |
}
|
|
|
807 |
|
|
|
808 |
$options = ['file' => $filepath];
|
|
|
809 |
|
|
|
810 |
// Try each curl class in turn until we succeed.
|
|
|
811 |
// First attempt an upload with no auth headers (will work for personal onedrive accounts).
|
|
|
812 |
// If that fails, try an upload with the auth headers (will work for work onedrive accounts).
|
|
|
813 |
$curls = [$curl, $authcurl];
|
|
|
814 |
$response = null;
|
|
|
815 |
foreach ($curls as $curlinstance) {
|
|
|
816 |
$curlinstance->setHeader('Content-type: ' . $mimetype);
|
|
|
817 |
$size = filesize($filepath);
|
|
|
818 |
$curlinstance->setHeader('Content-Range: bytes 0-' . ($size - 1) . '/' . $size);
|
|
|
819 |
$response = $curlinstance->put($created->uploadUrl, $options);
|
|
|
820 |
if ($curlinstance->errno == 0) {
|
|
|
821 |
$response = json_decode($response);
|
|
|
822 |
}
|
|
|
823 |
if (!empty($response->id)) {
|
|
|
824 |
// We can stop now - there is a valid file returned.
|
|
|
825 |
break;
|
|
|
826 |
}
|
|
|
827 |
}
|
|
|
828 |
|
|
|
829 |
if (empty($response->id)) {
|
|
|
830 |
$details = 'File not created';
|
|
|
831 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
832 |
}
|
|
|
833 |
|
|
|
834 |
return $response->id;
|
|
|
835 |
}
|
|
|
836 |
|
|
|
837 |
|
|
|
838 |
/**
|
|
|
839 |
* Called when a file is selected as a "link".
|
|
|
840 |
* Invoked at MOODLE/repository/repository_ajax.php
|
|
|
841 |
*
|
|
|
842 |
* What should happen here is that the file should be copied to a new file owned by the moodle system user.
|
|
|
843 |
* It should be organised in a folder based on the file context.
|
|
|
844 |
* It's sharing permissions should allow read access with the link.
|
|
|
845 |
* The returned reference should point to the newly copied file - not the original.
|
|
|
846 |
*
|
|
|
847 |
* @param string $reference this reference is generated by
|
|
|
848 |
* repository::get_file_reference()
|
|
|
849 |
* @param context $context the target context for this new file.
|
|
|
850 |
* @param string $component the target component for this new file.
|
|
|
851 |
* @param string $filearea the target filearea for this new file.
|
|
|
852 |
* @param string $itemid the target itemid for this new file.
|
|
|
853 |
* @return string $modifiedreference (final one before saving to DB)
|
|
|
854 |
*/
|
|
|
855 |
public function reference_file_selected($reference, $context, $component, $filearea, $itemid) {
|
|
|
856 |
global $CFG, $SITE;
|
|
|
857 |
|
|
|
858 |
// What we need to do here is transfer ownership to the system user (or copy)
|
|
|
859 |
// then set the permissions so anyone with the share link can view,
|
|
|
860 |
// finally update the reference to contain the share link if it was not
|
|
|
861 |
// already there (and point to new file id if we copied).
|
|
|
862 |
$source = json_decode($reference);
|
|
|
863 |
if (!empty($source->usesystem)) {
|
|
|
864 |
// If we already copied this file to the system account - we are done.
|
|
|
865 |
return $reference;
|
|
|
866 |
}
|
|
|
867 |
|
|
|
868 |
// Get a system and a user oauth client.
|
|
|
869 |
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
870 |
|
|
|
871 |
if ($systemauth === false) {
|
|
|
872 |
$details = 'Cannot connect as system user';
|
|
|
873 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
874 |
}
|
|
|
875 |
|
|
|
876 |
$userauth = $this->get_user_oauth_client();
|
|
|
877 |
if ($userauth === false) {
|
|
|
878 |
$details = 'Cannot connect as current user';
|
|
|
879 |
throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
|
|
|
880 |
}
|
|
|
881 |
|
|
|
882 |
$systemservice = new repository_onedrive\rest($systemauth);
|
|
|
883 |
|
|
|
884 |
// Download the file.
|
|
|
885 |
$tmpfilename = clean_param($source->id, PARAM_PATH);
|
|
|
886 |
$temppath = make_request_directory() . $tmpfilename;
|
|
|
887 |
|
|
|
888 |
$options = ['filepath' => $temppath, 'timeout' => 60, 'followlocation' => true, 'maxredirs' => 5];
|
|
|
889 |
$base = 'https://graph.microsoft.com/v1.0/';
|
|
|
890 |
$sourceurl = new moodle_url($base . 'me/drive/items/' . $source->id . '/content');
|
|
|
891 |
$sourceurl = $sourceurl->out(false);
|
|
|
892 |
|
|
|
893 |
$result = $userauth->download_one($sourceurl, null, $options);
|
|
|
894 |
|
|
|
895 |
if (!$result) {
|
|
|
896 |
throw new repository_exception('cannotdownload', 'repository');
|
|
|
897 |
}
|
|
|
898 |
|
|
|
899 |
// Now copy it to a sensible folder.
|
|
|
900 |
$contextlist = array_reverse($context->get_parent_contexts(true));
|
|
|
901 |
|
|
|
902 |
$cache = cache::make('repository_onedrive', 'folder');
|
|
|
903 |
$parentid = 'root';
|
|
|
904 |
$fullpath = '';
|
|
|
905 |
$allfolders = [];
|
|
|
906 |
foreach ($contextlist as $context) {
|
|
|
907 |
// Prepare human readable context folders names, making sure they are still unique within the site.
|
|
|
908 |
$prevlang = force_current_language($CFG->lang);
|
|
|
909 |
$foldername = $context->get_context_name();
|
|
|
910 |
force_current_language($prevlang);
|
|
|
911 |
|
|
|
912 |
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
|
|
913 |
// Append the site short name to the root folder.
|
|
|
914 |
$foldername .= '_'.$SITE->shortname;
|
|
|
915 |
// Append the relevant object id.
|
|
|
916 |
} else if ($context->instanceid) {
|
|
|
917 |
$foldername .= '_id_'.$context->instanceid;
|
|
|
918 |
} else {
|
|
|
919 |
// This does not really happen but just in case.
|
|
|
920 |
$foldername .= '_ctx_'.$context->id;
|
|
|
921 |
}
|
|
|
922 |
|
|
|
923 |
$foldername = urlencode(clean_param($foldername, PARAM_PATH));
|
|
|
924 |
$allfolders[] = $foldername;
|
|
|
925 |
}
|
|
|
926 |
|
|
|
927 |
$allfolders[] = urlencode(clean_param($component, PARAM_PATH));
|
|
|
928 |
$allfolders[] = urlencode(clean_param($filearea, PARAM_PATH));
|
|
|
929 |
$allfolders[] = urlencode(clean_param($itemid, PARAM_PATH));
|
|
|
930 |
|
|
|
931 |
// Variable $allfolders now has the complete path we want to store the file in.
|
|
|
932 |
// Create each folder in $allfolders under the system account.
|
|
|
933 |
foreach ($allfolders as $foldername) {
|
|
|
934 |
if ($fullpath) {
|
|
|
935 |
$fullpath .= '/';
|
|
|
936 |
}
|
|
|
937 |
$fullpath .= $foldername;
|
|
|
938 |
|
|
|
939 |
$folderid = $cache->get($fullpath);
|
|
|
940 |
if (empty($folderid)) {
|
|
|
941 |
$folderid = $this->get_file_id_by_path($systemservice, $fullpath);
|
|
|
942 |
}
|
|
|
943 |
if ($folderid !== false) {
|
|
|
944 |
$cache->set($fullpath, $folderid);
|
|
|
945 |
$parentid = $folderid;
|
|
|
946 |
} else {
|
|
|
947 |
// Create it.
|
|
|
948 |
$parentid = $this->create_folder_in_folder($systemservice, $foldername, $parentid);
|
|
|
949 |
$cache->set($fullpath, $parentid);
|
|
|
950 |
}
|
|
|
951 |
}
|
|
|
952 |
|
|
|
953 |
// Delete any existing file at this path.
|
|
|
954 |
$path = $fullpath . '/' . urlencode(clean_param($source->name, PARAM_PATH));
|
|
|
955 |
$this->delete_file_by_path($systemservice, $path);
|
|
|
956 |
|
|
|
957 |
// Upload the file.
|
|
|
958 |
$safefilename = clean_param($source->name, PARAM_PATH);
|
|
|
959 |
$mimetype = $this->get_mimetype_from_filename($safefilename);
|
|
|
960 |
// We cannot send authorization headers in the upload or personal microsoft accounts will fail (what a joke!).
|
|
|
961 |
$curl = new \curl();
|
|
|
962 |
$fileid = $this->upload_file($systemservice, $curl, $systemauth, $temppath, $mimetype, $parentid, $safefilename);
|
|
|
963 |
|
|
|
964 |
// Read with link.
|
|
|
965 |
$link = $this->set_file_sharing_anyone_with_link_can_read($systemservice, $fileid);
|
|
|
966 |
|
|
|
967 |
$summary = $this->get_file_summary($systemservice, $fileid);
|
|
|
968 |
|
|
|
969 |
// Update the details in the file reference before it is saved.
|
|
|
970 |
$source->id = $summary->id;
|
|
|
971 |
$source->link = $link;
|
|
|
972 |
$source->usesystem = true;
|
|
|
973 |
|
|
|
974 |
$reference = json_encode($source);
|
|
|
975 |
|
|
|
976 |
return $reference;
|
|
|
977 |
}
|
|
|
978 |
|
|
|
979 |
/**
|
|
|
980 |
* Get human readable file info from the reference.
|
|
|
981 |
*
|
|
|
982 |
* @param string $reference
|
|
|
983 |
* @param int $filestatus
|
|
|
984 |
*/
|
|
|
985 |
public function get_reference_details($reference, $filestatus = 0) {
|
|
|
986 |
if (empty($reference)) {
|
|
|
987 |
return get_string('unknownsource', 'repository');
|
|
|
988 |
}
|
|
|
989 |
$source = json_decode($reference);
|
|
|
990 |
if (empty($source->usesystem)) {
|
|
|
991 |
return '';
|
|
|
992 |
}
|
|
|
993 |
$systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
|
|
994 |
|
|
|
995 |
if ($systemauth === false) {
|
|
|
996 |
return '';
|
|
|
997 |
}
|
|
|
998 |
$systemservice = new repository_onedrive\rest($systemauth);
|
|
|
999 |
$info = $this->get_file_summary($systemservice, $source->id);
|
|
|
1000 |
|
|
|
1001 |
$owner = '';
|
|
|
1002 |
if (!empty($info->createdByUser->displayName)) {
|
|
|
1003 |
$owner = $info->createdByUser->displayName;
|
|
|
1004 |
}
|
|
|
1005 |
if ($owner) {
|
|
|
1006 |
return get_string('owner', 'repository_onedrive', $owner);
|
|
|
1007 |
} else {
|
|
|
1008 |
return $info->name;
|
|
|
1009 |
}
|
|
|
1010 |
}
|
|
|
1011 |
|
|
|
1012 |
/**
|
|
|
1013 |
* Return true if any instances of the skydrive repo exist - and we can import them.
|
|
|
1014 |
*
|
|
|
1015 |
* @return bool
|
|
|
1016 |
* @deprecated since Moodle 4.0
|
|
|
1017 |
* @todo MDL-72620 This will be deleted in Moodle 4.4.
|
|
|
1018 |
*/
|
|
|
1019 |
public static function can_import_skydrive_files() {
|
|
|
1020 |
global $DB;
|
|
|
1021 |
|
|
|
1022 |
$skydrive = $DB->get_record('repository', ['type' => 'skydrive'], 'id', IGNORE_MISSING);
|
|
|
1023 |
$onedrive = $DB->get_record('repository', ['type' => 'onedrive'], 'id', IGNORE_MISSING);
|
|
|
1024 |
|
|
|
1025 |
if (empty($skydrive) || empty($onedrive)) {
|
|
|
1026 |
return false;
|
|
|
1027 |
}
|
|
|
1028 |
|
|
|
1029 |
$ready = true;
|
|
|
1030 |
try {
|
|
|
1031 |
$issuer = \core\oauth2\api::get_issuer(get_config('onedrive', 'issuerid'));
|
|
|
1032 |
if (!$issuer->get('enabled')) {
|
|
|
1033 |
$ready = false;
|
|
|
1034 |
}
|
|
|
1035 |
if (!$issuer->is_configured()) {
|
|
|
1036 |
$ready = false;
|
|
|
1037 |
}
|
|
|
1038 |
} catch (dml_missing_record_exception $e) {
|
|
|
1039 |
$ready = false;
|
|
|
1040 |
}
|
|
|
1041 |
if (!$ready) {
|
|
|
1042 |
return false;
|
|
|
1043 |
}
|
|
|
1044 |
|
|
|
1045 |
$sql = "SELECT count('x')
|
|
|
1046 |
FROM {repository_instances} i, {repository} r
|
|
|
1047 |
WHERE r.type=:plugin AND r.id=i.typeid";
|
|
|
1048 |
$params = array('plugin' => 'skydrive');
|
|
|
1049 |
return $DB->count_records_sql($sql, $params) > 0;
|
|
|
1050 |
}
|
|
|
1051 |
|
|
|
1052 |
/**
|
|
|
1053 |
* Import all the files that were created with the skydrive repo to this repo.
|
|
|
1054 |
*
|
|
|
1055 |
* @return bool
|
|
|
1056 |
* @deprecated since Moodle 4.0
|
|
|
1057 |
* @todo MDL-72620 This will be deleted in Moodle 4.4.
|
|
|
1058 |
*/
|
|
|
1059 |
public static function import_skydrive_files() {
|
|
|
1060 |
global $DB;
|
|
|
1061 |
|
|
|
1062 |
debugging('import_skydrive_files() is deprecated. Please migrate your files from repository_skydrive to ' .
|
|
|
1063 |
'repository_onedrive before it will be completely removed.', DEBUG_DEVELOPER);
|
|
|
1064 |
|
|
|
1065 |
if (!self::can_import_skydrive_files()) {
|
|
|
1066 |
return false;
|
|
|
1067 |
}
|
|
|
1068 |
// Should only be one of each.
|
|
|
1069 |
$skydrivetype = repository::get_type_by_typename('skydrive');
|
|
|
1070 |
|
|
|
1071 |
$skydriveinstances = repository::get_instances(['type' => 'skydrive']);
|
|
|
1072 |
$skydriveinstance = reset($skydriveinstances);
|
|
|
1073 |
$onedriveinstances = repository::get_instances(['type' => 'onedrive']);
|
|
|
1074 |
$onedriveinstance = reset($onedriveinstances);
|
|
|
1075 |
|
|
|
1076 |
// Update all file references.
|
|
|
1077 |
$DB->set_field('files_reference', 'repositoryid', $onedriveinstance->id, ['repositoryid' => $skydriveinstance->id]);
|
|
|
1078 |
|
|
|
1079 |
// Delete and disable the skydrive repo.
|
|
|
1080 |
$skydrivetype->delete();
|
|
|
1081 |
core_plugin_manager::reset_caches();
|
|
|
1082 |
|
|
|
1083 |
$sql = "SELECT count('x')
|
|
|
1084 |
FROM {repository_instances} i, {repository} r
|
|
|
1085 |
WHERE r.type=:plugin AND r.id=i.typeid";
|
|
|
1086 |
$params = array('plugin' => 'skydrive');
|
|
|
1087 |
return $DB->count_records_sql($sql, $params) == 0;
|
|
|
1088 |
}
|
|
|
1089 |
|
|
|
1090 |
/**
|
|
|
1091 |
* Edit/Create Admin Settings Moodle form.
|
|
|
1092 |
*
|
|
|
1093 |
* @param moodleform $mform Moodle form (passed by reference).
|
|
|
1094 |
* @param string $classname repository class name.
|
|
|
1095 |
*/
|
|
|
1096 |
public static function type_config_form($mform, $classname = 'repository') {
|
|
|
1097 |
global $OUTPUT;
|
|
|
1098 |
|
|
|
1099 |
$url = new moodle_url('/admin/tool/oauth2/issuers.php');
|
|
|
1100 |
$url = $url->out();
|
|
|
1101 |
|
|
|
1102 |
$mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_onedrive', $url));
|
|
|
1103 |
|
|
|
1104 |
if (self::can_import_skydrive_files()) {
|
|
|
1105 |
debugging('can_import_skydrive_files() is deprecated. Please migrate your files from repository_skydrive to ' .
|
|
|
1106 |
'repository_onedrive before it will be completely removed.', DEBUG_DEVELOPER);
|
|
|
1107 |
|
|
|
1108 |
$notice = get_string('skydrivefilesexist', 'repository_onedrive');
|
|
|
1109 |
$url = new moodle_url('/repository/onedrive/importskydrive.php');
|
|
|
1110 |
$attrs = ['class' => 'btn btn-primary'];
|
|
|
1111 |
$button = $OUTPUT->action_link($url, get_string('importskydrivefiles', 'repository_onedrive'), null, $attrs);
|
|
|
1112 |
$mform->addElement('static', null, '', $OUTPUT->notification($notice) . $button);
|
|
|
1113 |
}
|
|
|
1114 |
|
|
|
1115 |
parent::type_config_form($mform);
|
|
|
1116 |
$options = [];
|
|
|
1117 |
$issuers = \core\oauth2\api::get_all_issuers();
|
|
|
1118 |
|
|
|
1119 |
foreach ($issuers as $issuer) {
|
|
|
1120 |
$options[$issuer->get('id')] = s($issuer->get('name'));
|
|
|
1121 |
}
|
|
|
1122 |
|
|
|
1123 |
$strrequired = get_string('required');
|
|
|
1124 |
|
|
|
1125 |
$mform->addElement('select', 'issuerid', get_string('issuer', 'repository_onedrive'), $options);
|
|
|
1126 |
$mform->addHelpButton('issuerid', 'issuer', 'repository_onedrive');
|
|
|
1127 |
$mform->addRule('issuerid', $strrequired, 'required', null, 'client');
|
|
|
1128 |
|
|
|
1129 |
$mform->addElement('static', null, '', get_string('fileoptions', 'repository_onedrive'));
|
|
|
1130 |
$choices = [
|
|
|
1131 |
'internal' => get_string('internal', 'repository_onedrive'),
|
|
|
1132 |
'external' => get_string('external', 'repository_onedrive'),
|
|
|
1133 |
'both' => get_string('both', 'repository_onedrive')
|
|
|
1134 |
];
|
|
|
1135 |
$mform->addElement('select', 'supportedreturntypes', get_string('supportedreturntypes', 'repository_onedrive'), $choices);
|
|
|
1136 |
|
|
|
1137 |
$choices = [
|
|
|
1138 |
FILE_INTERNAL => get_string('internal', 'repository_onedrive'),
|
|
|
1139 |
FILE_CONTROLLED_LINK => get_string('external', 'repository_onedrive'),
|
|
|
1140 |
];
|
|
|
1141 |
$mform->addElement('select', 'defaultreturntype', get_string('defaultreturntype', 'repository_onedrive'), $choices);
|
|
|
1142 |
|
|
|
1143 |
}
|
|
|
1144 |
}
|
|
|
1145 |
|
|
|
1146 |
/**
|
|
|
1147 |
* Callback to get the required scopes for system account.
|
|
|
1148 |
*
|
|
|
1149 |
* @param \core\oauth2\issuer $issuer
|
|
|
1150 |
* @return string
|
|
|
1151 |
*/
|
|
|
1152 |
function repository_onedrive_oauth2_system_scopes(\core\oauth2\issuer $issuer) {
|
|
|
1153 |
if ($issuer->get('id') == get_config('onedrive', 'issuerid')) {
|
|
|
1154 |
return repository_onedrive::SCOPES;
|
|
|
1155 |
}
|
|
|
1156 |
return '';
|
|
|
1157 |
}
|