MDL-52051 repository_dropbox: Add the V2 API
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Dropbox Authentication exception.
|
||||
*
|
||||
* @since Moodle 3.2
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace repository_dropbox;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Dropbox Authentication exception.
|
||||
*
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class authentication_exception extends dropbox_exception {
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Dropbox V2 API.
|
||||
*
|
||||
* @since Moodle 3.2
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace repository_dropbox;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/oauthlib.php');
|
||||
|
||||
/**
|
||||
* Dropbox V2 API.
|
||||
*
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class dropbox extends \oauth2_client {
|
||||
|
||||
/**
|
||||
* Create the DropBox API Client.
|
||||
*
|
||||
* @param string $key The API key
|
||||
* @param string $secret The API secret
|
||||
* @param string $callback The callback URL
|
||||
*/
|
||||
public function __construct($key, $secret, $callback) {
|
||||
parent::__construct($key, $secret, $callback, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auth url for OAuth 2.0 request.
|
||||
*
|
||||
* @return string the auth url
|
||||
*/
|
||||
protected function auth_url() {
|
||||
return 'https://www.dropbox.com/oauth2/authorize';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token url for OAuth 2.0 request.
|
||||
*
|
||||
* @return string the auth url
|
||||
*/
|
||||
protected function token_url() {
|
||||
return 'https://api.dropboxapi.com/oauth2/token';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the constructed API endpoint URL.
|
||||
*
|
||||
* @param string $endpoint The endpoint to be contacted
|
||||
* @return moodle_url The constructed API URL
|
||||
*/
|
||||
protected function get_api_endpoint($endpoint) {
|
||||
return new \moodle_url('https://api.dropboxapi.com/2/' . $endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the constructed content endpoint URL.
|
||||
*
|
||||
* @param string $endpoint The endpoint to be contacted
|
||||
* @return moodle_url The constructed content URL
|
||||
*/
|
||||
protected function get_content_endpoint($endpoint) {
|
||||
return new \moodle_url('https://api-content.dropbox.com/2/' . $endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an API call against the specified endpoint with supplied data.
|
||||
*
|
||||
* @param string $endpoint The endpoint to be contacted
|
||||
* @param array $data Any data to pass to the endpoint
|
||||
* @return object Content decoded from the endpoint
|
||||
*/
|
||||
protected function fetch_dropbox_data($endpoint, $data = []) {
|
||||
$url = $this->get_api_endpoint($endpoint);
|
||||
$this->cleanopt();
|
||||
$this->resetHeader();
|
||||
|
||||
if ($data === null) {
|
||||
// Some API endpoints explicitly expect a data submission of 'null'.
|
||||
$options['CURLOPT_POSTFIELDS'] = 'null';
|
||||
} else {
|
||||
$options['CURLOPT_POSTFIELDS'] = json_encode($data);
|
||||
}
|
||||
$options['CURLOPT_POST'] = 1;
|
||||
$this->setHeader('Content-Type: application/json');
|
||||
|
||||
$response = $this->request($url, $options);
|
||||
$result = json_decode($response);
|
||||
|
||||
$this->check_and_handle_api_errors($result);
|
||||
|
||||
if ($this->has_additional_results($result)) {
|
||||
// Any API endpoint returning 'has_more' will provide a cursor, and also have a matching endpoint suffixed
|
||||
// with /continue which takes that cursor.
|
||||
if (preg_match('_/continue$_', $endpoint) === 0) {
|
||||
// Only add /continue if it is not already present.
|
||||
$endpoint .= '/continue';
|
||||
}
|
||||
|
||||
// Fetch the next page of results.
|
||||
$additionaldata = $this->fetch_dropbox_data($endpoint, [
|
||||
'cursor' => $result->cursor,
|
||||
]);
|
||||
|
||||
// Merge the list of entries.
|
||||
$result->entries = array_merge($result->entries, $additionaldata->entries);
|
||||
}
|
||||
|
||||
if (isset($result->has_more)) {
|
||||
// Unset the cursor and has_more flags.
|
||||
unset($result->cursor);
|
||||
unset($result->has_more);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the supplied result is paginated and not the final page.
|
||||
*
|
||||
* @param object $result The result of an operation
|
||||
* @return boolean
|
||||
*/
|
||||
public function has_additional_results($result) {
|
||||
return !empty($result->has_more) && !empty($result->cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch content from the specified endpoint with the supplied data.
|
||||
*
|
||||
* @param string $endpoint The endpoint to be contacted
|
||||
* @param array $data Any data to pass to the endpoint
|
||||
* @return string The returned data
|
||||
*/
|
||||
protected function fetch_dropbox_content($endpoint, $data = []) {
|
||||
$url = $this->get_content_endpoint($endpoint);
|
||||
$this->cleanopt();
|
||||
$this->resetHeader();
|
||||
|
||||
$options['CURLOPT_POST'] = 1;
|
||||
$this->setHeader('Content-Type: ');
|
||||
$this->setHeader('Dropbox-API-Arg: ' . json_encode($data));
|
||||
|
||||
$response = $this->request($url, $options);
|
||||
|
||||
$this->check_and_handle_api_errors($response);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for an attempt to handle API errors.
|
||||
*
|
||||
* This function attempts to deal with errors as per
|
||||
* https://www.dropbox.com/developers/documentation/http/documentation#error-handling.
|
||||
*
|
||||
* @param string $data The returned content.
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
protected function check_and_handle_api_errors($data) {
|
||||
if ($this->info['http_code'] == 200) {
|
||||
// Dropbox only returns errors on non-200 response codes.
|
||||
return;
|
||||
}
|
||||
|
||||
switch($this->info['http_code']) {
|
||||
case 400:
|
||||
// Bad input parameter. Error message should indicate which one and why.
|
||||
throw new \coding_exception('Invalid input parameter passed to DropBox API.');
|
||||
break;
|
||||
case 401:
|
||||
// Bad or expired token. This can happen if the access token is expired or if the access token has been
|
||||
// revoked by Dropbox or the user. To fix this, you should re-authenticate the user.
|
||||
throw new authentication_exception('Authentication token expired');
|
||||
break;
|
||||
case 409:
|
||||
// Endpoint-specific error. Look to the response body for the specifics of the error.
|
||||
throw new \coding_exception('Endpoint specific error: ' . $data);
|
||||
break;
|
||||
case 429:
|
||||
// Your app is making too many requests for the given user or team and is being rate limited. Your app
|
||||
// should wait for the number of seconds specified in the "Retry-After" response header before trying
|
||||
// again.
|
||||
throw new rate_limit_exception();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->info['http_code'] >= 500 && $this->info['http_code'] < 600) {
|
||||
throw new \invalid_response_exception($this->info['http_code'] . ": " . $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file listing from dropbox.
|
||||
*
|
||||
* @param string $path The path to query
|
||||
* @return object The returned directory listing, or null on failure
|
||||
*/
|
||||
public function get_listing($path = '') {
|
||||
if ($path === '/') {
|
||||
$path = '';
|
||||
}
|
||||
|
||||
$data = $this->fetch_dropbox_data('files/list_folder', [
|
||||
'path' => $path,
|
||||
]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file search results from dropbox.
|
||||
*
|
||||
* @param string $query The search query
|
||||
* @return object The returned directory listing, or null on failure
|
||||
*/
|
||||
public function search($query = '') {
|
||||
$data = $this->fetch_dropbox_data('files/search', [
|
||||
'path' => '',
|
||||
'query' => $query,
|
||||
]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the entry is expected to have a thumbnail.
|
||||
* See docs at https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail.
|
||||
*
|
||||
* @param object $entry The file entry received from the DropBox API
|
||||
* @return boolean Whether dropbox has a thumbnail available
|
||||
*/
|
||||
public function supports_thumbnail($entry) {
|
||||
if ($entry->{".tag"} !== "file") {
|
||||
// Not a file. No thumbnail available.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Thumbnails are available for files under 20MB with file extensions jpg, jpeg, png, tiff, tif, gif, and bmp.
|
||||
if ($entry->size > 20 * 1024 * 1024) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$supportedtypes = [
|
||||
'jpg' => true,
|
||||
'jpeg' => true,
|
||||
'png' => true,
|
||||
'tiff' => true,
|
||||
'tif' => true,
|
||||
'gif' => true,
|
||||
'bmp' => true,
|
||||
];
|
||||
|
||||
$extension = substr($entry->path_lower, strrpos($entry->path_lower, '.') + 1);
|
||||
return isset($supportedtypes[$extension]) && $supportedtypes[$extension];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail for the content, as supplied by dropbox.
|
||||
*
|
||||
* @param string $path The path to fetch a thumbnail for
|
||||
* @return string Thumbnail image content
|
||||
*/
|
||||
public function get_thumbnail($path) {
|
||||
$content = $this->fetch_dropbox_content('files/get_thumbnail', [
|
||||
'path' => $path,
|
||||
]);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a valid public share link for the specified file.
|
||||
*
|
||||
* @param string $id The file path or file id of the file to fetch information for.
|
||||
* @return object An object containing the id, path, size, and URL of the entry
|
||||
*/
|
||||
public function get_file_share_info($id) {
|
||||
// Attempt to fetch any existing shared link first.
|
||||
$data = $this->fetch_dropbox_data('sharing/list_shared_links', [
|
||||
'path' => $id,
|
||||
]);
|
||||
|
||||
if (isset($data->links)) {
|
||||
$link = reset($data->links);
|
||||
if (isset($link->{".tag"}) && $link->{".tag"} === "file") {
|
||||
return $this->normalize_file_share_info($link);
|
||||
}
|
||||
}
|
||||
|
||||
// No existing link available.
|
||||
// Create a new one.
|
||||
$link = $this->fetch_dropbox_data('sharing/create_shared_link_with_settings', [
|
||||
'path' => $id,
|
||||
'settings' => [
|
||||
'requested_visibility' => 'public',
|
||||
],
|
||||
]);
|
||||
|
||||
if (isset($link->{".tag"}) && $link->{".tag"} === "file") {
|
||||
return $this->normalize_file_share_info($link);
|
||||
}
|
||||
|
||||
// Some kind of error we don't know how to handle at this stage.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the file share info.
|
||||
*
|
||||
* @param object $entry Information retrieved from share endpoints
|
||||
* @return object Normalized entry information to store as repository information
|
||||
*/
|
||||
protected function normalize_file_share_info($entry) {
|
||||
return (object) [
|
||||
'id' => $entry->id,
|
||||
'path' => $entry->path_lower,
|
||||
'url' => $entry->url,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the callback.
|
||||
*/
|
||||
public function callback() {
|
||||
$this->log_out();
|
||||
$this->is_logged_in();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the current access token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function logout() {
|
||||
try {
|
||||
$this->fetch_dropbox_data('auth/token/revoke', null);
|
||||
} catch(authentication_exception $e) {
|
||||
// An authentication_exception may be expected if the token has
|
||||
// already expired.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* General Dropbox Exception.
|
||||
*
|
||||
* @since Moodle 3.2
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace repository_dropbox;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* General Dropbox Exception.
|
||||
*
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class dropbox_exception extends \moodle_exception {
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Upstream issue exception.
|
||||
*
|
||||
* @since Moodle 3.2
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace repository_dropbox;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Upstream issue exception.
|
||||
*
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider_exception extends dropbox_exception {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Dropbox Rate Limit Encountered.
|
||||
*
|
||||
* @since Moodle 3.2
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace repository_dropbox;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Dropbox Rate Limit Encountered.
|
||||
*
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class rate_limit_exception extends dropbox_exception {
|
||||
/**
|
||||
* Constructor for rate_limit_exception.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct('Rate limit hit');
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,6 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @return bool result
|
||||
*/
|
||||
function xmldb_repository_dropbox_upgrade($oldversion) {
|
||||
global $CFG;
|
||||
|
||||
// Moodle v2.8.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
@@ -35,5 +33,9 @@ function xmldb_repository_dropbox_upgrade($oldversion) {
|
||||
// Moodle v3.1.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2016052301) {
|
||||
set_config('legacyapi', 1, 'dropbox');
|
||||
upgrade_plugin_savepoint(true, 2016052301, 'repository', 'dropbox');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,3 +34,9 @@ $string['cachelimit'] = 'Cache limit';
|
||||
$string['cachelimit_info'] = 'Enter the maximum size of files (in bytes) to be cached on server for Dropbox aliases/shortcuts. Cached files will be served when the source is no longer available. Empty value or zero mean caching of all files regardless of size.';
|
||||
$string['dropbox:view'] = 'View a Dropbox folder';
|
||||
$string['logoutdesc'] = '(Logout when you finish using Dropbox)';
|
||||
|
||||
$string['usev2api'] = 'Use the OAuth 2 API (recommended)';
|
||||
$string['uselegacyapi'] = 'Use the deprecated v1 API';
|
||||
$string['legacyapihelpsupported'] = 'Dropbox introduced the OAuth 2 API in June 2015. You can configure a new application at <a href="https://www.dropbox.com/developers/apps/" target="_blank">https://www.dropbox.com/developers/apps/</a> and specify the OAuth 2 Redirect URI shown below.';
|
||||
$string['oauth2redirecturi'] = 'OAuth 2 Redirect URI';
|
||||
$string['oauth2title'] = 'OAuth 2 API';
|
||||
|
||||
+585
-20
@@ -33,6 +33,11 @@ require_once($CFG->dirroot . '/repository/lib.php');
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class repository_dropbox extends repository {
|
||||
/**
|
||||
* @var dropbox The instance of dropbox client.
|
||||
*/
|
||||
protected $dropbox;
|
||||
|
||||
/**
|
||||
* @var int The maximum file size to cache in the moodle filepool.
|
||||
*/
|
||||
@@ -49,9 +54,41 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = []) {
|
||||
$this->legacy = new repository_dropbox_legacy($repositoryid, $context, $options);
|
||||
if (self::use_legacy_api()) {
|
||||
$this->legacy = new repository_dropbox_legacy($repositoryid, $context, $options);
|
||||
|
||||
parent::__construct($repositoryid, $context, $options);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$options['page'] = optional_param('p', 1, PARAM_INT);
|
||||
parent::__construct($repositoryid, $context, $options);
|
||||
|
||||
$returnurl = new moodle_url('/repository/repository_callback.php', [
|
||||
'callback' => 'yes',
|
||||
'repo_id' => $repositoryid,
|
||||
'sesskey' => sesskey(),
|
||||
]);
|
||||
|
||||
// Create the dropbox API instance.
|
||||
$key = get_config('dropbox', 'dropbox_key');
|
||||
$secret = get_config('dropbox', 'dropbox_secret');
|
||||
$this->dropbox = new repository_dropbox\dropbox(
|
||||
$key,
|
||||
$secret,
|
||||
$returnurl
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to use the legacy (v1) API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function use_legacy_api() {
|
||||
$uselegacyapi = get_config('dropbox', 'legacyapi');
|
||||
return !empty($uselegacyapi);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +97,42 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
|
||||
return $this->legacy->send_file($storedfile, $lifetime, $filter, $forcedownload, $options);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->send_file($storedfile, $lifetime, $filter, $forcedownload, $options);
|
||||
}
|
||||
|
||||
$reference = $this->unpack_reference($storedfile->get_reference());
|
||||
|
||||
$maxcachesize = $this->max_cache_bytes();
|
||||
if (empty($maxcachesize)) {
|
||||
// Always cache the file, regardless of size.
|
||||
$cachefile = true;
|
||||
} else {
|
||||
// Size available. Only cache if it is under maxcachesize.
|
||||
$cachefile = $storedfile->get_filesize() < $maxcachesize;
|
||||
}
|
||||
|
||||
if (!$cachefile) {
|
||||
\core\session\manager::write_close();
|
||||
header('Location: ' . $this->get_file_download_link($reference->url));
|
||||
die;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->import_external_file_contents($storedfile, $this->max_cache_bytes());
|
||||
if (!is_array($options)) {
|
||||
$options = array();
|
||||
}
|
||||
$options['sendcachedexternalfile'] = true;
|
||||
\core\session\manager::write_close();
|
||||
send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options);
|
||||
} catch (moodle_exception $e) {
|
||||
// Redirect to Dropbox, it will show the error.
|
||||
// Note: We redirect to Dropbox shared link, not to the download link here!
|
||||
\core\session\manager::write_close();
|
||||
header('Location: ' . $reference->url);
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +142,29 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function get_reference_details($reference, $filestatus = 0) {
|
||||
return $this->legacy->get_reference_details($reference, $filestatus);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->get_reference_details($reference, $filestatus);
|
||||
}
|
||||
|
||||
global $USER;
|
||||
$ref = unserialize($reference);
|
||||
$detailsprefix = $this->get_name();
|
||||
if (isset($ref->userid) && $ref->userid != $USER->id && isset($ref->username)) {
|
||||
$detailsprefix .= ' ('.$ref->username.')';
|
||||
}
|
||||
$details = $detailsprefix;
|
||||
if (isset($ref->path)) {
|
||||
$details .= ': '. $ref->path;
|
||||
}
|
||||
if (isset($ref->path) && !$filestatus) {
|
||||
// Indicate this is from dropbox with path.
|
||||
return $details;
|
||||
} else {
|
||||
if (isset($ref->url)) {
|
||||
$details = $detailsprefix. ': '. $ref->url;
|
||||
}
|
||||
return get_string('lostsource', 'repository', $details);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +176,15 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function cache_file_by_reference($reference, $storedfile) {
|
||||
return $this->legacy->cache_file_by_reference($reference, $storedfile);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->cache_file_by_reference($reference, $storedfile);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->import_external_file_contents($storedfile, $this->max_cache_bytes());
|
||||
} catch (Exception $e) {
|
||||
// Cache failure should not cause a fatal error. This is only a nice-to-have feature.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +202,12 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function get_file_source_info($source) {
|
||||
return $this->legacy->get_file_source_info($source);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->get_file_source_info($source);
|
||||
}
|
||||
|
||||
global $USER;
|
||||
return 'Dropbox ('.fullname($USER).'): ' . $source;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +216,25 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function get_file_reference($source) {
|
||||
return $this->legacy->get_file_reference($source);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->get_file_reference($source);
|
||||
}
|
||||
|
||||
global $USER;
|
||||
$reference = new stdClass;
|
||||
$reference->userid = $USER->id;
|
||||
$reference->username = fullname($USER);
|
||||
$reference->path = $source;
|
||||
|
||||
// Determine whether we are downloading the file, or should use a file reference.
|
||||
$usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
|
||||
if ($usefilereference) {
|
||||
if ($data = $this->dropbox->get_file_share_info($source)) {
|
||||
$reference = (object) array_merge((array) $data, (array) $reference);
|
||||
}
|
||||
}
|
||||
|
||||
return serialize($reference);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +243,13 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function get_link($reference) {
|
||||
return $this->legacy->get_link($reference);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->get_link($reference);
|
||||
}
|
||||
|
||||
$unpacked = $this->unpack_reference($reference);
|
||||
|
||||
return $this->get_file_download_link($unpacked->url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +258,19 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function get_file($reference, $saveas = '') {
|
||||
return $this->legacy->get_file($reference, $saveas);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->get_file($reference, $saveas);
|
||||
}
|
||||
|
||||
$unpacked = $this->unpack_reference($reference);
|
||||
|
||||
// This is a shared link, and hopefully it is still active.
|
||||
$downloadlink = $this->get_file_download_link($unpacked->url);
|
||||
|
||||
$saveas = $this->prepare_file($saveas);
|
||||
file_put_contents($saveas, fopen($downloadlink, 'r'));
|
||||
|
||||
return ['path' => $saveas];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,7 +279,11 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function supported_filetypes() {
|
||||
return $this->legacy->supported_filetypes();
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->supported_filetypes();
|
||||
}
|
||||
|
||||
return '*';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +292,11 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function supported_returntypes() {
|
||||
return $this->legacy->supported_returntypes();
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->supported_returntypes();
|
||||
}
|
||||
|
||||
return FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +305,48 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function get_listing($path = '', $page = '1') {
|
||||
return $this->legacy->get_listing($path, $page);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->get_listing($path, $page);
|
||||
}
|
||||
|
||||
if (empty($path) || $path == '/') {
|
||||
$path = '';
|
||||
} else {
|
||||
$path = file_correct_filepath($path);
|
||||
}
|
||||
|
||||
$list = [
|
||||
'list' => [],
|
||||
'manage' => 'https://www.dropbox.com/home',
|
||||
'logouturl' => 'https://www.dropbox.com/logout',
|
||||
'message' => get_string('logoutdesc', 'repository_dropbox'),
|
||||
'dynload' => true,
|
||||
'path' => $this->process_breadcrumbs($path),
|
||||
];
|
||||
|
||||
// Note - we deliberately do not catch the coding exceptions here.
|
||||
try {
|
||||
$result = $this->dropbox->get_listing($path);
|
||||
} catch (\repository_dropbox\authentication_exception $e) {
|
||||
// The token has expired.
|
||||
return $this->print_login();
|
||||
} catch (\repository_dropbox\dropbox_exception $e) {
|
||||
// There was some other form of non-coding failure.
|
||||
// This could be a rate limit, or it could be a server-side error.
|
||||
// Just return early instead.
|
||||
return $list;
|
||||
}
|
||||
|
||||
if (!is_object($result) || empty($result)) {
|
||||
return $list;
|
||||
}
|
||||
|
||||
if (empty($result->entries) or !is_array($result->entries)) {
|
||||
return $list;
|
||||
}
|
||||
|
||||
$list['list'] = $this->process_entries($result->entries);
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +357,41 @@ class repository_dropbox extends repository {
|
||||
* @return array
|
||||
*/
|
||||
public function search($query, $page = 0) {
|
||||
return parent::search($query, $page);
|
||||
if (self::use_legacy_api()) {
|
||||
return parent::search($query, $page);
|
||||
}
|
||||
|
||||
$list = [
|
||||
'list' => [],
|
||||
'manage' => 'https://www.dropbox.com/home',
|
||||
'logouturl' => 'https://www.dropbox.com/logout',
|
||||
'message' => get_string('logoutdesc', 'repository_dropbox'),
|
||||
'dynload' => true,
|
||||
];
|
||||
|
||||
// Note - we deliberately do not catch the coding exceptions here.
|
||||
try {
|
||||
$result = $this->dropbox->search($query);
|
||||
} catch (\repository_dropbox\authentication_exception $e) {
|
||||
// The token has expired.
|
||||
return $this->print_login();
|
||||
} catch (\repository_dropbox\dropbox_exception $e) {
|
||||
// There was some other form of non-coding failure.
|
||||
// This could be a rate limit, or it could be a server-side error.
|
||||
// Just return early instead.
|
||||
return $list;
|
||||
}
|
||||
|
||||
if (!is_object($result) || empty($result)) {
|
||||
return $list;
|
||||
}
|
||||
|
||||
if (empty($result->matches) or !is_array($result->matches)) {
|
||||
return $list;
|
||||
}
|
||||
|
||||
$list['list'] = $this->process_entries($result->matches);
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +400,16 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function send_thumbnail($source) {
|
||||
return $this->legacy->send_thumbnail($source);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->send_thumbnail($source);
|
||||
}
|
||||
|
||||
$content = $this->dropbox->get_thumbnail($source);
|
||||
|
||||
// Set 30 days lifetime for the image.
|
||||
// If the image is changed in dropbox it will have different revision number and URL will be different.
|
||||
// It is completely safe to cache the thumbnail in the browser for a long time.
|
||||
send_file($content, basename($source), 30 * DAYSECS, 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,15 +419,103 @@ class repository_dropbox extends repository {
|
||||
* @return string New serialized reference
|
||||
*/
|
||||
protected function fix_old_style_reference($packed) {
|
||||
return $this->legacy->fix_old_style_reference($packed);
|
||||
if (self::use_legacy_api()) {
|
||||
throw new \coding_exception(__FUNCTION__ . ' is a v2 API function only');
|
||||
}
|
||||
|
||||
$ref = unserialize($packed);
|
||||
$ref = $this->dropbox->get_file_share_info($ref->path);
|
||||
if (!$ref || empty($ref->url)) {
|
||||
// Some error occurred, do not fix reference for now.
|
||||
return $packed;
|
||||
}
|
||||
|
||||
$newreference = serialize($ref);
|
||||
if ($newreference !== $packed) {
|
||||
// We need to update references in the database.
|
||||
global $DB;
|
||||
$params = array(
|
||||
'newreference' => $newreference,
|
||||
'newhash' => sha1($newreference),
|
||||
'reference' => $packed,
|
||||
'hash' => sha1($packed),
|
||||
'repoid' => $this->id,
|
||||
);
|
||||
$refid = $DB->get_field_sql('SELECT id FROM {files_reference}
|
||||
WHERE reference = :reference AND referencehash = :hash
|
||||
AND repositoryid = :repoid', $params);
|
||||
if (!$refid) {
|
||||
return $newreference;
|
||||
}
|
||||
|
||||
$existingrefid = $DB->get_field_sql('SELECT id FROM {files_reference}
|
||||
WHERE reference = :newreference AND referencehash = :newhash
|
||||
AND repositoryid = :repoid', $params);
|
||||
if ($existingrefid) {
|
||||
// The same reference already exists, we unlink all files from it,
|
||||
// link them to the current reference and remove the old one.
|
||||
$DB->execute('UPDATE {files} SET referencefileid = :refid
|
||||
WHERE referencefileid = :existingrefid',
|
||||
array('refid' => $refid, 'existingrefid' => $existingrefid));
|
||||
$DB->delete_records('files_reference', array('id' => $existingrefid));
|
||||
}
|
||||
|
||||
// Update the reference.
|
||||
$params['refid'] = $refid;
|
||||
$DB->execute('UPDATE {files_reference}
|
||||
SET reference = :newreference, referencehash = :newhash
|
||||
WHERE id = :refid', $params);
|
||||
}
|
||||
return $newreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack the supplied serialized reference, fixing it if required.
|
||||
*
|
||||
* @param string $packed The packed reference
|
||||
* @return object The unpacked reference
|
||||
*/
|
||||
protected function unpack_reference($packed) {
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->fix_old_style_reference($packed);
|
||||
}
|
||||
|
||||
$reference = unserialize($packed);
|
||||
if (empty($reference->url)) {
|
||||
// The reference is missing some information. Attempt to update it.
|
||||
return unserialize($this->fix_old_style_reference($packed));
|
||||
}
|
||||
|
||||
return $reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a URL received from dropbox API function 'shares' into URL that
|
||||
* can be used to download/access file directly
|
||||
*
|
||||
* @param string $sharedurl
|
||||
* @return string
|
||||
*/
|
||||
protected function get_file_download_link($sharedurl) {
|
||||
$url = new \moodle_url($sharedurl);
|
||||
$url->param('dl', 1);
|
||||
|
||||
return $url->out(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout from dropbox.
|
||||
*
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function logout() {
|
||||
return $this->legacy->logout();
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->logout();
|
||||
}
|
||||
|
||||
$this->dropbox->logout();
|
||||
|
||||
return $this->print_login();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +524,11 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function check_login() {
|
||||
return $this->legacy->check_login();
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->check_login();
|
||||
}
|
||||
|
||||
return $this->dropbox->is_logged_in();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +537,21 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function print_login() {
|
||||
return $this->legacy->print_login();
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->print_login();
|
||||
}
|
||||
|
||||
$url = $this->dropbox->get_login_url();
|
||||
if ($this->options['ajax']) {
|
||||
$ret = array();
|
||||
$btn = new \stdClass();
|
||||
$btn->type = 'popup';
|
||||
$btn->url = $url->out(false);
|
||||
$ret['login'] = array($btn);
|
||||
return $ret;
|
||||
} else {
|
||||
echo html_writer::link($url, get_string('login', 'repository'), array('target' => '_blank'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,7 +560,11 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function callback() {
|
||||
return $this->legacy->callback();
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->callback();
|
||||
}
|
||||
|
||||
$this->dropbox->callback();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +577,26 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function cron() {
|
||||
return $this->legacy->cron();
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->cron();
|
||||
}
|
||||
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_external_files($this->id);
|
||||
$fetchedreferences = [];
|
||||
foreach ($files as $file) {
|
||||
if (isset($fetchedreferences[$file->get_referencefileid()])) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// This call will cache all files that are smaller than max_cache_bytes()
|
||||
// and synchronise file size of all others.
|
||||
$this->import_external_file_contents($file, $this->max_cache_bytes());
|
||||
$fetchedreferences[$file->get_referencefileid()] = true;
|
||||
} catch (moodle_exception $e) {
|
||||
// If an exception is thrown, just continue. This is only a pre-fetch to help speed up general use.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,6 +625,19 @@ class repository_dropbox extends repository {
|
||||
$mform->setType('dropbox_secret', PARAM_RAW_TRIMMED);
|
||||
$mform->addElement('static', null, '', get_string('instruction', 'repository_dropbox'));
|
||||
|
||||
$mform->addElement('static', null,
|
||||
get_string('oauth2title', 'repository_dropbox'),
|
||||
get_string('legacyapihelpsupported', 'repository_dropbox'));
|
||||
$mform->addElement('static', null,
|
||||
get_string('oauth2redirecturi', 'repository_dropbox'),
|
||||
self::get_oauth2callbackurl()->out()
|
||||
);
|
||||
|
||||
// The Legacy API is still available so give the user the option of which version to use.
|
||||
$mform->addElement('radio', 'legacyapi', get_string('usev2api', 'repository_dropbox'), null, false);
|
||||
$mform->addElement('radio', 'legacyapi', get_string('uselegacyapi', 'repository_dropbox'), null, true);
|
||||
$mform->setDefault('legacyapi', false);
|
||||
|
||||
$mform->addElement('text', 'dropbox_cachelimit', get_string('cachelimit', 'repository_dropbox'), array('size' => '40'));
|
||||
$mform->addRule('dropbox_cachelimit', null, 'numeric', null, 'client');
|
||||
$mform->setType('dropbox_cachelimit', PARAM_INT);
|
||||
@@ -289,6 +666,11 @@ class repository_dropbox extends repository {
|
||||
unset($options['dropbox_cachelimit']);
|
||||
}
|
||||
|
||||
if (!empty($options['legacyapi'])) {
|
||||
set_config('legacyapi', $this->legacyapi, 'dropbox');
|
||||
unset($options['legacyapi']);
|
||||
}
|
||||
|
||||
return parent::set_option($options);
|
||||
}
|
||||
|
||||
@@ -304,16 +686,30 @@ class repository_dropbox extends repository {
|
||||
return trim(get_config('dropbox', 'dropbox_secret'));
|
||||
} else if ($config === 'dropbox_cachelimit') {
|
||||
return $this->max_cache_bytes();
|
||||
} elseif ($config === 'legacyapi') {
|
||||
return trim(get_config('dropbox', 'legacyapi'));
|
||||
} else {
|
||||
$options = parent::get_option();
|
||||
$options['dropbox_key'] = trim(get_config('dropbox', 'dropbox_key'));
|
||||
$options['dropbox_secret'] = trim(get_config('dropbox', 'dropbox_secret'));
|
||||
$options['dropbox_cachelimit'] = $this->max_cache_bytes();
|
||||
$options['legacyapi'] = trim(get_config('dropbox', 'legacyapi'));
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OAuth 2 Redirect URI.
|
||||
*
|
||||
* @return moodle_url
|
||||
*/
|
||||
public static function get_oauth2callbackurl() {
|
||||
global $CFG;
|
||||
|
||||
return new moodle_url($CFG->httpswwwroot . '/admin/oauth2callback.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Option names of dropbox plugin.
|
||||
*
|
||||
@@ -321,6 +717,7 @@ class repository_dropbox extends repository {
|
||||
*/
|
||||
public static function get_type_option_names() {
|
||||
return [
|
||||
'legacyapi',
|
||||
'dropbox_key',
|
||||
'dropbox_secret',
|
||||
'pluginname',
|
||||
@@ -357,7 +754,175 @@ class repository_dropbox extends repository {
|
||||
* @inheritDocs
|
||||
*/
|
||||
public function sync_reference(stored_file $file) {
|
||||
return $this->legacy->sync_reference($file);
|
||||
if (self::use_legacy_api()) {
|
||||
return $this->legacy->sync_reference($file);
|
||||
}
|
||||
|
||||
global $CFG;
|
||||
|
||||
if ($file->get_referencelastsync() + DAYSECS > time()) {
|
||||
// Only synchronise once per day.
|
||||
return false;
|
||||
}
|
||||
|
||||
$reference = $this->unpack_reference($file->get_reference());
|
||||
if (!isset($reference->url)) {
|
||||
// The URL to sync with is missing.
|
||||
return false;
|
||||
}
|
||||
|
||||
$c = new curl;
|
||||
$url = $this->get_file_download_link($reference->url);
|
||||
if (file_extension_in_typegroup($reference->path, 'web_image')) {
|
||||
$saveas = $this->prepare_file('');
|
||||
try {
|
||||
$result = $c->download_one($url, [], [
|
||||
'filepath' => $saveas,
|
||||
'timeout' => $CFG->repositorysyncimagetimeout,
|
||||
'followlocation' => true,
|
||||
]);
|
||||
$info = $c->get_info();
|
||||
if ($result === true && isset($info['http_code']) && $info['http_code'] == 200) {
|
||||
$fs = get_file_storage();
|
||||
list($contenthash, $filesize, ) = $fs->add_file_to_pool($saveas);
|
||||
$file->set_synchronized($contenthash, $filesize);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// IF the download_one fails, we will attempt to download
|
||||
// again with get() anyway.
|
||||
}
|
||||
}
|
||||
|
||||
$c->get($url, null, array('timeout' => $CFG->repositorysyncimagetimeout, 'followlocation' => true, 'nobody' => true));
|
||||
$info = $c->get_info();
|
||||
if (isset($info['http_code']) && $info['http_code'] == 200 &&
|
||||
array_key_exists('download_content_length', $info) &&
|
||||
$info['download_content_length'] >= 0) {
|
||||
$filesize = (int)$info['download_content_length'];
|
||||
$file->set_synchronized(null, $filesize);
|
||||
return true;
|
||||
}
|
||||
$file->set_missingsource();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a standard entries list.
|
||||
*
|
||||
* @param array $entries The list of entries returned from the API
|
||||
* @return array The manipulated entries for display in the file picker
|
||||
*/
|
||||
protected function process_entries(array $entries) {
|
||||
if (self::use_legacy_api()) {
|
||||
throw new \coding_exception(__FUNCTION__ . ' is a v2 API function only');
|
||||
}
|
||||
|
||||
global $OUTPUT;
|
||||
|
||||
$dirslist = [];
|
||||
$fileslist = [];
|
||||
foreach ($entries as $entry) {
|
||||
$entrydata = $entry;
|
||||
if (isset($entrydata->metadata)) {
|
||||
// If this is metadata, fetch the metadata content.
|
||||
// We only use the consistent parts of the file, folder, and metadata.
|
||||
$entrydata = $entrydata->metadata;
|
||||
}
|
||||
if ($entrydata->{".tag"} === "folder") {
|
||||
$dirslist[] = [
|
||||
'title' => $entrydata->name,
|
||||
// Use the display path here rather than lower.
|
||||
// Dropbox is case insensitive but this leads to more accurate breadcrumbs.
|
||||
'path' => file_correct_filepath($entrydata->path_display),
|
||||
'thumbnail' => $OUTPUT->pix_url(file_folder_icon(64))->out(false),
|
||||
'thumbnail_height' => 64,
|
||||
'thumbnail_width' => 64,
|
||||
'children' => array(),
|
||||
];
|
||||
} else if ($entrydata->{".tag"} === "file") {
|
||||
$fileslist[] = [
|
||||
'title' => $entrydata->name,
|
||||
// Use the path_lower here to make life easier elsewhere.
|
||||
'source' => $entrydata->path_lower,
|
||||
'size' => $entrydata->size,
|
||||
'date' => strtotime($entrydata->client_modified),
|
||||
'thumbnail' => $OUTPUT->pix_url(file_extension_icon($entrydata->path_lower, 64))->out(false),
|
||||
'realthumbnail' => $this->get_thumbnail_url($entrydata),
|
||||
'thumbnail_height' => 64,
|
||||
'thumbnail_width' => 64,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$fileslist = array_filter($fileslist, array($this, 'filter'));
|
||||
|
||||
return array_merge($dirslist, array_values($fileslist));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the breadcrumbs for a listing.
|
||||
*
|
||||
* @param string $path The path to create breadcrumbs for
|
||||
* @return array
|
||||
*/
|
||||
protected function process_breadcrumbs($path) {
|
||||
if (self::use_legacy_api()) {
|
||||
throw new \coding_exception(__FUNCTION__ . ' is a v2 API function only');
|
||||
}
|
||||
|
||||
// Process breadcrumb trail.
|
||||
// Note: Dropbox is case insensitive.
|
||||
// Without performing an additional API call, it isn't possible to get the path_display.
|
||||
// As a result, the path here is the path_lower.
|
||||
$breadcrumbs = [
|
||||
[
|
||||
'path' => '/',
|
||||
'name' => get_string('dropbox', 'repository_dropbox'),
|
||||
],
|
||||
];
|
||||
|
||||
$path = rtrim($path, '/');
|
||||
$directories = explode('/', $path);
|
||||
$pathtodate = '';
|
||||
foreach ($directories as $directory) {
|
||||
if ($directory === '') {
|
||||
continue;
|
||||
}
|
||||
$pathtodate .= '/' . $directory;
|
||||
$breadcrumbs[] = [
|
||||
'path' => $pathtodate,
|
||||
'name' => $directory,
|
||||
];
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab the thumbnail URL for the specified entry.
|
||||
*
|
||||
* @param object $entry The file entry as retrieved from the API
|
||||
* @return moodle_url
|
||||
*/
|
||||
protected function get_thumbnail_url($entry) {
|
||||
if (self::use_legacy_api()) {
|
||||
throw new \coding_exception(__FUNCTION__ . ' is a v2 API function only');
|
||||
}
|
||||
|
||||
if ($this->dropbox->supports_thumbnail($entry)) {
|
||||
$thumburl = new moodle_url('/repository/dropbox/thumbnail.php', [
|
||||
// The id field in dropbox is unique - no need to specify a revision.
|
||||
'source' => $entry->id,
|
||||
'path' => $entry->path_lower,
|
||||
|
||||
'repo_id' => $this->id,
|
||||
'ctx_id' => $this->context->id,
|
||||
]);
|
||||
return $thumburl->out(false);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,617 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tests for the Dropbox API (v2).
|
||||
*
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for the Dropbox API (v2).
|
||||
*
|
||||
* @package repository_dropbox
|
||||
* @copyright Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class repository_dropbox_api_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Data provider for has_additional_results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function has_additional_results_provider() {
|
||||
return [
|
||||
'No more results' => [
|
||||
(object) [
|
||||
'has_more' => false,
|
||||
'cursor' => '',
|
||||
],
|
||||
false
|
||||
],
|
||||
'Has more, No cursor' => [
|
||||
(object) [
|
||||
'has_more' => true,
|
||||
'cursor' => '',
|
||||
],
|
||||
false
|
||||
],
|
||||
'Has more, Has cursor' => [
|
||||
(object) [
|
||||
'has_more' => true,
|
||||
'cursor' => 'example_cursor',
|
||||
],
|
||||
true
|
||||
],
|
||||
'Missing has_more' => [
|
||||
(object) [
|
||||
'cursor' => 'example_cursor',
|
||||
],
|
||||
false
|
||||
],
|
||||
'Missing cursor' => [
|
||||
(object) [
|
||||
'has_more' => 'example_cursor',
|
||||
],
|
||||
false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the has_additional_results API function.
|
||||
*
|
||||
* @dataProvider has_additional_results_provider
|
||||
* @param object $result The data to test
|
||||
* @param bool $expected The expected result
|
||||
*/
|
||||
public function test_has_additional_results($result, $expected) {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(null)
|
||||
->getMock();
|
||||
|
||||
$this->assertEquals($expected, $mock->has_additional_results($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for check_and_handle_api_errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check_and_handle_api_errors_provider() {
|
||||
return [
|
||||
'200 http_code' => [
|
||||
['http_code' => 200],
|
||||
'',
|
||||
null,
|
||||
null,
|
||||
],
|
||||
'400 http_code' => [
|
||||
['http_code' => 400],
|
||||
'Unused',
|
||||
'coding_exception',
|
||||
'Invalid input parameter passed to DropBox API.',
|
||||
],
|
||||
'401 http_code' => [
|
||||
['http_code' => 401],
|
||||
'Unused',
|
||||
'\repository_dropbox\authentication_exception',
|
||||
'Authentication token expired',
|
||||
],
|
||||
'409 http_code' => [
|
||||
['http_code' => 409],
|
||||
'Some data here',
|
||||
'coding_exception',
|
||||
'Endpoint specific error: Some data here',
|
||||
],
|
||||
'429 http_code' => [
|
||||
['http_code' => 429],
|
||||
'Unused',
|
||||
'\repository_dropbox\rate_limit_exception',
|
||||
'Rate limit hit',
|
||||
],
|
||||
'500 http_code' => [
|
||||
['http_code' => 500],
|
||||
'Response body',
|
||||
'invalid_response_exception',
|
||||
'500: Response body',
|
||||
],
|
||||
'599 http_code' => [
|
||||
['http_code' => 599],
|
||||
'Response body',
|
||||
'invalid_response_exception',
|
||||
'599: Response body',
|
||||
],
|
||||
'600 http_code (invalid, but not officially an error)' => [
|
||||
['http_code' => 600],
|
||||
'',
|
||||
null,
|
||||
null,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for check_and_handle_api_errors.
|
||||
*
|
||||
* @dataProvider check_and_handle_api_errors_provider
|
||||
* @param object $info The response to test
|
||||
* @param string $data The contented returned by the curl call
|
||||
* @param string $exception The name of the expected exception
|
||||
* @param string $exceptionmessage The expected message in the exception
|
||||
*/
|
||||
public function test_check_and_handle_api_errors($info, $data, $exception, $exceptionmessage) {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(null)
|
||||
->getMock();
|
||||
|
||||
$mock->info = $info;
|
||||
|
||||
$rc = new \ReflectionClass('\repository_dropbox\dropbox');
|
||||
$rcm = $rc->getMethod('check_and_handle_api_errors');
|
||||
$rcm->setAccessible(true);
|
||||
|
||||
if ($exception) {
|
||||
$this->setExpectedException($exception, $exceptionmessage);
|
||||
}
|
||||
|
||||
$result = $rcm->invoke($mock, $data);
|
||||
|
||||
$this->assertNull($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the supports_thumbnail function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function supports_thumbnail_provider() {
|
||||
$tests = [
|
||||
'Only files support thumbnails' => [
|
||||
(object) ['.tag' => 'folder'],
|
||||
false,
|
||||
],
|
||||
'Dropbox currently only supports thumbnail generation for files under 20MB' => [
|
||||
(object) [
|
||||
'.tag' => 'file',
|
||||
'size' => 21 * 1024 * 1024,
|
||||
],
|
||||
false,
|
||||
],
|
||||
'Unusual file extension containing a working format but ending in a non-working one' => [
|
||||
(object) [
|
||||
'.tag' => 'file',
|
||||
'size' => 100 * 1024,
|
||||
'path_lower' => 'Example.jpg.pdf',
|
||||
],
|
||||
false,
|
||||
],
|
||||
'Unusual file extension ending in a working extension' => [
|
||||
(object) [
|
||||
'.tag' => 'file',
|
||||
'size' => 100 * 1024,
|
||||
'path_lower' => 'Example.pdf.jpg',
|
||||
],
|
||||
true,
|
||||
],
|
||||
];
|
||||
|
||||
// See docs at https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail.
|
||||
$types = [
|
||||
'pdf' => false,
|
||||
'doc' => false,
|
||||
'docx' => false,
|
||||
'jpg' => true,
|
||||
'jpeg' => true,
|
||||
'png' => true,
|
||||
'tiff' => true,
|
||||
'tif' => true,
|
||||
'gif' => true,
|
||||
'bmp' => true,
|
||||
];
|
||||
foreach ($types as $type => $result) {
|
||||
$tests["Test support for {$type}"] = [
|
||||
(object) [
|
||||
'.tag' => 'file',
|
||||
'size' => 100 * 1024,
|
||||
'path_lower' => "example_filename.{$type}",
|
||||
],
|
||||
$result,
|
||||
];
|
||||
}
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the supports_thumbnail function.
|
||||
*
|
||||
* @dataProvider supports_thumbnail_provider
|
||||
* @param object $entry The entry to test
|
||||
* @param bool $expected Whether this entry supports thumbnail generation
|
||||
*/
|
||||
public function test_supports_thumbnail($entry, $expected) {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(null)
|
||||
->getMock();
|
||||
|
||||
$this->assertEquals($expected, $mock->supports_thumbnail($entry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the logout makes a call to the correct revocation endpoint.
|
||||
*/
|
||||
public function test_logout_revocation() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['fetch_dropbox_data'])
|
||||
->getMock();
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('fetch_dropbox_data')
|
||||
->with($this->equalTo('auth/token/revoke'), $this->equalTo(null));
|
||||
|
||||
$this->assertNull($mock->logout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the logout function catches authentication_exception exceptions and discards them.
|
||||
*/
|
||||
public function test_logout_revocation_catch_auth_exception() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['fetch_dropbox_data'])
|
||||
->getMock();
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('fetch_dropbox_data')
|
||||
->will($this->throwException(new \repository_dropbox\authentication_exception('Exception should be caught')));
|
||||
|
||||
$this->assertNull($mock->logout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the logout function does not catch any other exception.
|
||||
*/
|
||||
public function test_logout_revocation_does_not_catch_other_exceptions() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['fetch_dropbox_data'])
|
||||
->getMock();
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('fetch_dropbox_data')
|
||||
->will($this->throwException(new \repository_dropbox\rate_limit_exception));
|
||||
|
||||
$this->setExpectedException('\repository_dropbox\rate_limit_exception');
|
||||
$mock->logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test basic fetch_dropbox_data function.
|
||||
*/
|
||||
public function test_fetch_dropbox_data_endpoint() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'request',
|
||||
'get_api_endpoint',
|
||||
'get_content_endpoint',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$endpoint = 'testEndpoint';
|
||||
|
||||
// The fetch_dropbox_data call should be called against the standard endpoint only.
|
||||
$mock->expects($this->once())
|
||||
->method('get_api_endpoint')
|
||||
->with($endpoint)
|
||||
->will($this->returnValue("https://example.com/api/2/{$endpoint}"));
|
||||
|
||||
$mock->expects($this->never())
|
||||
->method('get_content_endpoint');
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('request')
|
||||
->will($this->returnValue(json_encode([])));
|
||||
|
||||
// Make the call.
|
||||
$rc = new \ReflectionClass('\repository_dropbox\dropbox');
|
||||
$rcm = $rc->getMethod('fetch_dropbox_data');
|
||||
$rcm->setAccessible(true);
|
||||
$rcm->invoke($mock, $endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some Dropbox endpoints require that the POSTFIELDS be set to null exactly.
|
||||
*/
|
||||
public function test_fetch_dropbox_data_postfields_null() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'request',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$endpoint = 'testEndpoint';
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('request')
|
||||
->with($this->anything(), $this->callback(function($d) {
|
||||
return $d['CURLOPT_POSTFIELDS'] === 'null';
|
||||
}))
|
||||
->will($this->returnValue(json_encode([])));
|
||||
|
||||
// Make the call.
|
||||
$rc = new \ReflectionClass('\repository_dropbox\dropbox');
|
||||
$rcm = $rc->getMethod('fetch_dropbox_data');
|
||||
$rcm->setAccessible(true);
|
||||
$rcm->invoke($mock, $endpoint, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* When data is specified, it should be json_encoded in POSTFIELDS.
|
||||
*/
|
||||
public function test_fetch_dropbox_data_postfields_data() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'request',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$endpoint = 'testEndpoint';
|
||||
$data = ['something' => 'somevalue'];
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('request')
|
||||
->with($this->anything(), $this->callback(function($d) use ($data) {
|
||||
return $d['CURLOPT_POSTFIELDS'] === json_encode($data);
|
||||
}))
|
||||
->will($this->returnValue(json_encode([])));
|
||||
|
||||
// Make the call.
|
||||
$rc = new \ReflectionClass('\repository_dropbox\dropbox');
|
||||
$rcm = $rc->getMethod('fetch_dropbox_data');
|
||||
$rcm->setAccessible(true);
|
||||
$rcm->invoke($mock, $endpoint, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* When more results are available, these should be fetched until there are no more.
|
||||
*/
|
||||
public function test_fetch_dropbox_data_recurse_on_additional_records() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'request',
|
||||
'get_api_endpoint',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$endpoint = 'testEndpoint';
|
||||
|
||||
// We can't detect if fetch_dropbox_data was called twice because
|
||||
// we can'
|
||||
$mock->expects($this->exactly(3))
|
||||
->method('request')
|
||||
->will($this->onConsecutiveCalls(
|
||||
json_encode(['has_more' => true, 'cursor' => 'Example', 'entries' => ['foo', 'bar']]),
|
||||
json_encode(['has_more' => true, 'cursor' => 'Example', 'entries' => ['baz']]),
|
||||
json_encode(['has_more' => false, 'cursor' => '', 'entries' => ['bum']])
|
||||
));
|
||||
|
||||
// We automatically adjust for the /continue endpoint.
|
||||
$mock->expects($this->exactly(3))
|
||||
->method('get_api_endpoint')
|
||||
->withConsecutive(['testEndpoint'], ['testEndpoint/continue'], ['testEndpoint/continue'])
|
||||
->willReturn($this->onConsecutiveCalls(
|
||||
'https://example.com/api/2/testEndpoint',
|
||||
'https://example.com/api/2/testEndpoint/continue',
|
||||
'https://example.com/api/2/testEndpoint/continue'
|
||||
));
|
||||
|
||||
// Make the call.
|
||||
$rc = new \ReflectionClass('\repository_dropbox\dropbox');
|
||||
$rcm = $rc->getMethod('fetch_dropbox_data');
|
||||
$rcm->setAccessible(true);
|
||||
$result = $rcm->invoke($mock, $endpoint, null);
|
||||
|
||||
$this->assertEquals([
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
'bum',
|
||||
], $result->entries);
|
||||
|
||||
$this->assertFalse(isset($result->cursor));
|
||||
$this->assertFalse(isset($result->has_more));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base tests for the fetch_dropbox_content function.
|
||||
*/
|
||||
public function test_fetch_dropbox_content() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'request',
|
||||
'setHeader',
|
||||
'get_content_endpoint',
|
||||
'get_api_endpoint',
|
||||
'check_and_handle_api_errors',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$data = ['exampledata' => 'examplevalue'];
|
||||
$endpoint = 'getContent';
|
||||
$url = "https://example.com/api/2/{$endpoint}";
|
||||
$response = 'Example content';
|
||||
|
||||
// Only the content endpoint should be called.
|
||||
$mock->expects($this->once())
|
||||
->method('get_content_endpoint')
|
||||
->with($endpoint)
|
||||
->will($this->returnValue($url));
|
||||
|
||||
$mock->expects($this->never())
|
||||
->method('get_api_endpoint');
|
||||
|
||||
$mock->expects($this->exactly(2))
|
||||
->method('setHeader')
|
||||
->withConsecutive(
|
||||
[$this->equalTo('Content-Type: ')],
|
||||
[$this->equalTo('Dropbox-API-Arg: ' . json_encode($data))]
|
||||
);
|
||||
|
||||
// Only one request should be made, and it should forcibly be a POST.
|
||||
$mock->expects($this->once())
|
||||
->method('request')
|
||||
->with($this->equalTo($url), $this->callback(function($options) {
|
||||
return $options['CURLOPT_POST'] === 1;
|
||||
}))
|
||||
->willReturn($response);
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('check_and_handle_api_errors')
|
||||
->with($this->equalTo($response))
|
||||
;
|
||||
|
||||
// Make the call.
|
||||
$rc = new \ReflectionClass('\repository_dropbox\dropbox');
|
||||
$rcm = $rc->getMethod('fetch_dropbox_content');
|
||||
$rcm->setAccessible(true);
|
||||
$result = $rcm->invoke($mock, $endpoint, $data);
|
||||
|
||||
$this->assertEquals($response, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the get_file_share_info function returns an existing link if one is available.
|
||||
*/
|
||||
public function test_get_file_share_info_existing() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'fetch_dropbox_data',
|
||||
'normalize_file_share_info',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$id = 'LifeTheUniverseAndEverything';
|
||||
$file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue'];
|
||||
$sharelink = 'https://example.com/share/link';
|
||||
|
||||
// Mock fetch_dropbox_data to return an existing file.
|
||||
$mock->expects($this->once())
|
||||
->method('fetch_dropbox_data')
|
||||
->with(
|
||||
$this->equalTo('sharing/list_shared_links'),
|
||||
$this->equalTo(['path' => $id])
|
||||
)
|
||||
->willReturn((object) ['links' => [$file]]);
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('normalize_file_share_info')
|
||||
->with($this->equalTo($file))
|
||||
->will($this->returnValue($sharelink));
|
||||
|
||||
$this->assertEquals($sharelink, $mock->get_file_share_info($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the get_file_share_info function creates a new link if one is not available.
|
||||
*/
|
||||
public function test_get_file_share_info_new() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'fetch_dropbox_data',
|
||||
'normalize_file_share_info',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$id = 'LifeTheUniverseAndEverything';
|
||||
$file = (object) ['.tag' => 'file', 'id' => $id, 'path_lower' => 'SomeValue'];
|
||||
$sharelink = 'https://example.com/share/link';
|
||||
|
||||
// Mock fetch_dropbox_data to return an existing file.
|
||||
$mock->expects($this->exactly(2))
|
||||
->method('fetch_dropbox_data')
|
||||
->withConsecutive(
|
||||
[$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
|
||||
[$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
|
||||
'path' => $id,
|
||||
'settings' => [
|
||||
'requested_visibility' => 'public',
|
||||
]
|
||||
])]
|
||||
)
|
||||
->will($this->onConsecutiveCalls(
|
||||
(object) ['links' => []],
|
||||
$file
|
||||
));
|
||||
|
||||
$mock->expects($this->once())
|
||||
->method('normalize_file_share_info')
|
||||
->with($this->equalTo($file))
|
||||
->will($this->returnValue($sharelink));
|
||||
|
||||
$this->assertEquals($sharelink, $mock->get_file_share_info($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test failure behaviour with get_file_share_info fails to create a new link.
|
||||
*/
|
||||
public function test_get_file_share_info_new_failure() {
|
||||
$mock = $this->getMockBuilder('\repository_dropbox\dropbox')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'fetch_dropbox_data',
|
||||
'normalize_file_share_info',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$id = 'LifeTheUniverseAndEverything';
|
||||
|
||||
// Mock fetch_dropbox_data to return an existing file.
|
||||
$mock->expects($this->exactly(2))
|
||||
->method('fetch_dropbox_data')
|
||||
->withConsecutive(
|
||||
[$this->equalTo('sharing/list_shared_links'), $this->equalTo(['path' => $id])],
|
||||
[$this->equalTo('sharing/create_shared_link_with_settings'), $this->equalTo([
|
||||
'path' => $id,
|
||||
'settings' => [
|
||||
'requested_visibility' => 'public',
|
||||
]
|
||||
])]
|
||||
)
|
||||
->will($this->onConsecutiveCalls(
|
||||
(object) ['links' => []],
|
||||
null
|
||||
));
|
||||
|
||||
$mock->expects($this->never())
|
||||
->method('normalize_file_share_info');
|
||||
|
||||
$this->assertNull($mock->get_file_share_info($id));
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,9 @@ class repository_dropbox_generator extends testing_repository_generator {
|
||||
if (!isset($record['dropbox_cachelimit'])) {
|
||||
$record['dropbox_cachelimit'] = 0;
|
||||
}
|
||||
if (!isset($record['legacyapi'])) {
|
||||
$record['legacyapi'] = 0;
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2016052300; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2016052301; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2016051900; // Requires this Moodle version
|
||||
$plugin->component = 'repository_dropbox'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -105,6 +105,7 @@ class core_repository_generator_testcase extends advanced_testcase {
|
||||
$record->dropbox_key = '12345';
|
||||
$record->dropbox_secret = '67890';
|
||||
$record->dropbox_cachelimit = '123';
|
||||
$record->legacyapi = '0';
|
||||
$dropbox = $this->getDataGenerator()->create_repository_type('dropbox', $record);
|
||||
|
||||
$config = get_config('dropbox');
|
||||
@@ -112,7 +113,7 @@ class core_repository_generator_testcase extends advanced_testcase {
|
||||
$record->enablecourseinstances = '0';
|
||||
$this->assertEquals($record, $config);
|
||||
$this->assertEquals('Custom Dropbox',
|
||||
$DB->get_field('repository_instances', 'name', array('typeid' => $dropbox->id), MUST_EXIST));
|
||||
$DB->get_field('repository_instances', 'name', array('typeid' => $dropbox->id), MUST_EXIST));
|
||||
|
||||
// Multiple instances.
|
||||
$record = new stdClass();
|
||||
|
||||
Reference in New Issue
Block a user