first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class authentication_exception extends dropbox_exception {
}
+411
View File
@@ -0,0 +1,411 @@
<?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 <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace repository_dropbox;
use core\oauth2\client;
use core\oauth2\issuer;
/**
* Dropbox V2 API.
*
* @package repository_dropbox
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dropbox extends client {
/**
* @var array Custom continue endpoints that differ from the standard.
*/
private $mappedcontinueoverides = [
'files/search_v2' => 'files/search/continue_v2'
];
/**
* Create the DropBox API Client.
*
* @param issuer $issuer The dropbox issuer
* @param string $callback The callback URL
*/
public function __construct(issuer $issuer, $callback) {
parent::__construct($issuer, $callback, '', false, true);
}
/**
* Override - Return an empty string to override parent function.
*
* Dropbox does not require scopes to be provided and can function without them.
* Additional information MDL-70268
*
* @return string
*/
protected function get_login_scopes() {
return '';
}
/**
* 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);
}
/**
* Get the continue endpoint for the provided endpoint.
*
* @param string $endpoint The original endpoint
* @return string $endpoint The generated/mapped continue link
*/
protected function get_endpoint_for_continue(string $endpoint) {
// 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) {
// First check if the API call uses a custom mapped continue endpoint.
if (isset($this->mappedcontinueoverides[$endpoint])) {
$endpoint = $this->mappedcontinueoverides[$endpoint];
} else {
// Only add /continue if it is not already present.
$endpoint .= '/continue';
}
}
return $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
* @param string $resultnode The name of the node that contains the data
* @return object Content decoded from the endpoint
*/
protected function fetch_dropbox_data($endpoint, $data = [], string $resultnode = 'entries') {
$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)) {
$endpoint = $this->get_endpoint_for_continue($endpoint);
// Fetch the next page of results.
$additionaldata = $this->fetch_dropbox_data($endpoint, [
'cursor' => $result->cursor,
], $resultnode);
// Merge the list of entries.
$result->$resultnode = array_merge($result->$resultnode, $additionaldata->$resultnode);
}
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 mixed $data The returned content.
* @throws moodle_exception
*/
protected function check_and_handle_api_errors($data) {
if (!is_array($this->info) or $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 JSON response body for the specifics of the error.
throw new \coding_exception('Endpoint specific error: ' . $data->error_summary);
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 = '') {
// There is nothing to be searched. Return an empty array to mimic the response from Dropbox.
if (!$query) {
return [];
}
$data = $this->fetch_dropbox_data('files/search_v2', [
'options' => [
'path' => '',
'filename_only' => true,
],
'query' => $query,
], 'matches');
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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dropbox_exception extends \moodle_exception {
}
@@ -0,0 +1,114 @@
<?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/>.
/**
* Privacy Subsystem implementation for repository_dropbox.
*
* @package repository_dropbox
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace repository_dropbox\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\context;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\userlist;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for repository_dropbox implementing metadata and plugin providers.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_external_location_link(
'dropbox.com',
[
'query' => 'privacy:metadata:repository_dropbox:query'
],
'privacy:metadata:repository_dropbox'
);
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
return new contextlist();
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
}
}
@@ -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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @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 <andrew@nicols.co.uk>
* @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');
}
}
@@ -0,0 +1,49 @@
<?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/>.
namespace repository_dropbox\task;
defined('MOODLE_INTERNAL') || die();
/**
* A schedule task for dropbox repository cron.
*
* @package repository_dropbox
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cron_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('crontask', 'repository_dropbox');
}
/**
* Run dropbox repository cron.
*/
public function execute() {
global $CFG;
require_once($CFG->dirroot . '/repository/lib.php');
$instances = \repository::get_instances(['type' => 'dropbox']);
foreach ($instances as $instance) {
$instance->cron();
}
}
}
+37
View File
@@ -0,0 +1,37 @@
<?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/>.
/**
* Plugin capabilities.
*
* @package repository_dropbox
* @copyright 2010 Dongsheng Cai
* @author Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'repository/dropbox:view' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'user' => CAP_ALLOW
)
)
);
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Definition of dropbox repository scheduled tasks.
*
* @package repository_dropbox
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => '\repository_dropbox\task\cron_task',
'blocking' => 0,
'minute' => '*',
'hour' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*'
)
);
+35
View File
@@ -0,0 +1,35 @@
<?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/>.
/**
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_repository_dropbox_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
@@ -0,0 +1,39 @@
<?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/>.
/**
* Strings for component 'repository_dropbox', language 'en', branch 'MOODLE_20_STABLE'
*
* @package repository_dropbox
* @copyright 2010 Dongsheng Cai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['configplugin'] = 'Dropbox configuration';
$string['crontask'] = 'Background processing for Dropbox repository';
$string['notitle'] = 'notitle';
$string['remember'] = 'Remember me';
$string['pluginname'] = 'Dropbox';
$string['dropbox'] = 'Dropbox';
$string['issuer'] = 'OAuth 2 service';
$string['issuer_help'] = 'Select the OAuth 2 service that is configured to talk to the Dropbox API. If the service doesn\'t exist yet, you will need to create it.';
$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['oauth2redirecturi'] = 'OAuth 2 Redirect URI';
$string['privacy:metadata:repository_dropbox'] = 'The Dropbox repository plugin does not store any personal data, but does transmit user data from Moodle to the remote system.';
$string['privacy:metadata:repository_dropbox:query'] = 'The Dropbox repository user search text query.';
+788
View File
@@ -0,0 +1,788 @@
<?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
*/
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 extends repository {
/**
* @var dropbox The instance of dropbox client.
*/
private $dropbox;
/**
* @var int The maximum file size to cache in the moodle filepool.
*/
public $cachelimit = null;
/**
* Constructor of dropbox plugin.
*
* @inheritDocs
*/
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = []) {
$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.
$issuer = \core\oauth2\api::get_issuer(get_config('dropbox', 'dropbox_issuerid'));
$this->dropbox = new repository_dropbox\dropbox($issuer, $returnurl);
}
/**
* Repository method to serve the referenced file.
*
* @inheritDocs
*/
public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
$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;
}
}
/**
* Return human readable reference information.
* {@link stored_file::get_reference()}
*
* @inheritDocs
*/
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);
}
}
/**
* Cache file from external repository by reference.
* {@link repository::get_file_reference()}
* {@link repository::get_file()}
* Invoked at MOODLE/repository/repository_ajax.php.
*
* @inheritDocs
*/
public function 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.
}
}
/**
* Return the source information.
*
* The result of the function is stored in files.source field. It may be analysed
* when the source file is lost or repository may use it to display human-readable
* location of reference original.
*
* This method is called when file is picked for the first time only. When file
* (either copy or a reference) is already in moodle and it is being picked
* again to another file area (also as a copy or as a reference), the value of
* files.source is copied.
*
* @inheritDocs
*/
public function get_file_source_info($source) {
global $USER;
return 'Dropbox ('.fullname($USER).'): ' . $source;
}
/**
* Prepare file reference information.
*
* @inheritDocs
*/
public function 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);
}
/**
* Return file URL for external link.
*
* @inheritDocs
*/
public function get_link($reference) {
$unpacked = $this->unpack_reference($reference);
return $this->get_file_download_link($unpacked->url);
}
/**
* Downloads a file from external repository and saves it in temp dir.
*
* @inheritDocs
*/
public function 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];
}
/**
* Dropbox plugin supports all kinds of files.
*
* @inheritDocs
*/
public function supported_filetypes() {
return '*';
}
/**
* User cannot use the external link to dropbox.
*
* @inheritDocs
*/
public function supported_returntypes() {
return FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
}
/**
* Get dropbox files.
*
* @inheritDocs
*/
public function get_listing($path = '', $page = '1') {
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;
}
/**
* Get dropbox files in the specified path.
*
* @param string $query The search query
* @param int $page The page number
* @return array
*/
public function search($query, $page = 0) {
$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;
}
/**
* Displays a thumbnail for current user's dropbox file.
*
* @inheritDocs
*/
public function 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);
}
/**
* Fixes references in DB that contains user credentials.
*
* @param string $packed Content of DB field files_reference.reference
* @return string New serialized reference
*/
protected function fix_old_style_reference($packed) {
$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) {
$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() {
$this->dropbox->logout();
return $this->print_login();
}
/**
* Check if the dropbox is logged in via the oauth process.
*
* @inheritDocs
*/
public function check_login() {
return $this->dropbox->is_logged_in();
}
/**
* Generate dropbox login url.
*
* @inheritDocs
*/
public function 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'));
}
}
/**
* Request access token.
*
* @inheritDocs
*/
public function callback() {
$this->dropbox->callback();
}
/**
* 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.
*
* @inheritDocs
*/
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) {
// If an exception is thrown, just continue. This is only a pre-fetch to help speed up general use.
}
}
}
/**
* Add Plugin settings input to Moodle form.
*
* @inheritDocs
*/
public static function type_config_form($mform, $classname = 'repository') {
parent::type_config_form($mform);
$options = [];
$issuers = \core\oauth2\api::get_all_issuers();
foreach ($issuers as $issuer) {
$options[$issuer->get('id')] = s($issuer->get('name'));
}
$strrequired = get_string('required');
$mform->addElement('select', 'dropbox_issuerid', get_string('issuer', 'repository_dropbox'), $options);
$mform->addHelpButton('dropbox_issuerid', 'issuer', 'repository_dropbox');
$mform->addRule('dropbox_issuerid', $strrequired, 'required', null, 'client');
$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);
$mform->addElement('static', 'dropbox_cachelimit_info', '', get_string('cachelimit_info', 'repository_dropbox'));
}
/**
* Set options.
*
* @param array $options
* @return mixed
*/
public function set_option($options = []) {
if (!empty($options['dropbox_issuerid'])) {
set_config('dropbox_issuerid', trim($options['dropbox_issuerid']), 'dropbox');
unset($options['dropbox_issuerid']);
}
if (!empty($options['dropbox_cachelimit'])) {
$this->cachelimit = (int) trim($options['dropbox_cachelimit']);
set_config('dropbox_cachelimit', $this->cachelimit, 'dropbox');
unset($options['dropbox_cachelimit']);
}
return parent::set_option($options);
}
/**
* Get dropbox options
* @param string $config
* @return mixed
*/
public function get_option($config = '') {
if ($config === 'dropbox_issuerid') {
return trim(get_config('dropbox', 'dropbox_issuerid'));
} else if ($config === 'dropbox_cachelimit') {
return $this->max_cache_bytes();
} else {
$options = parent::get_option();
$options['dropbox_issuerid'] = trim(get_config('dropbox', 'dropbox_issuerid'));
$options['dropbox_cachelimit'] = $this->max_cache_bytes();
}
return $options;
}
/**
* Return the OAuth 2 Redirect URI.
*
* @return moodle_url
*/
public static function get_oauth2callbackurl() {
global $CFG;
return new moodle_url('/admin/oauth2callback.php');
}
/**
* Option names of dropbox plugin.
*
* @inheritDocs
*/
public static function get_type_option_names() {
return [
'dropbox_issuerid',
'pluginname',
'dropbox_cachelimit',
];
}
/**
* Performs synchronisation of an external file if the previous one has expired.
*
* This function must be implemented for external repositories supporting
* FILE_REFERENCE, it is called for existing aliases when their filesize,
* contenthash or timemodified are requested. It is not called for internal
* repositories (see {@link repository::has_moodle_files()}), references to
* internal files are updated immediately when source is modified.
*
* Referenced files may optionally keep their content in Moodle filepool (for
* thumbnail generation or to be able to serve cached copy). In this
* case both contenthash and filesize need to be synchronized. Otherwise repositories
* should use contenthash of empty file and correct filesize in bytes.
*
* Note that this function may be run for EACH file that needs to be synchronised at the
* moment. If anything is being downloaded or requested from external sources there
* should be a small timeout. The synchronisation is performed to update the size of
* the file and/or to update image and re-generated image preview. There is nothing
* fatal if syncronisation fails but it is fatal if syncronisation takes too long
* and hangs the script generating a page.
*
* Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or
* $file->get_timemodified() make sure that recursion does not happen.
*
* Called from {@link stored_file::sync_external_file()}
*
* @inheritDocs
*/
public function sync_reference(stored_file $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) {
$file->set_synchronised_content_from_file($saveas);
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) {
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;
}
// Due to a change in the api, the actual content is in a nested metadata tree.
if ($entrydata->{".tag"} == "metadata" && isset($entrydata->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->image_url(file_folder_icon())->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->image_url(file_extension_icon($entrydata->path_lower))->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) {
// 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 ($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 '';
}
/**
* 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;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+1
View File
@@ -0,0 +1 @@
<svg width="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" height="32" preserveAspectRatio="xMinYMid meet"><path d="M8 2.4l8 5.1-8 5.1-8-5.1 8-5.1zm16 0l8 5.1-8 5.1-8-5.1 8-5.1zM0 17.7l8-5.1 8 5.1-8 5.1-8-5.1zm24-5.1l8 5.1-8 5.1-8-5.1 8-5.1zM8 24.5l8-5.1 8 5.1-8 5.1-8-5.1z" fill="#0062FF"/></svg>

After

Width:  |  Height:  |  Size: 309 B

+609
View File
@@ -0,0 +1,609 @@
<?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/>.
namespace repository_dropbox;
/**
* Tests for the Dropbox API (v2).
*
* @package repository_dropbox
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api_test 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): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([])
->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::class,
'Authentication token expired',
],
'409 http_code' => [
['http_code' => 409],
json_decode('{"error": "Some value", "error_summary": "Some data here"}'),
'coding_exception',
'Endpoint specific error: Some data here',
],
'429 http_code' => [
['http_code' => 429],
'Unused',
\repository_dropbox\rate_limit_exception::class,
'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): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([])
->getMock();
$mock->info = $info;
$rc = new \ReflectionClass(\repository_dropbox\dropbox::class);
$rcm = $rc->getMethod('check_and_handle_api_errors');
if ($exception) {
$this->expectException($exception);
}
if ($exceptionmessage) {
$this->expectExceptionMessage($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): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([])
->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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods(['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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods(['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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods(['fetch_dropbox_data'])
->getMock();
$mock->expects($this->once())
->method('fetch_dropbox_data')
->will($this->throwException(new \repository_dropbox\rate_limit_exception));
$this->expectException(\repository_dropbox\rate_limit_exception::class);
$mock->logout();
}
/**
* Test basic fetch_dropbox_data function.
*/
public function test_fetch_dropbox_data_endpoint(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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::class);
$rcm = $rc->getMethod('fetch_dropbox_data');
$rcm->invoke($mock, $endpoint);
}
/**
* Some Dropbox endpoints require that the POSTFIELDS be set to null exactly.
*/
public function test_fetch_dropbox_data_postfields_null(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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::class);
$rcm = $rc->getMethod('fetch_dropbox_data');
$rcm->invoke($mock, $endpoint, null);
}
/**
* When data is specified, it should be json_encoded in POSTFIELDS.
*/
public function test_fetch_dropbox_data_postfields_data(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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::class);
$rcm = $rc->getMethod('fetch_dropbox_data');
$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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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', 'matches' => ['foo', 'bar']]),
json_encode(['has_more' => true, 'cursor' => 'Example', 'matches' => ['baz']]),
json_encode(['has_more' => false, 'cursor' => '', 'matches' => ['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::class);
$rcm = $rc->getMethod('fetch_dropbox_data');
$result = $rcm->invoke($mock, $endpoint, null, 'matches');
$this->assertEquals([
'foo',
'bar',
'baz',
'bum',
], $result->matches);
$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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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::class);
$rcm = $rc->getMethod('fetch_dropbox_content');
$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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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(): void {
$mock = $this->getMockBuilder(\repository_dropbox\dropbox::class)
->disableOriginalConstructor()
->onlyMethods([
'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));
}
}
@@ -0,0 +1,53 @@
<?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 repository data generator
*
* @package repository_dropbox
* @category test
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Dropbox repository data generator class
*
* @package repository_dropbox
* @category test
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repository_dropbox_generator extends testing_repository_generator {
/**
* Fill in type record defaults.
*
* @param array $record
* @return array
*/
protected function prepare_type_record(array $record) {
$record = parent::prepare_type_record($record);
if (!isset($record['dropbox_issuerid'])) {
$record['dropbox_issuerid'] = 0;
}
if (!isset($record['dropbox_cachelimit'])) {
$record['dropbox_cachelimit'] = 0;
}
return $record;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?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 script displays one thumbnail of the image in current user's dropbox.
* If {@link repository_dropbox::send_thumbnail()} can not display image
* the default 64x64 filetype icon is returned
*
* @package repository_dropbox
* @copyright 2012 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/lib.php');
$repoid = optional_param('repo_id', 0, PARAM_INT); // Repository ID
$contextid = optional_param('ctx_id', SYSCONTEXTID, PARAM_INT); // Context ID
$source = optional_param('source', '', PARAM_TEXT); // File path in current user's dropbox
$thumbnailavailable = isloggedin();
$thumbnailavailable = $thumbnailavailable && $repoid;
$thumbnailavailable = $thumbnailavailable && $source;
$thumbnailavailable = $thumbnailavailable && ($repo = repository::get_repository_by_id($repoid, $contextid));
$thumbnailavailable = $thumbnailavailable && method_exists($repo, 'send_thumbnail');
if ($thumbnailavailable) {
// Try requesting thumbnail and outputting it.
// This function exits if thumbnail was retrieved.
$repo->send_thumbnail($source);
}
// Send default icon for the file type.
$fileicon = file_extension_icon($source);
send_file($CFG->dirroot . '/pix/' . $fileicon . '.png', basename($fileicon) . '.svg');
+30
View File
@@ -0,0 +1,30 @@
<?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/>.
/**
* Version details
*
* @package repository_dropbox
* @copyright 2010 Dongsheng Cai
* @author Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'repository_dropbox'; // Full name of the plugin (used for diagnostics)