AutorÃa | Ultima modificación | Ver Log |
<?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.}}}