diff --git a/repository/skydrive/classes/access.php b/repository/skydrive/classes/access.php new file mode 100644 index 00000000000..8c3fd90edc6 --- /dev/null +++ b/repository/skydrive/classes/access.php @@ -0,0 +1,56 @@ +. + +/** + * Class for loading/storing access records from the DB. + * + * @package core + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace repository_skydrive; + +defined('MOODLE_INTERNAL') || die(); + +use core\persistent; + +/** + * Class for loading/storing issuer from the DB + * + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class access extends persistent { + + const TABLE = 'repository_skydrive_access'; + + /** + * Return the definition of the properties of this model. + * + * @return array + */ + protected static function define_properties() { + return array( + 'permissionid' => array( + 'type' => PARAM_RAW + ), + 'itemid' => array( + 'type' => PARAM_RAW + ) + ); + } + +} diff --git a/repository/skydrive/classes/remove_temp_access_task.php b/repository/skydrive/classes/remove_temp_access_task.php new file mode 100644 index 00000000000..98247ec93db --- /dev/null +++ b/repository/skydrive/classes/remove_temp_access_task.php @@ -0,0 +1,79 @@ +. + +/** + * A scheduled task. + * + * @package repository_skydrive + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace repository_skydrive; + +use \core\task\scheduled_task; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Simple task to delete temporary permission records. + * @package core + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class remove_temp_access_task extends scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('removetempaccesstask', 'repository_skydrive'); + } + + /** + * Do the job. + * Throw exceptions on errors (the job will be retried). + */ + public function execute() { + $accessrecords = access::get_records(); + $expires = new DateTime(); + $expires->sub(new DateInterval("P7D")); + $timestamp = $expires->getTimestamp(); + + $issuerid = get_config('repository_skydrive', 'issuerid'); + $issuer = \core\oauth2\api::get_issuer_by_id($issuerid); + + // Add the current user as an OAuth writer. + $systemauth = \core\oauth2\api::get_system_oauth_client($issuer); + + if ($systemauth === false) { + $details = 'Cannot connect as system user'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + $systemservice = new repository_skydrive\rest($systemauth); + + foreach ($accessrecords as $access) { + if ($access->get('timemodified') < $timestamp) { + $params = ['permissionid' => $access->get('permissionid'), 'itemid' => $access->get('itemid')]; + $systemservice->call('delete_permission', $params); + $access->delete(); + } + } + } + +} diff --git a/repository/skydrive/classes/rest.php b/repository/skydrive/classes/rest.php new file mode 100644 index 00000000000..5a35baa0a33 --- /dev/null +++ b/repository/skydrive/classes/rest.php @@ -0,0 +1,159 @@ +. + +/** + * Microsoft Graph API Rest Interface. + * + * @package repository_skydrive + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace repository_skydrive; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Microsoft Graph API Rest Interface. + * + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class rest extends \core\oauth2\rest { + + /** + * Define the functions of the rest API. + * + * @return array Example: + * [ 'listFiles' => [ 'method' => 'get', 'endpoint' => 'http://...', 'args' => [ 'folder' => PARAM_STRING ] ] ] + */ + public function get_api_functions() { + return [ + 'list' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/{parent}/children', + 'method' => 'get', + 'args' => [ + '$select' => PARAM_RAW, + '$expand' => PARAM_RAW, + 'parent' => PARAM_RAW, + '$skip' => PARAM_INT, + '$skipToken' => PARAM_RAW, + '$count' => PARAM_INT + ], + 'response' => 'json' + ], + 'search' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/{parent}/search(q=\'{search}\')', + 'method' => 'get', + 'args' => [ + 'search' => PARAM_NOTAGS, + '$select' => PARAM_RAW, + 'parent' => PARAM_RAW, + '$skip' => PARAM_INT, + '$skipToken' => PARAM_RAW, + '$count' => PARAM_INT + ], + 'response' => 'json' + ], + 'get' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}', + 'method' => 'get', + 'args' => [ + 'fileid' => PARAM_RAW, + '$select' => PARAM_RAW, + '$expand' => PARAM_RAW + ], + 'response' => 'json' + ], + 'list_permissions' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/permissions', + 'method' => 'get', + 'args' => [ + '$select' => PARAM_RAW, + '$expand' => PARAM_RAW, + 'fileid' => PARAM_RAW, + '$skip' => PARAM_INT, + '$skipToken' => PARAM_RAW, + '$count' => PARAM_INT + ], + 'response' => 'json' + ], + 'create_permission' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/invite', + 'method' => 'post', + 'args' => [ + 'fileid' => PARAM_RAW + ], + 'response' => 'json' + ], + 'get_file_by_path' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/root:/{fullpath}', + 'method' => 'get', + 'args' => [ + 'fullpath' => PARAM_RAW, + '$select' => PARAM_RAW + ], + 'response' => 'json' + ], + 'create_folder' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{parentid}/children', + 'method' => 'post', + 'args' => [ + 'parentid' => PARAM_RAW + ], + 'response' => 'json' + ], + 'create_link' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/createLink', + 'method' => 'post', + 'args' => [ + 'fileid' => PARAM_RAW + ], + 'response' => 'json' + ], + 'get_drive' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive', + 'method' => 'get', + 'args' => [], + 'response' => 'json' + ], + 'delete_file_by_path' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/root:/{fullpath}', + 'method' => 'delete', + 'args' => [ + 'fullpath' => PARAM_RAW, + ], + 'response' => 'json' + ], + 'copy_share' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/shares/{sharetoken}/root/copy', + 'method' => 'post', + 'args' => [ + 'sharetoken' => PARAM_RAW, + ], + 'response' => 'json' + ], + 'delete_permission' => [ + 'endpoint' => 'https://graph.microsoft.com/v1.0/me/drive/items/{fileid}/permissions/{permissionid}', + 'method' => 'delete', + 'args' => [ + 'fileid' => PARAM_RAW, + 'permissionid' => PARAM_RAW + ], + 'response' => 'json' + ], + ]; + } +} diff --git a/repository/skydrive/db/caches.php b/repository/skydrive/db/caches.php index 8b61e339ba6..1050c238de2 100644 --- a/repository/skydrive/db/caches.php +++ b/repository/skydrive/db/caches.php @@ -25,7 +25,15 @@ defined('MOODLE_INTERNAL') || die(); $definitions = array( - 'foldername' => array( - 'mode' => cache_store::MODE_SESSION, - ) + // Used to store file ids for folders. + // The keys used are full path to the folder, the values are the id in google drive. + // The static acceleration size has been based upon the depths of a single path. + 'folder' => array( + 'mode' => cache_store::MODE_APPLICATION, + 'simplekeys' => false, + 'simpledata' => true, + 'staticacceleration' => true, + 'staticaccelerationsize' => 10, + 'canuselocalstore' => true + ), ); diff --git a/repository/skydrive/db/install.xml b/repository/skydrive/db/install.xml new file mode 100644 index 00000000000..f61ae0e88df --- /dev/null +++ b/repository/skydrive/db/install.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + +
+
+
diff --git a/repository/skydrive/db/tasks.php b/repository/skydrive/db/tasks.php new file mode 100644 index 00000000000..b55c4ea3bb6 --- /dev/null +++ b/repository/skydrive/db/tasks.php @@ -0,0 +1,43 @@ +. + +/** + * Definition of repository_skydrive scheduled tasks. + * + * The handlers defined on this file are processed and registered into + * the Moodle DB after any install or upgrade operation. All plugins + * support this. + * + * @package repository_skydrive + * @copyright 2017 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/* List of handlers */ + +$tasks = array( + array( + 'classname' => 'repository_skydrive\remove_temp_access_task', + 'blocking' => 0, + 'minute' => 'R', + 'hour' => 'R', + 'day' => '*', + 'dayofweek' => 'R', + 'month' => '*' + ), +); diff --git a/repository/skydrive/db/upgrade.php b/repository/skydrive/db/upgrade.php new file mode 100644 index 00000000000..7a714846fcd --- /dev/null +++ b/repository/skydrive/db/upgrade.php @@ -0,0 +1,54 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +/** + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_repository_skydrive_upgrade($oldversion) { + global $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2017031400) { + + // Define table repository_skydrive_access to be created. + $table = new xmldb_table('repository_skydrive_access'); + + // Adding fields to table repository_skydrive_access. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('permissionid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('itemid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table repository_skydrive_access. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('usermodifiedkey', XMLDB_KEY_FOREIGN, array('usermodified'), 'user', array('id')); + + // Conditionally launch create table for repository_skydrive_access. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Skydrive savepoint reached. + upgrade_plugin_savepoint(true, 2017031400, 'repository', 'skydrive'); + } + return true; +} diff --git a/repository/skydrive/lang/en/repository_skydrive.php b/repository/skydrive/lang/en/repository_skydrive.php index 1ed71f48fc5..015647fa9d1 100644 --- a/repository/skydrive/lang/en/repository_skydrive.php +++ b/repository/skydrive/lang/en/repository_skydrive.php @@ -22,10 +22,20 @@ * @author Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['cachedef_foldername'] = 'Folder name cache'; -$string['clientid'] = 'Client ID'; -$string['configplugin'] = 'Configure Microsoft OneDrive'; -$string['oauthinfo'] = '

To use this plugin, you must register your site with Microsoft.

As part of the registration process, you will need to enter the following URL as \'Redirect domain\':

{$a->callbackurl}

Once registered, you will be provided with a client ID and secret which can be entered here.

'; +$string['configplugin'] = 'Configure OneDrive plugin'; +$string['skydrive:view'] = 'View OneDrive repository'; $string['pluginname'] = 'Microsoft OneDrive'; -$string['secret'] = 'Secret'; -$string['skydrive:view'] = 'View OneDrive'; +$string['issuer'] = 'OAuth 2 service'; +$string['issuer_help'] = 'Select the OAuth 2 service that is configured to talk to the OneDrive API. If the services does not exist yet, you might need to create it.'; +$string['servicenotenabled'] = 'Access not configured.'; +$string['oauth2serviceslink'] = 'OAuth 2 Services Configuration'; +$string['searchfor'] = 'Search for {$a}'; +$string['internal'] = 'Internal (files stored in Moodle)'; +$string['external'] = 'External (only links stored in Moodle)'; +$string['both'] = 'Internal and External'; +$string['supportedreturntypes'] = 'Supported files'; +$string['defaultreturntype'] = 'Default return type'; +$string['fileoptions'] = 'The types and defaults for returned files is configurable here. Note that all files linked externally will be updated so that the owner is the Moodle system account.'; +$string['owner'] = 'Owned by: {$a}'; +$string['cachedef_folder'] = 'OneDrive File IDs for folders in the system account'; + diff --git a/repository/skydrive/lib.php b/repository/skydrive/lib.php index 69c6316454d..41c87dc8f8e 100644 --- a/repository/skydrive/lib.php +++ b/repository/skydrive/lib.php @@ -25,8 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -require_once('microsoftliveapi.php'); - /** * Microsoft skydrive repository plugin. * @@ -36,46 +34,80 @@ require_once('microsoftliveapi.php'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_skydrive extends repository { - /** @var microsoft_skydrive skydrive oauth2 api helper object */ - private $skydrive = null; + /** + * OAuth 2 client + * @var \core\oauth2\client + */ + private $client = null; /** - * Constructor + * OAuth 2 Issuer + * @var \core\oauth2\issuer + */ + private $issuer = null; + + /** + * Additional scopes required for drive. + */ + const SCOPES = 'files.readwrite.all'; + + /** + * Constructor. * * @param int $repositoryid repository instance id. * @param int|stdClass $context a context id or context object. * @param array $options repository options. + * @param int $readonly indicate this repo is readonly or not. + * @return void */ - public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { - parent::__construct($repositoryid, $context, $options); + public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) { + parent::__construct($repositoryid, $context, $options, $readonly = 0); - $clientid = get_config('skydrive', 'clientid'); - $secret = get_config('skydrive', 'secret'); - $returnurl = new moodle_url('/repository/repository_callback.php'); - $returnurl->param('callback', 'yes'); - $returnurl->param('repo_id', $this->id); - $returnurl->param('sesskey', sesskey()); - - $this->skydrive = new microsoft_skydrive($clientid, $secret, $returnurl); - $this->check_login(); + $this->issuer = \core\oauth2\api::get_issuer(get_config('skydrive', 'issuerid')); } /** - * Checks whether the user is logged in or not. + * Get a cached user authenticated oauth client. * - * @return bool true when logged in + * @param moodle_url $overrideurl - Use this url instead of the repo callback. + * @return \core\oauth2\client + */ + protected function get_user_oauth_client($overrideurl = false) { + if ($this->client) { + return $this->client; + } + if ($overrideurl) { + $returnurl = $overrideurl; + } else { + $returnurl = new moodle_url('/repository/repository_callback.php'); + $returnurl->param('callback', 'yes'); + $returnurl->param('repo_id', $this->id); + $returnurl->param('sesskey', sesskey()); + } + + $this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, self::SCOPES); + + return $this->client; + } + + /** + * Checks whether the user is authenticate or not. + * + * @return bool true when logged in. */ public function check_login() { - return $this->skydrive->is_logged_in(); + $client = $this->get_user_oauth_client(); + return $client->is_logged_in(); } /** - * Print the login form, if required + * Print or return the login form. * - * @return array of login options + * @return void|array for ajax. */ public function print_login() { - $url = $this->skydrive->get_login_url(); + $client = $this->get_user_oauth_client(); + $url = $client->get_login_url(); if ($this->options['ajax']) { $popup = new stdClass(); @@ -88,124 +120,879 @@ class repository_skydrive extends repository { } /** - * Given a path, and perhaps a search, get a list of files. + * Build the breadcrumb from a path. * - * See details on {@link http://docs.moodle.org/dev/Repository_plugins} + * @param string $path to create a breadcrumb from. + * @return array containing name and path of each crumb. + */ + protected function build_breadcrumb($path) { + $bread = explode('/', $path); + $crumbtrail = ''; + foreach ($bread as $crumb) { + list($id, $name) = $this->explode_node_path($crumb); + $name = empty($name) ? $id : $name; + $breadcrumb[] = array( + 'name' => $name, + 'path' => $this->build_node_path($id, $name, $crumbtrail) + ); + $tmp = end($breadcrumb); + $crumbtrail = $tmp['path']; + } + return $breadcrumb; + } + + /** + * Generates a safe path to a node. * - * @param string $path identifier for current path - * @param string $page the page number of file list - * @return array list of files including meta information as specified by parent. + * Typically, a node will be id|Name of the node. + * + * @param string $id of the node. + * @param string $name of the node, will be URL encoded. + * @param string $root to append the node on, must be a result of this function. + * @return string path to the node. + */ + protected function build_node_path($id, $name = '', $root = '') { + $path = $id; + if (!empty($name)) { + $path .= '|' . urlencode($name); + } + if (!empty($root)) { + $path = trim($root, '/') . '/' . $path; + } + return $path; + } + + /** + * Returns information about a node in a path. + * + * @see self::build_node_path() + * @param string $node to extrat information from. + * @return array about the node. + */ + protected function explode_node_path($node) { + if (strpos($node, '|') !== false) { + list($id, $name) = explode('|', $node, 2); + $name = urldecode($name); + } else { + $id = $node; + $name = ''; + } + $id = urldecode($id); + return array( + 0 => $id, + 1 => $name, + 'id' => $id, + 'name' => $name + ); + } + + /** + * List the files and folders. + * + * @param string $path path to browse. + * @param string $page page to browse. + * @return array of result. */ public function get_listing($path='', $page = '') { - $ret = array(); - $ret['dynload'] = true; - $ret['nosearch'] = true; - $ret['manage'] = 'https://skydrive.live.com/'; - - $fileslist = $this->skydrive->get_file_list($path); - // Filter list for accepted types. Hopefully this will be done by core some day. - $fileslist = array_filter($fileslist, array($this, 'filter')); - $ret['list'] = $fileslist; - - // Generate path bar, always start with the plugin name. - $ret['path'] = array(); - $ret['path'][] = array('name'=> $this->name, 'path'=>''); - - // Now add each level folder. - $trail = ''; - if (!empty($path)) { - $parts = explode('/', $path); - foreach ($parts as $folderid) { - if (!empty($folderid)) { - $trail .= ('/'.$folderid); - $ret['path'][] = array('name' => $this->skydrive->get_folder_name($folderid), - 'path' => $trail); - } - } + if (empty($path)) { + $path = $this->build_node_path('root', get_string('pluginname', 'repository_skydrive')); } + // We analyse the path to extract what to browse. + $trail = explode('/', $path); + $uri = array_pop($trail); + list($id, $name) = $this->explode_node_path($uri); + + // Handle the special keyword 'search', which we defined in self::search() so that + // we could set up a breadcrumb in the search results. In any other case ID would be + // 'root' which is a special keyword, or a parent (folder) ID. + if ($id === 'search') { + $q = $name; + $id = 'root'; + + // Append the active path for search. + $str = get_string('searchfor', 'repository_skydrive', $searchtext); + $path = $this->build_node_path('search', $str, $path); + } + + // Query the Drive. + $parent = $id; + if ($parent != 'root') { + $parent = 'items/' . $parent; + } + $q = ''; + $results = $this->query($q, $path, $parent); + + $ret = []; + $ret['dynload'] = true; + $ret['path'] = $this->build_breadcrumb($path); + $ret['list'] = $results; + $ret['manage'] = 'https://www.office.com/'; return $ret; } /** - * Downloads a repository file and saves to a path. + * Search throughout the Google Drive. * - * @param string $id identifier of file - * @param string $filename to save file as - * @return array with keys: - * path: internal location of the file - * url: URL to the source + * @param string $searchtext text to search for. + * @param int $page search page. + * @return array of results. */ - public function get_file($id, $filename = '') { - $path = $this->prepare_file($filename); - return $this->skydrive->download_file($id, $path); + public function search($searchtext, $page = 0) { + $path = $this->build_node_path('root', get_string('pluginname', 'repository_skydrive')); + $str = get_string('searchfor', 'repository_skydrive', $searchtext); + $path = $this->build_node_path('search', $str, $path); + + // Query the Drive. + $parent = 'root'; + $results = $this->query($searchtext, $path, 'root'); + + $ret = []; + $ret['dynload'] = true; + $ret['path'] = $this->build_breadcrumb($path); + $ret['list'] = $results; + $ret['manage'] = 'https://www.office.com/'; + return $ret; } /** - * Return names of the options to display in the repository form + * Query Google Drive for files and folders using a search query. * - * @return array of option names + * Documentation about the query format can be found here: + * https://developers.google.com/drive/search-parameters + * + * This returns a list of files and folders with their details as they should be + * formatted and returned by functions such as get_listing() or search(). + * + * @param string $q search query as expected by the Google API. + * @param string $path parent path of the current files, will not be used for the query. + * @param int $page page. + * @return array of files and folders. */ - public static function get_type_option_names() { - return array('clientid', 'secret', 'pluginname'); + protected function query($q, $path = null, $parent = null, $page = 0) { + global $OUTPUT; + + $files = []; + $folders = []; + $fields = "folder,id,lastModifiedDateTime,name,size,webUrl,thumbnails"; + $params = ['$select' => $fields, '$expand' => 'thumbnails', 'parent' => $parent]; + + try { + // Retrieving files and folders. + $client = $this->get_user_oauth_client(); + $service = new repository_skydrive\rest($client); + + if (!empty($q)) { + $params['search'] = urlencode($q); + + // MS does not return thumbnails on a search. + unset($params['$expand']); + $response = $service->call('search', $params); + } else { + $response = $service->call('list', $params); + } + } catch (Exception $e) { + if ($e->getCode() == 403 && strpos($e->getMessage(), 'Access Not Configured') !== false) { + throw new repository_exception('servicenotenabled', 'repository_skydrive'); + } else { + throw $e; + } + } + + $remotefiles = isset($response->value) ? $response->value : []; + foreach ($remotefiles as $remotefile) { + if (!empty($remotefile->folder)) { + // This is a folder. + $folders[$remotefile->id] = [ + 'title' => $remotefile->name, + 'path' => $this->build_node_path($remotefile->id, $remotefile->name, $path), + 'date' => strtotime($remotefile->lastModifiedDateTime), + 'thumbnail' => $OUTPUT->pix_url(file_folder_icon(64))->out(false), + 'thumbnail_height' => 64, + 'thumbnail_width' => 64, + 'children' => [] + ]; + } else { + // We can download all other file types. + $title = $remotefile->name; + $source = json_encode([ + 'id' => $remotefile->id, + 'name' => $remotefile->name, + 'link' => $remotefile->webUrl + ]); + + // Adds the file to the file list. Using the itemId along with the name as key + // of the array because Google Drive allows files with identical names. + $thumb = ''; + $thumbwidth = 0; + $thumbheight = 0; + $extendedinfoerr = false; + + if (empty($remotefile->thumbnails)) { + // Try and get it directly from the item. + $params = ['fileid' => $remotefile->id, '$select' => $fields, '$expand' => 'thumbnails']; + try { + $response = $service->call('get', $params); + $remotefile = $response; + } catch (Exception $e) { + // This is not a failure condition - we just could not get extended info about the file. + $extendedinfoerr = true; + } + } + + if (!empty($remotefile->thumbnails)) { + $thumbs = $remotefile->thumbnails; + if (count($thumbs)) { + $first = reset($thumbs); + if (!empty($first->medium) && !empty($first->medium->url)) { + $thumb = $first->medium->url; + $thumbwidth = min($first->medium->width, 64); + $thumbheight = min($first->medium->height, 64); + } + } + } + + $files[$remotefile->id] = [ + 'title' => $title, + 'source' => $source, + 'date' => strtotime($remotefile->lastModifiedDateTime), + 'size' => isset($remotefile->size) ? $remotefile->size : null, + 'thumbnail' => $thumb, + 'thumbnail_height' => $thumbwidth, + 'thumbnail_width' => $thumbheight, + ]; + } + } + + // Filter and order the results. + $files = array_filter($files, [$this, 'filter']); + core_collator::ksort($files, core_collator::SORT_NATURAL); + core_collator::ksort($folders, core_collator::SORT_NATURAL); + return array_merge(array_values($folders), array_values($files)); } /** - * Setup repistory form. + * Logout. * - * @param moodleform $mform Moodle form (passed by reference) - * @param string $classname repository class name - */ - public static function type_config_form($mform, $classname = 'repository') { - $a = new stdClass; - $a->callbackurl = microsoft_skydrive::callback_url()->out(false); - $mform->addElement('static', null, '', get_string('oauthinfo', 'repository_skydrive', $a)); - - parent::type_config_form($mform); - $strrequired = get_string('required'); - $mform->addElement('text', 'clientid', get_string('clientid', 'repository_skydrive')); - $mform->addElement('text', 'secret', get_string('secret', 'repository_skydrive')); - $mform->addRule('clientid', $strrequired, 'required', null, 'client'); - $mform->addRule('secret', $strrequired, 'required', null, 'client'); - $mform->setType('clientid', PARAM_RAW_TRIMMED); - $mform->setType('secret', PARAM_RAW_TRIMMED); - } - - /** - * Logout from repository instance and return - * login form. - * - * @return page to display + * @return string */ public function logout() { - $this->skydrive->log_out(); - return $this->print_login(); + $client = $this->get_user_oauth_client(); + $client->log_out(); + return parent::logout(); } /** - * This repository doesn't support global search. + * Get a file. * - * @return bool if supports global search + * @param string $reference reference of the file. + * @param string $file name to save the file to. + * @return string JSON encoded array of information about the file. */ - public function global_search() { - return false; + public function get_file($reference, $filename = '') { + global $CFG; + + $client = $this->get_user_oauth_client(); + $base = 'https://graph.microsoft.com/v1.0/'; + + $sourceinfo = json_decode($reference); + $sourceurl = new moodle_url($base . 'me/drive/items/' . $sourceinfo->id . '/content'); + $source = $sourceurl->out(false); + + // We use download_one and not the rest API because it has special timeouts etc. + $path = $this->prepare_file($filename); + $options = ['filepath' => $path, 'timeout' => 15, 'followlocation' => true, 'maxredirs' => 5]; + $result = $client->download_one($source, null, $options); + + if ($result) { + @chmod($path, $CFG->filepermissions); + return array( + 'path' => $path, + 'url' => $reference + ); + } + throw new repository_exception('cannotdownload', 'repository'); } /** - * This repoistory supports any filetype. + * Prepare file reference information. * - * @return string '*' means this repository support any files + * We are using this method to clean up the source to make sure that it + * is a valid source. + * + * @param string $source of the file. + * @return string file reference. + */ + public function get_file_reference($source) { + // We could do some magic upgrade code here. + return $source; + } + + /** + * What kind of files will be in this repository? + * + * @return array return '*' means this repository support any files, otherwise + * return mimetypes of files, it can be an array */ public function supported_filetypes() { return '*'; } /** - * This repostiory only supports internal files + * Tells how the file can be picked from this repository. * - * @return int return type bitmask supported + * @return int */ public function supported_returntypes() { - return FILE_INTERNAL; + // We can only support references if the system account is connected. + if (!empty($this->issuer) && $this->issuer->is_system_account_connected()) { + $setting = get_config('skydrive', 'supportedreturntypes'); + if ($setting == 'internal') { + return FILE_INTERNAL; + } else if ($setting == 'external') { + return FILE_CONTROLLED_LINK; + } else { + return FILE_CONTROLLED_LINK | FILE_INTERNAL; + } + } else { + return FILE_INTERNAL; + } + } + + /** + * Which return type should be selected by default. + * + * @return int + */ + public function default_returntype() { + $setting = get_config('skydrive', 'defaultreturntype'); + $supported = get_config('skydrive', 'supportedreturntypes'); + if (($setting == FILE_INTERNAL && $supported != 'external') || $supported == 'internal') { + return FILE_INTERNAL; + } else { + return FILE_CONTROLLED_LINK; + } + } + + /** + * Return names of the general options. + * By default: no general option name. + * + * @return array + */ + public static function get_type_option_names() { + return array('issuerid', 'pluginname', 'defaultreturntype', 'supportedreturntypes'); + } + + /** + * Store the access token. + */ + public function callback() { + $client = $this->get_user_oauth_client(); + // This will upgrade to an access token if we have an authorization code and save the access token in the session. + $client->is_logged_in(); + } + + /** + * Repository method to serve the referenced file + * + * @see send_stored_file + * + * @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) { + $source = json_decode($storedfile->get_reference()); + + $fb = get_file_browser(); + $context = context::instance_by_id($storedfile->get_contextid(), MUST_EXIST); + $info = $fb->get_file_info($context, + $storedfile->get_component(), + $storedfile->get_filearea(), + $storedfile->get_itemid(), + $storedfile->get_filepath(), + $storedfile->get_filename()); + + if ($info->is_writable()) { + // Add the current user as an OAuth writer. + $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer); + + if ($systemauth === false) { + $details = 'Cannot connect as system user'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + $systemservice = new repository_skydrive\rest($systemauth); + + // Get the user oauth so we can get the account to add. + $url = moodle_url::make_pluginfile_url($storedfile->get_contextid(), + $storedfile->get_component(), + $storedfile->get_filearea(), + $storedfile->get_itemid(), + $storedfile->get_filepath(), + $storedfile->get_filename(), + $forcedownload); + $url->param('sesskey', sesskey()); + $userauth = $this->get_user_oauth_client($url); + if (!$userauth->is_logged_in()) { + redirect($userauth->get_login_url()); + } + if ($userauth === false) { + $details = 'Cannot connect as current user'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + $userinfo = $userauth->get_userinfo(); + $useremail = $userinfo['email']; + + $this->add_temp_writer_to_file($systemservice, $source->id, $useremail); + } + + if ($source->link) { + redirect($source->link); + } else { + $details = 'File is missing source link'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + } + + /** + * List the permissions on a file. + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fileid The id of the file. + * @return array + */ + protected function list_file_permissions(\repository_skydrive\rest $client, $fileid) { + $fields = "id,roles,link,grantedTo"; + return $client->call('list_permissions', ['fileid' => $fileid, '$select' => $fields]); + } + + /** + * See if a folder exists within a folder + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fullpath + * @return string|boolean The file id if it exists or false. + */ + protected function get_file_id_by_path(\repository_skydrive\rest $client, $fullpath) { + $fields = "id"; + try { + $response = $client->call('get_file_by_path', ['fullpath' => $fullpath, '$select' => $fields]); + } catch (\core\oauth2\rest_exception $re) { + return false; + } + return $response->id; + } + + /** + * Delete a file by full path. + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fullpath + * @return boolean + */ + protected function delete_file_by_path(\repository_skydrive\rest $client, $fullpath) { + try { + $response = $client->call('delete_file_by_path', ['fullpath' => $fullpath]); + } catch (\core\oauth2\rest_exception $re) { + return false; + } + return true; + } + + + /** + * Get a file summary by full path. + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fullpath + * @return stdClass + */ + protected function get_file_summary_by_path(\repository_skydrive\rest $client, $fullpath) { + $fields = "folder,id,lastModifiedDateTime,name,size,webUrl,createdByUser"; + $response = $client->call('get_file_by_path', ['fullpath' => $fullpath, '$select' => $fields]); + if (empty($response->id)) { + $details = 'Cannot get file summary:' . $fullpath; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return $response; + } + + /** + * Create a folder within a folder + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $foldername The folder we are creating. + * @param string $parentid The parent folder we are creating in. + * + * @return string The file id of the new folder. + */ + protected function create_folder_in_folder(\repository_skydrive\rest $client, $foldername, $parentid) { + $params = ['parentid' => $parentid]; + $folder = [ 'name' => $foldername, 'folder' => [ 'childCount' => 0 ]]; + $created = $client->call('create_folder', $params, json_encode($folder)); + if (empty($created->id)) { + $details = 'Cannot create folder:' . $foldername; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return $created->id; + } + + /** + * Get simple file info for humans. + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fileid The file we are querying. + * + * @return stdClass + */ + protected function get_file_summary(\repository_skydrive\rest $client, $fileid) { + $fields = "folder,id,lastModifiedDateTime,name,size,webUrl,createdByUser"; + $response = $client->call('get', ['fileid' => $fileid, '$select' => $fields]); + return $response; + } + + /** + * Get the id of this users root drive. + * + * @param \repository_skydrive\rest $client Authenticated client. + * + * @return string id + */ + protected function get_root_drive_id(\repository_skydrive\rest $client) { + $response = $client->call('get_drive', []); + + if (empty($response->id)) { + $details = 'Cannot get driveid'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return $response->id; + } + + /** + * Add a writer to the permissions on the file (temporary). + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fileid The file we are updating. + * @param string $email The email of the writer account to add. + * @return boolean + */ + protected function add_temp_writer_to_file(\repository_skydrive\rest $client, $fileid, $email) { + // Expires in 7 days. + $expires = new DateTime(); + $expires->add(new DateInterval("P7D")); + + $updateeditor = [ + 'recipients' => [[ 'email' => $email ]], + 'roles' => ['write'], + 'requireSignIn' => true, + 'sendInvitation' => false + ]; + $params = ['fileid' => $fileid]; + $response = $client->call('create_permission', $params, json_encode($updateeditor)); + if (empty($response->value[0]->id)) { + $details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + // Store the permission id in the DB. Scheduled task will remove this permission after 7 days. + if ($access = repository_skydrive\access::get_record(['permissionid' => $response->value[0]->id, 'itemid' => $fileid ])) { + // Update the timemodified. + $access->update(); + } else { + $record = (object) [ 'permissionid' => $response->value[0]->id, 'itemid' => $fileid ]; + $access = new repository_skydrive\access(0, $record); + $access->create(); + } + return true; + } + + /** + * Add a writer to the permissions on the file. + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fileid The file we are updating. + * @param string $userid The userid of the writer account to add. + * @return boolean + */ + protected function add_writer_to_file(\repository_skydrive\rest $client, $fileid, $useremail) { + $updateeditor = [ + 'recipients' => [ [ 'email' => $useremail ] ], + 'roles' => ['write'], + 'requireSignIn' => true, + 'sendInvitation' => false + ]; + $params = [ 'fileid' => $fileid ]; + $response = $client->call('create_permission', $params, json_encode($updateeditor)); + if (empty($response->value)) { + $details = 'Cannot add user ' . $useremail . ' as a writer for document: ' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return true; + } + + /** + * Allow anyone with the link to read the file. + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $fileid The file we are updating. + * @return boolean + */ + protected function set_file_sharing_anyone_with_link_can_read(\repository_skydrive\rest $client, $fileid) { + $updateread = [ + 'type' => 'view', + 'scope' => 'anonymous' + ]; + $params = ['fileid' => $fileid]; + $response = $client->call('create_link', $params, json_encode($updateread)); + if (empty($response->link)) { + $details = 'Cannot update link sharing for the document: ' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return true; + } + + /** + * Get share info. + * + * @param \repository_skydrive\rest $client Authenticated client. + * @param string $sharetoken The share we are querying. + * @return stdClass + */ + protected function copy_share(\repository_skydrive\rest $client, $sharetoken, $newdrive, $parentid) { + $folder = [ + 'parentReference' => ['id' => $parentid, 'driveId' => $newdrive] + ]; + $params = ['sharetoken' => $sharetoken]; + $response = $client->call('copy_share', $params, json_encode($folder)); + return true; + } + + /** + * From MS docs - to get a share token from a url, do this: + * Reference: https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/shares_get + * To access a sharing URL using the shares API, the URL needs to be transformed into a sharing token. + * To transform a URL into a sharing token: + * Base64 encode the sharing URL. + * Convert the base64 encoded data to unpadded base64url format by: + * Trim trailing = characeters from the string. + * Replace unsafe URL characters with an equivelent character; replace / with _ and + with -. + * Append u! to the beginning of the string. + * + * @param string sharingUrl + * @return string sharingtoken + */ + protected function get_share_token($shareurl) { + return 'u!' . str_replace(['/', '+'], ['_', '-'], rtrim(base64_encode($shareurl), '=')); + } + + /** + * Called when a file is selected as a "link". + * Invoked at MOODLE/repository/repository_ajax.php + * + * @param string $reference this reference is generated by + * repository::get_file_reference() + * @param context $context the target context for this new file. + * @param string $component the target component for this new file. + * @param string $filearea the target filearea for this new file. + * @param string $itemid the target itemid for this new file. + * @return string $modifiedreference (final one before saving to DB) + */ + public function reference_file_selected($reference, $context, $component, $filearea, $itemid) { + // What we need to do here is transfer ownership to the system user (or copy) + // then set the permissions so anyone with the share link can view, + // finally update the reference to contain the share link if it was not + // already there (and point to new file id if we copied). + var_dump($reference); + $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer); + + if ($systemauth === false) { + $details = 'Cannot connect as system user'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + $systemuserinfo = $systemauth->get_userinfo(); + $systemuseremail = $systemuserinfo['email']; + + $source = json_decode($reference); + + $userauth = $this->get_user_oauth_client(); + if ($userauth === false) { + $details = 'Cannot connect as current user'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + $userinfo = $userauth->get_userinfo(); + $useremail = $userinfo['email']; + + $userservice = new repository_skydrive\rest($userauth); + $systemservice = new repository_skydrive\rest($systemauth); + + // Get the list of existing permissions so we can see if the owner is already the system account, + // and whether we need to update the link sharing options. + $permissions = $this->list_file_permissions($userservice, $source->id); + + $readshareupdaterequired = true; + $ownerupdaterequired = true; + foreach ($permissions->value as $permission) { + if (!empty($permission->link)) { + if ($permission->link->scope == 'anonymous' && + $permission->link->type == 'view') { + $shareurl = $permission->link->webUrl; + $readshareupdaterequired = false; + break; + } + } + } + + // Add Moodle as writer. + $this->add_writer_to_file($userservice, $source->id, $systemuseremail); + + // Now copy it to a sensible folder. + $contextlist = array_reverse($context->get_parent_contexts(true)); + + $cache = cache::make('repository_skydrive', 'folder'); + $parentid = 'root'; + $fullpath = ''; + $allfolders = []; + foreach ($contextlist as $context) { + // Make sure a folder exists here. + $foldername = urlencode(clean_param($context->get_context_name(), PARAM_PATH)); + $allfolders[] = $foldername; + } + + $allfolders[] = urlencode(clean_param($component, PARAM_PATH)); + $allfolders[] = urlencode(clean_param($filearea, PARAM_PATH)); + $allfolders[] = urlencode(clean_param($itemid, PARAM_PATH)); + + foreach ($allfolders as $foldername) { + if ($fullpath) { + $fullpath .= '/'; + } + $fullpath .= $foldername; + + $folderid = $cache->get($fullpath); + if (empty($folderid)) { + $folderid = $this->get_file_id_by_path($systemservice, $fullpath); + } + if ($folderid !== false) { + $cache->set($fullpath, $folderid); + $parentid = $folderid; + } else { + // Create it. + $parentid = $this->create_folder_in_folder($systemservice, $foldername, $parentid); + $cache->set($fullpath, $parentid); + } + } + + // Get the users drive id. + $newdrive = $this->get_root_drive_id($systemservice); + + if ($readshareupdaterequired) { + $response = $this->set_file_sharing_anyone_with_link_can_read($userservice, $source->id); + $shareurl = $response->value->webUrl; + } + + // Turn the share url into a sharing token. + $sharetoken = $this->get_share_token($shareurl); + + // Delete any existing file at this path. + $path = $fullpath . '/' . $source->name; + $this->delete_file_by_path($systemservice, $path); + + // Copy the file so we have a backup. + $this->copy_share($systemservice, $sharetoken, $newdrive, $parentid); + + $summary = $this->get_file_summary_by_path($systemservice, $path); + + // Update the details in the file reference before it is saved. + $source->id = $summary->id; + $source->link = $summary->webUrl; + + $reference = json_encode($source); + + return $reference; + } + + /** + * Get human readable file info from the reference. + * + * @param string $reference + * @param int $filestatus + */ + public function get_reference_details($reference, $filestatus = 0) { + if (empty($reference)) { + return get_string('unknownsource', 'repository'); + } + $source = json_decode($reference); + $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer); + + if ($systemauth === false) { + return ''; + } + $systemservice = new repository_skydrive\rest($systemauth); + $info = $this->get_file_summary($systemservice, $source->id); + + $owner = ''; + if (!empty($info->createdByUser->displayName)) { + $owner = $info->createdByUser->displayName; + } + if ($owner) { + return get_string('owner', 'repository_skydrive', $owner); + } else { + return $info->name; + } + } + + /** + * Edit/Create Admin Settings Moodle form. + * + * @param moodleform $mform Moodle form (passed by reference). + * @param string $classname repository class name. + */ + public static function type_config_form($mform, $classname = 'repository') { + $url = new moodle_url('/admin/tool/oauth2/issuers.php'); + $url = $url->out(); + + $mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_skydrive', $url)); + + 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', 'issuerid', get_string('issuer', 'repository_skydrive'), $options); + $mform->addHelpButton('issuerid', 'issuer', 'repository_skydrive'); + $mform->addRule('issuerid', $strrequired, 'required', null, 'client'); + + $mform->addElement('static', null, '', get_string('fileoptions', 'repository_skydrive')); + $choices = [ + 'internal' => get_string('internal', 'repository_skydrive'), + 'external' => get_string('external', 'repository_skydrive'), + 'both' => get_string('both', 'repository_skydrive') + ]; + $mform->addElement('select', 'supportedreturntypes', get_string('supportedreturntypes', 'repository_skydrive'), $choices); + + $choices = [ + FILE_INTERNAL => get_string('internal', 'repository_skydrive'), + FILE_CONTROLLED_LINK => get_string('external', 'repository_skydrive'), + ]; + $mform->addElement('select', 'defaultreturntype', get_string('defaultreturntype', 'repository_skydrive'), $choices); } } + +/** + * Callback to get the required scopes for system account. + * + * @return string + */ +function repository_skydrive_oauth2_system_scopes(\core\oauth2\issuer $issuer) { + if ($issuer->get('id') == get_config('skydrive', 'issuerid')) { + return repository_skydrive::SCOPES; + } + return ''; +} diff --git a/repository/skydrive/microsoftliveapi.php b/repository/skydrive/microsoftliveapi.php deleted file mode 100644 index 5fc52274eae..00000000000 --- a/repository/skydrive/microsoftliveapi.php +++ /dev/null @@ -1,245 +0,0 @@ -. - -/** - * Functions for operating with the skydrive API - * - * @package repository_skydrive - * @copyright 2012 Lancaster University Network Services Ltd - * @author Dan Poltawski - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir.'/oauthlib.php'); - -/** - * A helper class to access microsoft live resources using the api. - * - * This uses the microsfot API defined in - * http://msdn.microsoft.com/en-us/library/hh243648.aspx - * - * @package repository_skydrive - * @copyright 2012 Lancaster University Network Services Ltd - * @author Dan Poltawski - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class microsoft_skydrive extends oauth2_client { - /** @var string OAuth 2.0 scope */ - const SCOPE = 'wl.skydrive'; - /** @var string Base url to access API */ - const API = 'https://apis.live.net/v5.0'; - /** @var cache_session cache of foldernames */ - var $foldernamecache = null; - - /** - * Construct a skydrive request object - * - * @param string $clientid client id for OAuth 2.0 provided by microsoft - * @param string $clientsecret secret for OAuth 2.0 provided by microsoft - * @param moodle_url $returnurl url to return to after succseful auth - */ - public function __construct($clientid, $clientsecret, $returnurl) { - parent::__construct($clientid, $clientsecret, $returnurl, self::SCOPE); - // Make a session cache - $this->foldernamecache = cache::make('repository_skydrive', 'foldername'); - } - - /** - * Returns the auth url for OAuth 2.0 request - * @return string the auth url - */ - protected function auth_url() { - return 'https://login.live.com/oauth20_authorize.srf'; - } - - /** - * Returns the token url for OAuth 2.0 request - * @return string the auth url - */ - protected function token_url() { - return 'https://login.live.com/oauth20_token.srf'; - } - - /** - * Post request. - * - * Overridden to convert the data to a string, else curl will set the wrong headers. - * - * @param string $url The URL. - * @param array|string $params The parameters. - * @param array $options The options. - * @return bool - */ - public function post($url, $params = '', $options = array()) { - return parent::post($url, format_postdata_for_curlcall($params), $options); - } - - /** - * Downloads a file to a file from skydrive using authenticated request - * - * @param string $id id of file - * @param string $path path to save file to - * @return array stucture for repository download_file - */ - public function download_file($id, $path) { - $url = self::API."/${id}/content"; - // Microsoft live redirects to the real download location.. - $this->setopt(array('CURLOPT_FOLLOWLOCATION' => true, 'CURLOPT_MAXREDIRS' => 3)); - $content = $this->get($url); - file_put_contents($path, $content); - return array('path'=>$path, 'url'=>$url); - } - - /** - * Returns a folder name property for a given folderid. - * - * @param string $folderid the folder id which is passed - * @return mixed folder name or false in case of error - */ - public function get_folder_name($folderid) { - if (empty($folderid)) { - throw new coding_exception('Empty folderid passed to get_folder_name'); - } - - // Cache based on oauthtoken and folderid. - $cachekey = $this->folder_cache_key($folderid); - - if ($foldername = $this->foldernamecache->get($cachekey)) { - return $foldername; - } - - $url = self::API."/{$folderid}"; - $ret = json_decode($this->get($url)); - if (isset($ret->error)) { - $this->log_out(); - return false; - } - - $this->foldernamecache->set($cachekey, $ret->name); - return $ret->name; - } - - /** - * Returns a list of files the user has formated for files api - * - * @param string $path the path which we are in - * @return mixed Array of files formated for fileapoi - */ - public function get_file_list($path = '') { - global $OUTPUT; - - $precedingpath = ''; - if (empty($path)) { - $url = self::API."/me/skydrive/files/"; - } else { - $parts = explode('/', $path); - $currentfolder = array_pop($parts); - $url = self::API."/{$currentfolder}/files/"; - } - - $ret = json_decode($this->get($url)); - - if (isset($ret->error)) { - $this->log_out(); - return false; - } - - $files = array(); - - foreach ($ret->data as $file) { - switch($file->type) { - case 'folder': - case 'album': - // Cache the foldername for future requests. - $cachekey = $this->folder_cache_key($file->id); - $this->foldernamecache->set($cachekey, $file->name); - - $files[] = array( - 'title' => $file->name, - 'path' => $path.'/'.$file->id, - 'size' => 0, - 'date' => strtotime($file->updated_time), - 'thumbnail' => $OUTPUT->image_url(file_folder_icon(90))->out(false), - 'children' => array(), - ); - break; - case 'photo': - $files[] = array( - 'title' => $file->name, - 'size' => $file->size, - 'date' => strtotime($file->updated_time), - 'thumbnail' => $OUTPUT->image_url(file_extension_icon($file->name, 90))->out(false), - 'realthumbnail' => $file->picture, - 'source' => $file->id, - 'url' => $file->link, - 'image_height' => $file->height, - 'image_width' => $file->width, - 'author' => $file->from->name, - ); - break; - case 'video': - $files[] = array( - 'title' => $file->name, - 'size' => $file->size, - 'date' => strtotime($file->updated_time), - 'thumbnail' => $OUTPUT->image_url(file_extension_icon($file->name, 90))->out(false), - 'realthumbnail' => $file->picture, - 'source' => $file->id, - 'url' => $file->link, - 'author' => $file->from->name, - ); - break; - case 'audio': - $files[] = array( - 'title' => $file->name, - 'size' => $file->size, - 'date' => strtotime($file->updated_time), - 'thumbnail' => $OUTPUT->image_url(file_extension_icon($file->name, 90))->out(false), - 'source' => $file->id, - 'url' => $file->link, - 'author' => $file->from->name, - ); - break; - case 'file': - $files[] = array( - 'title' => $file->name, - 'size' => $file->size, - 'date' => strtotime($file->updated_time), - 'thumbnail' => $OUTPUT->image_url(file_extension_icon($file->name, 90))->out(false), - 'source' => $file->id, - 'url' => $file->link, - 'author' => $file->from->name, - ); - break; - } - } - return $files; - } - - /** - * Returns a key for foldernane cache - * - * @param string $folderid the folder id which is to be cached - * @return string the cache key to use - */ - private function folder_cache_key($folderid) { - // Cache based on oauthtoken and folderid. - return $this->get_tokenname().'_'.$folderid; - } -} diff --git a/repository/skydrive/version.php b/repository/skydrive/version.php index 8afca1d1f90..a3995847071 100644 --- a/repository/skydrive/version.php +++ b/repository/skydrive/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016120500; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2017031400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2016112900; // Requires this Moodle version. $plugin->component = 'repository_skydrive'; // Full name of the plugin (used for diagnostics).