Merge branch 'MDL-52051-31' of git://github.com/andrewnicols/moodle into MOODLE_31_STABLE

This commit is contained in:
David Monllao
2016-10-11 17:11:41 +08:00
15 changed files with 2541 additions and 507 deletions
+4 -1
View File
@@ -517,7 +517,10 @@ abstract class oauth2_client extends curl {
// Store the token an expiry time.
$accesstoken = new stdClass;
$accesstoken->token = $r->access_token;
$accesstoken->expires = (time() + ($r->expires_in - 10)); // Expires 10 seconds before actual expiry.
if (isset($r->expires_in)) {
// Expires 10 seconds before actual expiry.
$accesstoken->expires = (time() + ($r->expires_in - 10));
}
$this->store_token($accesstoken);
return true;
@@ -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 {
}
+368
View File
@@ -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 {
}
+654
View File
@@ -0,0 +1,654 @@
<?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/>.
/**
* This plugin is used to access user's dropbox files
*
* @since Moodle 2.0
* @package repository_dropbox
* @copyright 2012 Marina Glancy
* @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/repository/lib.php');
/**
* Repository to access Dropbox files
*
* @package repository_dropbox
* @copyright 2010 Dongsheng Cai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repository_dropbox_legacy extends repository {
/** @var dropbox the instance of dropbox client */
private $dropbox;
/** @var array files */
public $files;
/** @var bool flag of login status */
public $logged = false;
/** @var int maximum size of file to cache in moodle filepool */
public $cachelimit = null;
/** @var int cached file ttl */
private $cachedfilettl = null;
/**
* Constructor of dropbox plugin
*
* @param int $repositoryid
* @param stdClass $context
* @param array $options
*/
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
global $CFG;
$options['page'] = optional_param('p', 1, PARAM_INT);
parent::__construct($repositoryid, $context, $options);
$this->setting = 'dropbox_';
$this->dropbox_key = $this->get_option('dropbox_key');
$this->dropbox_secret = $this->get_option('dropbox_secret');
// One day.
$this->cachedfilettl = 60 * 60 * 24;
if (isset($options['access_key'])) {
$this->access_key = $options['access_key'];
} else {
$this->access_key = get_user_preferences($this->setting.'_access_key', '');
}
if (isset($options['access_secret'])) {
$this->access_secret = $options['access_secret'];
} else {
$this->access_secret = get_user_preferences($this->setting.'_access_secret', '');
}
if (!empty($this->access_key) && !empty($this->access_secret)) {
$this->logged = true;
}
$callbackurl = new moodle_url($CFG->wwwroot.'/repository/repository_callback.php', array(
'callback' => 'yes',
'repo_id' => $repositoryid
));
$args = array(
'oauth_consumer_key' => $this->dropbox_key,
'oauth_consumer_secret' => $this->dropbox_secret,
'oauth_callback' => $callbackurl->out(false),
'api_root' => 'https://api.dropbox.com/1/oauth',
);
$this->dropbox = new repository_dropbox_legacy_dropbox($args);
}
/**
* Get the type name (no whitespace)
* For a human readable name, use get_readablename()
*
* @return string the type name
*/
public function get_typename() {
return 'dropbox';
}
/**
* Set access key
*
* @param string $access_key
*/
public function set_access_key($access_key) {
$this->access_key = $access_key;
}
/**
* Set access secret
*
* @param string $access_secret
*/
public function set_access_secret($access_secret) {
$this->access_secret = $access_secret;
}
/**
* Check if moodle has got access token and secret
*
* @return bool
*/
public function check_login() {
return !empty($this->logged);
}
/**
* Generate dropbox login url
*
* @return array
*/
public function print_login() {
$result = $this->dropbox->request_token();
set_user_preference($this->setting.'_request_secret', $result['oauth_token_secret']);
$url = $result['authorize_url'];
if ($this->options['ajax']) {
$ret = array();
$popup_btn = new stdClass();
$popup_btn->type = 'popup';
$popup_btn->url = $url;
$ret['login'] = array($popup_btn);
return $ret;
} else {
echo '<a target="_blank" href="'.$url.'">'.get_string('login', 'repository').'</a>';
}
}
/**
* Request access token
*
* @return array
*/
public function callback() {
$token = optional_param('oauth_token', '', PARAM_TEXT);
$secret = get_user_preferences($this->setting.'_request_secret', '');
$access_token = $this->dropbox->get_access_token($token, $secret);
set_user_preference($this->setting.'_access_key', $access_token['oauth_token']);
set_user_preference($this->setting.'_access_secret', $access_token['oauth_token_secret']);
}
/**
* Get dropbox files
*
* @param string $path
* @param int $page
* @return array
*/
public function get_listing($path = '', $page = '1') {
global $OUTPUT;
if (empty($path) || $path == '/') {
$path = '/';
} else {
$path = file_correct_filepath($path);
}
$encoded_path = str_replace("%2F", "/", rawurlencode($path));
$list = array();
$list['list'] = array();
$list['manage'] = 'https://www.dropbox.com/home';
$list['dynload'] = true;
$list['nosearch'] = true;
$list['logouturl'] = 'https://www.dropbox.com/logout';
$list['message'] = get_string('logoutdesc', 'repository_dropbox');
// Process breadcrumb trail.
$list['path'] = array(
array('name' => get_string('dropbox', 'repository_dropbox'), 'path' => '/')
);
$result = $this->dropbox->get_listing($encoded_path, $this->access_key, $this->access_secret);
if (!is_object($result) || empty($result)) {
return $list;
}
if (empty($result->path)) {
$current_path = '/';
} else {
$current_path = file_correct_filepath($result->path);
}
$trail = '';
if (!empty($path)) {
$parts = explode('/', $path);
if (count($parts) > 1) {
foreach ($parts as $part) {
if (!empty($part)) {
$trail .= ('/'.$part);
$list['path'][] = array('name' => $part, 'path' => $trail);
}
}
} else {
$list['path'][] = array('name' => $path, 'path' => $path);
}
}
if (!empty($result->error)) {
// Reset access key.
set_user_preference($this->setting.'_access_key', '');
set_user_preference($this->setting.'_access_secret', '');
throw new repository_exception('repositoryerror', 'repository', '', $result->error);
}
if (empty($result->contents) or !is_array($result->contents)) {
return $list;
}
$files = $result->contents;
$dirslist = array();
$fileslist = array();
foreach ($files as $file) {
if ($file->is_dir) {
$dirslist[] = array(
'title' => substr($file->path, strpos($file->path, $current_path) + strlen($current_path)),
'path' => file_correct_filepath($file->path),
'date' => strtotime($file->modified),
'thumbnail' => $OUTPUT->pix_url(file_folder_icon(64))->out(false),
'thumbnail_height' => 64,
'thumbnail_width' => 64,
'children' => array(),
);
} else {
$thumbnail = null;
if ($file->thumb_exists) {
$thumburl = new moodle_url('/repository/dropbox/thumbnail.php',
array('repo_id' => $this->id,
'ctx_id' => $this->context->id,
'source' => $file->path,
'rev' => $file->rev // include revision to avoid cache problems
));
$thumbnail = $thumburl->out(false);
}
$fileslist[] = array(
'title' => substr($file->path, strpos($file->path, $current_path) + strlen($current_path)),
'source' => $file->path,
'size' => $file->bytes,
'date' => strtotime($file->modified),
'thumbnail' => $OUTPUT->pix_url(file_extension_icon($file->path, 64))->out(false),
'realthumbnail' => $thumbnail,
'thumbnail_height' => 64,
'thumbnail_width' => 64,
);
}
}
$fileslist = array_filter($fileslist, array($this, 'filter'));
$list['list'] = array_merge($dirslist, array_values($fileslist));
return $list;
}
/**
* Displays a thumbnail for current user's dropbox file
*
* @param string $string
*/
public function send_thumbnail($source) {
global $CFG;
$saveas = $this->prepare_file('');
try {
$access_key = get_user_preferences($this->setting.'_access_key', '');
$access_secret = get_user_preferences($this->setting.'_access_secret', '');
$this->dropbox->set_access_token($access_key, $access_secret);
$this->dropbox->get_thumbnail($source, $saveas, $CFG->repositorysyncimagetimeout);
$content = file_get_contents($saveas);
unlink($saveas);
// 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 thumbnail in the browser for a long time.
send_file($content, basename($source), 30*24*60*60, 0, true);
} catch (Exception $e) {}
}
/**
* Logout from dropbox
* @return array
*/
public function logout() {
set_user_preference($this->setting.'_access_key', '');
set_user_preference($this->setting.'_access_secret', '');
$this->access_key = '';
$this->access_secret = '';
return $this->print_login();
}
/**
* Fixes references in DB that contains user credentials
*
* @param string $reference contents of DB field files_reference.reference
*/
public function fix_old_style_reference($reference) {
global $CFG;
$ref = unserialize($reference);
if (!isset($ref->url)) {
$this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
$ref->url = $this->dropbox->get_file_share_link($ref->path, $CFG->repositorygetfiletimeout);
if (!$ref->url) {
// Some error occurred, do not fix reference for now.
return $reference;
}
}
unset($ref->access_key);
unset($ref->access_secret);
$newreference = serialize($ref);
if ($newreference !== $reference) {
// We need to update references in the database.
global $DB;
$params = array(
'newreference' => $newreference,
'newhash' => sha1($newreference),
'reference' => $reference,
'hash' => sha1($reference),
'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;
}
/**
* 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
*/
private function get_file_download_link($sharedurl) {
$url = new \moodle_url($sharedurl);
$url->param('dl', 1);
return $url->out(false);
}
/**
* Downloads a file from external repository and saves it in temp dir
*
* @throws moodle_exception when file could not be downloaded
*
* @param string $reference the content of files.reference field or result of
* function {@link repository_dropbox::get_file_reference()}
* @param string $saveas filename (without path) to save the downloaded file in the
* temporary directory, if omitted or file already exists the new filename will be generated
* @return array with elements:
* path: internal location of the file
* url: URL to the source (from parameters)
*/
public function get_file($reference, $saveas = '') {
global $CFG;
$ref = unserialize($reference);
$saveas = $this->prepare_file($saveas);
if (isset($ref->access_key) && isset($ref->access_secret) && isset($ref->path)) {
$this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
return $this->dropbox->get_file($ref->path, $saveas, $CFG->repositorygetfiletimeout);
} else if (isset($ref->url)) {
$c = new curl;
$url = $this->get_file_download_link($ref->url);
$result = $c->download_one($url, null, array('filepath' => $saveas, 'timeout' => $CFG->repositorygetfiletimeout, 'followlocation' => true));
$info = $c->get_info();
if ($result !== true || !isset($info['http_code']) || $info['http_code'] != 200) {
throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
}
return array('path' => $saveas, 'url' => $url);
}
throw new moodle_exception('cannotdownload', 'repository');
}
/**
* Dropbox plugin supports all kinds of files
*
* @return array
*/
public function supported_filetypes() {
return '*';
}
/**
* User cannot use the external link to dropbox
*
* @return int
*/
public function supported_returntypes() {
return FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
}
/**
* Return file URL for external link
*
* @param string $reference the result of get_file_reference()
* @return string
*/
public function get_link($reference) {
global $CFG;
$ref = unserialize($reference);
if (!isset($ref->url)) {
$this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
$ref->url = $this->dropbox->get_file_share_link($ref->path, $CFG->repositorygetfiletimeout);
}
return $this->get_file_download_link($ref->url);
}
/**
* Prepare file reference information
*
* @param string $source
* @return string file referece
*/
public function get_file_reference($source) {
global $USER, $CFG;
$reference = new stdClass;
$reference->path = $source;
$reference->userid = $USER->id;
$reference->username = fullname($USER);
$reference->access_key = get_user_preferences($this->setting.'_access_key', '');
$reference->access_secret = get_user_preferences($this->setting.'_access_secret', '');
// By API we don't know if we need this reference to just download a file from dropbox
// into moodle filepool or create a reference. Since we need to create a shared link
// only in case of reference we analyze the script parameter.
$usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
if ($usefilereference) {
$this->dropbox->set_access_token($reference->access_key, $reference->access_secret);
$url = $this->dropbox->get_file_share_link($source, $CFG->repositorygetfiletimeout);
if ($url) {
unset($reference->access_key);
unset($reference->access_secret);
$reference->url = $url;
}
}
return serialize($reference);
}
public function sync_reference(stored_file $file) {
global $CFG;
if ($file->get_referencelastsync() + DAYSECS > time()) {
// Synchronise not more often than once a day.
return false;
}
$ref = unserialize($file->get_reference());
if (!isset($ref->url)) {
// This is an old-style reference in DB. We need to fix it.
$ref = unserialize($this->fix_old_style_reference($file->get_reference()));
}
if (!isset($ref->url)) {
return false;
}
$c = new curl;
$url = $this->get_file_download_link($ref->url);
if (file_extension_in_typegroup($ref->path, 'web_image')) {
$saveas = $this->prepare_file('');
try {
$result = $c->download_one($url, array(), array('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, $newfile) = $fs->add_file_to_pool($saveas);
$file->set_synchronized($contenthash, $filesize);
return true;
}
} catch (Exception $e) {
}
}
$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;
}
/**
* Cache file from external repository by reference.
*
* Dropbox repository regularly caches all external files that are smaller than
* {@link repository_dropbox::max_cache_bytes()}
*
* @param string $reference this reference is generated by
* repository::get_file_reference()
* @param stored_file $storedfile created file reference
*/
public function cache_file_by_reference($reference, $storedfile) {
try {
$this->import_external_file_contents($storedfile, $this->max_cache_bytes());
} catch (Exception $e) {
}
}
/**
* Return human readable reference information.
* {@link stored_file::get_reference()}
*
* @param string $reference
* @param int $filestatus status of the file, 0 - ok, 666 - source missing
* @return string
*/
public function get_reference_details($reference, $filestatus = 0) {
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);
}
}
/**
* Return the source information
*
* @param string $source
* @return string
*/
public function get_file_source_info($source) {
global $USER;
return 'Dropbox ('.fullname($USER).'): ' . $source;
}
/**
* Repository method to serve the referenced file
*
* This method is ivoked from {@link send_stored_file()}.
* Dropbox repository first caches the file by reading it into temporary folder and then
* serves from there.
*
* @param stored_file $storedfile the file that contains the reference
* @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
* @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
* @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
* @param array $options additional options affecting the file serving
*/
public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
$ref = unserialize($storedfile->get_reference());
if ($storedfile->get_filesize() > $this->max_cache_bytes()) {
header('Location: '.$this->get_file_download_link($ref->url));
die;
}
try {
$this->import_external_file_contents($storedfile, $this->max_cache_bytes());
if (!is_array($options)) {
$options = array();
}
$options['sendcachedexternalfile'] = true;
send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options);
} catch (moodle_exception $e) {
// Redirect to Dropbox, it will show the error.
// We redirect to Dropbox shared link, not to download link here!
header('Location: '.$ref->url);
die;
}
}
/**
* Returns the maximum size of the Dropbox files to cache in moodle.
*
* Note that {@link repository_dropbox::sync_reference()} will try to cache images even
* when they are bigger in order to generate thumbnails. However there is
* a small timeout for downloading images for synchronisation and it will
* probably fail if the image is too big.
*
* @return int
*/
public function max_cache_bytes() {
if ($this->cachelimit === null) {
$this->cachelimit = (int) get_config('dropbox', 'dropbox_cachelimit');
}
return $this->cachelimit;
}
/**
* Caches all references to Dropbox files in moodle filepool
*
* Invoked by {@link repository_dropbox_cron()}. Only files smaller than
* {@link repository_dropbox::max_cache_bytes()} and only files which
* synchronisation timeout have not expired are cached.
*/
public function 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) {}
}
}
}
@@ -35,7 +35,7 @@ require_once($CFG->libdir.'/oauthlib.php');
* @copyright 2010 Dongsheng Cai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dropbox extends oauth_helper {
class repository_dropbox_legacy_dropbox extends oauth_helper {
/** @var string dropbox access type, can be dropbox or sandbox */
private $mode = 'dropbox';
/** @var string dropbox api url*/
@@ -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');
}
}
+4 -2
View File
@@ -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';
+723 -501
View File
File diff suppressed because it is too large Load Diff
+617
View File
@@ -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;
}
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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();