diff --git a/lib/classes/oauth2/rest.php b/lib/classes/oauth2/rest.php index 03afc9fb4f4..708ad35ac5d 100644 --- a/lib/classes/oauth2/rest.php +++ b/lib/classes/oauth2/rest.php @@ -64,7 +64,7 @@ abstract class rest { * @param string $functionname * @param array $functionargs */ - public function call($functionname, $functionargs) { + public function call($functionname, $functionargs, $rawpost = false) { $functions = $this->get_api_functions(); $supportedmethods = [ 'get', 'put', 'post', 'patch', 'head', 'delete' ]; if (empty($functions[$functionname])) { @@ -86,7 +86,30 @@ abstract class rest { } } + // Allow params in the URL path like /me/{parent}/children. + foreach ($callargs as $argname => $value) { + $newendpoint = str_replace('{' . $argname . '}', $value, $endpoint); + if ($newendpoint != $endpoint) { + $endpoint = $newendpoint; + unset($callargs[$argname]); + } + } + + if ($rawpost !== false) { + $queryparams = $this->curl->build_post_data($callargs); + if (!empty($queryparams)) { + $endpoint .= '?' . $queryparams; + } + $callargs = $rawpost; + } + + error_log('CALL REST'); + error_log($endpoint); + error_log(json_encode($callargs)); + error_log($method); + $this->curl->setHeader('Content-type: application/json'); $response = $this->curl->$method($endpoint, $callargs); + error_log($response); if ($this->curl->errno == 0) { if ($responsetype == 'json') { diff --git a/lib/filelib.php b/lib/filelib.php index 57782bf6782..8d1d3cf818b 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3458,6 +3458,34 @@ class curl { return $this->request($url, $options); } + /** + * HTTP PATCH method + * + * @param string $url + * @param array|string $params + * @param array $options + * @return bool + */ + public function patch($url, $params = '', $options = array()) { + $options['CURLOPT_CUSTOMREQUEST'] = 'PATCH'; + if (is_array($params)) { + $this->_tmp_file_post_params = array(); + foreach ($params as $key => $value) { + if ($value instanceof stored_file) { + $value->add_to_curl_request($this, $key); + } else { + $this->_tmp_file_post_params[$key] = $value; + } + } + $options['CURLOPT_POSTFIELDS'] = $this->_tmp_file_post_params; + unset($this->_tmp_file_post_params); + } else { + // $params is the raw post data + $options['CURLOPT_POSTFIELDS'] = $params; + } + return $this->request($url, $options); + } + /** * HTTP POST method * diff --git a/repository/filepicker.js b/repository/filepicker.js index 386a6abcd17..3a2ff4edd89 100644 --- a/repository/filepicker.js +++ b/repository/filepicker.js @@ -29,6 +29,7 @@ * Active repository options * ===== * this.active_repo.id + * this.active_repo.defaultreturntype * this.active_repo.nosearch * this.active_repo.norefresh * this.active_repo.nologin @@ -1094,6 +1095,12 @@ M.core_filepicker.init = function(Y, options) { firstfilelink = (firstfilelink==null && allowed) ? filelinktypes[i] : firstfilelink; filelinkcount += allowed ? 1 : 0; } + var defaultreturntype = this.options.repositories[this.active_repo.id].defaultreturntype; + if (defaultreturntype) { + if (filelink[defaultreturntype]) { + firstfilelink = defaultreturntype; + } + } // make radio buttons enabled if this file-link-type is available and only if there are more than one file-link-type option // check the first available file-link-type option for (var linktype in filelink) { @@ -1415,6 +1422,7 @@ M.core_filepicker.init = function(Y, options) { this.objecttag = data.object?data.object:null; this.active_repo = {}; this.active_repo.issearchresult = data.issearchresult ? true : false; + this.active_repo.defaultreturntype = data.defaultreturntype?data.defaultreturntype:null; this.active_repo.dynload = data.dynload?data.dynload:false; this.active_repo.pages = Number(data.pages?data.pages:null); this.active_repo.page = Number(data.page?data.page:null); diff --git a/repository/googledocs/classes/rest.php b/repository/googledocs/classes/rest.php index f66aa8bd884..38028ba3434 100644 --- a/repository/googledocs/classes/rest.php +++ b/repository/googledocs/classes/rest.php @@ -54,7 +54,76 @@ class rest extends \core\oauth2\rest { 'spaces' => PARAM_RAW ], 'response' => 'json' - ] + ], + 'get' => [ + 'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}', + 'method' => 'get', + 'args' => [ + 'fields' => PARAM_RAW, + 'fileid' => PARAM_RAW + ], + 'response' => 'json' + ], + 'copy' => [ + 'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}/copy', + 'method' => 'post', + 'args' => [ + 'fields' => PARAM_RAW, + 'fileid' => PARAM_RAW + ], + 'response' => 'json' + ], + 'create' => [ + 'endpoint' => 'https://www.googleapis.com/drive/v3/files', + 'method' => 'post', + 'args' => [ + 'fields' => PARAM_RAW + ], + 'response' => 'json' + ], + 'update' => [ + 'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}', + 'method' => 'patch', + 'args' => [ + 'fileid' => PARAM_RAW, + 'fields' => PARAM_RAW, + 'addParents' => PARAM_RAW, + 'removeParents' => PARAM_RAW + ], + 'response' => 'json' + ], + 'create_permission' => [ + 'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}/permissions', + 'method' => 'post', + 'args' => [ + 'fileid' => PARAM_RAW, + 'emailMessage' => PARAM_RAW, + 'sendNotificationEmail' => PARAM_RAW, + 'transferOwnership' => PARAM_RAW, + ], + 'response' => 'json' + ], + 'update_permission' => [ + 'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}/permissions/{permissionid}', + 'method' => 'patch', + 'args' => [ + 'fileid' => PARAM_RAW, + 'permissionid' => PARAM_RAW, + 'emailMessage' => PARAM_RAW, + 'sendNotificationEmail' => PARAM_RAW, + 'transferOwnership' => PARAM_RAW, + ], + 'response' => 'json' + ], + 'list_permissions' => [ + 'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}/permissions', + 'method' => 'get', + 'args' => [ + 'fileid' => PARAM_RAW, + 'fields' => PARAM_RAW + ], + 'response' => 'json' + ], ]; } } diff --git a/repository/googledocs/db/upgrade.php b/repository/googledocs/db/upgrade.php index 2a5698ef3c1..9d4643230a1 100644 --- a/repository/googledocs/db/upgrade.php +++ b/repository/googledocs/db/upgrade.php @@ -63,6 +63,9 @@ function xmldb_repository_googledocs_upgrade($oldversion) { set_config('issuerid', $issuer->get('id'), 'googledocs'); } + if ($oldversion < 2017030600) { + set_config('supportedfiles', 'both', 'googledocs'); + } upgrade_plugin_savepoint(true, 2017030500, 'repository', 'googledocs'); } diff --git a/repository/googledocs/lang/en/repository_googledocs.php b/repository/googledocs/lang/en/repository_googledocs.php index 5654925eb09..e64dabcb73e 100644 --- a/repository/googledocs/lang/en/repository_googledocs.php +++ b/repository/googledocs/lang/en/repository_googledocs.php @@ -35,6 +35,12 @@ $string['issuer_help'] = 'Select the OAuth 2 service that is configured to talk $string['servicenotenabled'] = 'Access not configured. Make sure the service \'Drive API\' is enabled.'; $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.'; // Deprecated since Moodle 3.3. $string['oauthinfo'] = '
To use this plugin, you must register your site with Google, as described in the documentation Google OAuth 2.0 setup.
As part of the registration process, you will need to enter the following URL as \'Authorized Redirect URIs\':
{$a->callbackurl}
Once registered, you will be provided with a client ID and secret which can be used to configure all Google Drive and Picasa plugins.
Please also note that you will have to enable the service \'Drive API\'.
'; diff --git a/repository/googledocs/lib.php b/repository/googledocs/lib.php index 852ee5d1bae..bfffc736f92 100644 --- a/repository/googledocs/lib.php +++ b/repository/googledocs/lib.php @@ -185,7 +185,6 @@ class repository_googledocs extends repository { ); } - /** * List the files and folders. * @@ -217,6 +216,7 @@ class repository_googledocs extends repository { $ret = array(); $ret['dynload'] = true; + $ret['defaultreturntype'] = $this->default_returntype(); $ret['path'] = $this->build_breadcrumb($path); $ret['list'] = $results; return $ret; @@ -266,7 +266,7 @@ class repository_googledocs extends repository { $files = array(); $folders = array(); $config = get_config('googledocs'); - $fields = "files(id,name,mimeType,webContentLink,fileExtension,modifiedTime,size,thumbnailLink,iconLink)"; + $fields = "files(id,name,mimeType,webContentLink,webViewLink,fileExtension,modifiedTime,size,thumbnailLink,iconLink)"; $params = array('q' => $q, 'fields' => $fields, 'spaces' => 'drive'); try { @@ -284,7 +284,6 @@ class repository_googledocs extends repository { } } - $base = 'https://www.googleapis.com/drive/v3'; $gfiles = isset($response->files) ? $response->files : array(); foreach ($gfiles as $gfile) { if ($gfile->mimeType == 'application/vnd.google-apps.folder') { @@ -300,12 +299,11 @@ class repository_googledocs extends repository { ); } else { // This is a file. + $link = isset($gfile->webContentLink) ? $gfile->webContentLink : ''; if (isset($gfile->fileExtension)) { // The file has an extension, therefore we can download it. + $source = json_encode(['id' => $gfile->id, 'exportformat' => 'download', 'link' => $link]); $title = $gfile->name; - $params = ['alt' => 'media']; - $sourceurl = new moodle_url($base . '/files/' . $gfile->id, $params); - $source = $sourceurl->out(false); } else { // The file is probably a Google Doc file, we get the corresponding export link. // This should be improved by allowing the user to select the type of export they'd like. @@ -317,7 +315,7 @@ class repository_googledocs extends repository { switch ($type){ case 'document': $ext = $config->documentformat; - $title = $item['title'] . '.'. $ext; + $title = $gfile->name . '.'. $ext; if ($ext === 'rtf') { // Moodle user 'text/rtf' as the MIME type for RTF files. // Google uses 'application/rtf' for the same type of file. @@ -329,17 +327,17 @@ class repository_googledocs extends repository { break; case 'presentation': $ext = $config->presentationformat; - $title = $item['title'] . '.'. $ext; + $title = $gfile->name . '.'. $ext; $exporttype = $types[$ext]['type']; break; case 'spreadsheet': $ext = $config->spreadsheetformat; - $title = $item['title'] . '.'. $ext; + $title = $gfile->name . '.'. $ext; $exporttype = $types[$ext]['type']; break; case 'drawing': $ext = $config->drawingformat; - $title = $item['title'] . '.'. $ext; + $title = $gfile->name . '.'. $ext; $exporttype = $types[$ext]['type']; break; } @@ -347,9 +345,7 @@ class repository_googledocs extends repository { if (empty($title)) { continue; } - $params = ['mimeType' => $exporttype]; - $sourceurl = new moodle_url($base . '/files/' . $gfile->id . '/export', $params); - $source = $sourceurl->out(false); + $source = json_encode(['id' => $gfile->id, 'exportformat' => $exporttype, 'link' => $link]); } // 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. @@ -400,10 +396,24 @@ class repository_googledocs extends repository { global $CFG; $client = $this->get_user_oauth_client(); + $base = 'https://www.googleapis.com/drive/v3'; + $source = json_decode($reference); + + if ($source->exportformat == 'download') { + $params = ['alt' => 'media']; + $sourceurl = new moodle_url($base . '/files/' . $source->id, $params); + $source = $sourceurl->out(false); + } else { + $params = ['mimeType' => $source->exportformat]; + $sourceurl = new moodle_url($base . '/files/' . $source->id . '/export', $params); + $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($reference, null, $options); + $result = $client->download_one($source, null, $options); if ($result) { @chmod($path, $CFG->filepermissions); @@ -425,7 +435,8 @@ class repository_googledocs extends repository { * @return string file reference. */ public function get_file_reference($source) { - return clean_param($source, PARAM_URL); + // We could do some magic upgrade code here. + return $source; } /** @@ -446,7 +457,34 @@ class repository_googledocs extends repository { * @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('googledocs', 'supportedreturntypes'); + if ($setting == 'internal') { + return FILE_INTERNAL; + } else if ($setting == 'external') { + return FILE_REFERENCE; + } else { + return FILE_REFERENCE | FILE_INTERNAL; + } + } else { + return FILE_INTERNAL; + } + } + + /** + * Which return type should be selected by default. + * + * @return int + */ + public function default_returntype() { + $setting = get_config('googledocs', 'defaultreturntype'); + $supported = get_config('googledocs', 'supportedreturntypes'); + if (($setting == FILE_INTERNAL && $supported != 'external') || $supported == 'internal') { + return FILE_INTERNAL; + } else { + return FILE_REFERENCE; + } } /** @@ -458,7 +496,8 @@ class repository_googledocs extends repository { public static function get_type_option_names() { return array('issuerid', 'pluginname', 'documentformat', 'drawingformat', - 'presentationformat', 'spreadsheetformat'); + 'presentationformat', 'spreadsheetformat', + 'defaultreturntype', 'supportedreturntypes'); } /** @@ -466,10 +505,348 @@ class repository_googledocs extends repository { */ public function callback() { $client = $this->get_user_oauth_client(); - // This will upgrade to an access token if we have an authorization code. + // 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) { + // TODO. + $source = json_decode($storedfile->get_reference()); + + if ($source->link) { + header('Location: ' . $source->link); + } else { + $details = 'File is missing source link'; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + } + + /** + * List the permissions on a file. + * @param \core\oauth2\client $client Authenticated client. + * @param string $fileid The id of the file. + * + * @return array + */ + protected function list_file_permissions(\repository_googledocs\rest $client, $fileid) { + $fields = "permissions(id,type,emailAddress,role,allowFileDiscovery,displayName)"; + return $client->call('list_permissions', ['fileid' => $fileid]); + } + + /** + * See if a folder exists within a folder + * + * @param \core\oauth2\client $client Authenticated client. + * @param string $foldername The folder we are looking for. + * @param string $parentid The parent folder we are looking in. + * + * @return string|boolean The file id if it exists or false. + */ + protected function folder_exists_in_folder(\repository_googledocs\rest $client, $foldername, $parentid) { + $q = '\'' . addslashes($parentid) . '\' in parents and trashed = false and name = \'' . addslashes($foldername). '\''; + $fields = 'files(id, name)'; + $params = [ 'q' => $q, 'fields' => $fields]; + $response = $client->call('list', $params); + $missing = true; + foreach ($response->files as $child) { + if ($child->name == $foldername) { + return $child->id; + } + } + return false; + } + + /** + * Create a folder within a folder + * + * @param \core\oauth2\client $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_googledocs\rest $client, $foldername, $parentid) { + $fields = 'id'; + $params = ['fields' => $fields]; + $folder = ['mimeType' => 'application/vnd.google-apps.folder', 'name' => $foldername, 'parents' => [$parentid]]; + $created = $client->call('create', $params, json_encode($folder)); + if (empty($created->id)) { + $details = 'Cannot create folder:' . $foldername; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return $created->id; + } + + /** + * Get capabilities for a file. + * + * @param \core\oauth2\client $client Authenticated client. + * @param string $fileid The file we are checking. + * + * @return stdClass The file info with capabilities. + */ + protected function get_file_capabilities(\repository_googledocs\rest $client, $fileid) { + $fields = "id,capabilities,writersCanShare"; + $params = [ + 'fileid' => $fileid, + 'fields' => $fields + ]; + return $client->call('get', $params); + } + + /** + * Update file owner. + * + * @param \core\oauth2\client $client Authenticated client. + * @param string $fileid The file we are updating. + * + * @return boolean Did it work? + */ + protected function update_file_owner(\repository_googledocs\rest $client, $fileid, $owneremail) { + $updateowner = [ + 'emailAddress' => $owneremail, + 'role' => 'owner', + 'type' => 'user' + ]; + $params = ['fileid' => $fileid, 'transferOwnership' => 'true']; + try { + $response = $client->call('create_permission', $params, json_encode($updateowner)); + } catch (\core\oauth2\rest_exception $re) { + return false; + } + return !empty($response->id); + } + + /** + * Copy a file and return the new file details. A side effect of the copy + * is that the owner will be the account authenticated with this oauth client. + * + * @param \core\oauth2\client $client Authenticated client. + * @param string $fileid The file we are copying. + * + * @return stdClass file details. + */ + protected function copy_file(\repository_googledocs\rest $client, $fileid) { + $fields = "id,name,mimeType,webContentLink,webViewLink,size,thumbnailLink,iconLink"; + $params = [ + 'fileid' => $fileid, + 'fields' => $fields + ]; + $fileinfo = $client->call('copy', $params, ' '); + if (empty($fileinfo->id)) { + $details = 'Cannot copy file:' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return $fileinfo; + } + + /** + * Add a writer to the permissions on the file. + * + * @param \core\oauth2\client $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_writer_to_file($client, $fileid, $email) { + $updateeditor = [ + 'emailAddress' => $email, + 'role' => 'writer', + 'type' => 'user' + ]; + $params = ['fileid' => $fileid]; + $response = $client->call('create_permission', $params, json_encode($updateeditor)); + if (empty($response->id)) { + $details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return true; + } + + /** + * Move from root to folder + * + * @param \core\oauth2\client $client Authenticated client. + * @param string $fileid The file we are updating. + * @param string $folderid The id of the folder we are moving to + * @return boolean + */ + protected function move_file_from_root_to_folder($client, $fileid, $folderid) { + // Set the parent. + $params = [ + 'fileid' => $fileid, 'addParents' => $folderid, 'removeParents' => 'root' + ]; + $response = $client->call('update', $params, ' '); + if (empty($response->id)) { + $details = 'Cannot move the file to a folder: ' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return true; + } + + /** + * Prevent writers from sharing. + * + * @param \core\oauth2\client $client Authenticated client. + * @param string $fileid The file we are updating. + * @return boolean + */ + protected function prevent_writers_from_sharing_file($client, $fileid) { + // We don't want anyone but Moodle to change the sharing settings. + $params = [ + 'fileid' => $fileid + ]; + $update = [ + 'writersCanShare' => false + ]; + $response = $client->call('update', $params, json_encode($update)); + if (empty($response->id)) { + $details = 'Cannot prevent writers from sharing document: ' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return true; + } + + /** + * Allow anyone with the link to read the file. + * + * @param \core\oauth2\client $client Authenticated client. + * @param string $fileid The file we are updating. + * @return boolean + */ + protected function set_file_sharing_anyone_with_link_can_read($client, $fileid) { + $updateread = [ + 'type' => 'anyone', + 'role' => 'reader', + 'allowFileDiscovery' => 'false' + ]; + $params = ['fileid' => $fileid]; + $response = $client->call('create_permission', $params, json_encode($updateread)); + if (empty($response->id) || $response->id != 'anyoneWithLink') { + $details = 'Cannot update link sharing for the document: ' . $fileid; + throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details); + } + return true; + } + + /** + * 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. + * @return string $modifiedreference (final one before saving to DB) + */ + public function reference_file_selected($reference, $context) { + // 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). + $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_googledocs\rest($userauth); + $systemservice = new repository_googledocs\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->permissions as $permission) { + if ($permission->type == 'user' && + $permission->role == 'owner' && + isset($permission->emailAddress) && + $permission->emailAddress == $systemuseremail) { + $ownerupdaterequired = false; + } + if ($permission->id == 'anyoneWithLink' && + $permission->type == 'anyone' && + $permission->role == 'reader' && + $permission->allowFileDiscovery == false) { + $readshareupdaterequired = false; + } + } + + // Now move it to a sensible folder. + $contextlist = array_reverse($context->get_parent_contexts(true)); + + $parentid = 'root'; + foreach ($contextlist as $context) { + // Make sure a folder exists here. + $folderid = $this->folder_exists_in_folder($systemservice, $foldername, $parentid); + if ($folderid !== false) { + $parentid = $folderid; + } else { + // Create it. + $parentid = $this->create_folder_in_folder($systemservice, $foldername, $parentid); + } + } + + // See if we have edit capability before the copy. + $fileinfo = $this->get_file_capabilities($userservice, $source->id); + $canedit = !empty($fileinfo->capabilities->canEdit); + $writerscanshare = !empty($fileinfo->writersCanShare); + + // The owner was not the system user so we have to update the file. + if ($ownerupdaterequired) { + + $worked = $this->update_file_owner($userservice, $source->id, $systemuseremail); + if (!$worked) { + // Updating the owner only works for "google files" like documents etc. For binary + // files we will get here. + $source = $this->copy_file($systemservice, $source->id); + + $readshareupdaterequired = true; + $writerscanshare = true; + + if ($canedit) { + $this->add_writer_to_file($systemservice, $source->id, $useremail); + } + } + + $this->move_file_from_root_to_folder($systemservice, $source->id, $parentid); + } + + if ($writerscanshare) { + // We don't want anyone but Moodle to change the sharing settings. + $this->prevent_writers_from_sharing_file($systemservice, $source->id); + } + + if ($readshareupdaterequired) { + $this->set_file_sharing_anyone_with_link_can_read($systemservice, $source->id); + } + } + /** * Edit/Create Admin Settings Moodle form. * @@ -477,7 +854,8 @@ class repository_googledocs extends repository { * @param string $classname repository class name. */ public static function type_config_form($mform, $classname = 'repository') { - $url = (string)new moodle_url('/admin/tool/oauth2/issuers.php'); + $url = new moodle_url('/admin/tool/oauth2/issuers.php'); + $url = $url->out(); $mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_googledocs', $url)); @@ -488,11 +866,26 @@ class repository_googledocs extends repository { foreach ($issuers as $issuer) { $options[$issuer->get('id')] = s($issuer->get('name')); } + + $strrequired = get_string('required'); + $mform->addElement('select', 'issuerid', get_string('issuer', 'repository_googledocs'), $options); $mform->addHelpButton('issuerid', 'issuer', 'repository_googledocs'); $mform->addRule('issuerid', $strrequired, 'required', null, 'client'); - $strrequired = get_string('required'); + $mform->addElement('static', null, '', get_string('fileoptions', 'repository_googledocs')); + $choices = [ + 'internal' => get_string('internal', 'repository_googledocs'), + 'external' => get_string('external', 'repository_googledocs'), + 'both' => get_string('both', 'repository_googledocs') + ]; + $mform->addElement('select', 'supportedreturntypes', get_string('supportedreturntypes', 'repository_googledocs'), $choices); + + $choices = [ + FILE_INTERNAL => get_string('internal', 'repository_googledocs'), + FILE_REFERENCE => get_string('external', 'repository_googledocs'), + ]; + $mform->addElement('select', 'defaultreturntype', get_string('defaultreturntype', 'repository_googledocs'), $choices); $mform->addElement('static', null, '', get_string('importformat', 'repository_googledocs')); @@ -529,7 +922,8 @@ class repository_googledocs extends repository { $presentationformat['txt'] = 'txt'; core_collator::ksort($presentationformat, core_collator::SORT_NATURAL); - $mform->addElement('select', 'presentationformat', get_string('presentationformat', 'repository_googledocs'), $presentationformat); + $str = get_string('presentationformat', 'repository_googledocs'); + $mform->addElement('select', 'presentationformat', $str, $presentationformat); $mform->setDefault('presentationformat', $presentationformat['pptx']); $mform->setType('presentationformat', PARAM_ALPHANUM); @@ -541,9 +935,14 @@ class repository_googledocs extends repository { $spreadsheetformat['xlsx'] = 'xlsx'; core_collator::ksort($spreadsheetformat, core_collator::SORT_NATURAL); - $mform->addElement('select', 'spreadsheetformat', get_string('spreadsheetformat', 'repository_googledocs'), $spreadsheetformat); + $str = get_string('spreadsheetformat', 'repository_googledocs'); + $mform->addElement('select', 'spreadsheetformat', $str, $spreadsheetformat); $mform->setDefault('spreadsheetformat', $spreadsheetformat['xlsx']); $mform->setType('spreadsheetformat', PARAM_ALPHANUM); } } + // Icon from: http://www.iconspedia.com/icon/google-2706.html. +function repository_googledocs_oauth2_system_scopes() { + return 'https://www.googleapis.com/auth/drive'; +} diff --git a/repository/googledocs/version.php b/repository/googledocs/version.php index ce8f197427a..4aa78df7655 100644 --- a/repository/googledocs/version.php +++ b/repository/googledocs/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2017030500; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2017030600; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2016112900; // Requires this Moodle version. $plugin->component = 'repository_googledocs'; // Full name of the plugin (used for diagnostics). diff --git a/repository/lib.php b/repository/lib.php index c11f4cdc61f..6ed4db745bd 100644 --- a/repository/lib.php +++ b/repository/lib.php @@ -1281,6 +1281,19 @@ abstract class repository implements cacheable_object { public function cache_file_by_reference($reference, $storedfile) { } + /** + * reference_file_selected + * 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. + * @return string updated reference (final one before it's saved to db). + */ + public function reference_file_selected($reference, $context) { + return $reference; + } + /** * Return the source information * @@ -1890,6 +1903,17 @@ abstract class repository implements cacheable_object { return (FILE_INTERNAL | FILE_EXTERNAL); } + /** + * Tells how the file can be picked from this repository + * + * Maximum value is FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE + * + * @return int + */ + public function default_returntype() { + return FILE_INTERNAL; + } + /** * Provide repository instance information for Ajax * @@ -1904,6 +1928,7 @@ abstract class repository implements cacheable_object { $meta->icon = $OUTPUT->image_url('icon', 'repository_'.$meta->type)->out(false); $meta->supported_types = file_get_typegroup('extension', $this->supported_filetypes()); $meta->return_types = $this->supported_returntypes(); + $meta->defaultreturntype = $this->default_returntype(); $meta->sortorder = $this->options['sortorder']; return $meta; } diff --git a/repository/repository_ajax.php b/repository/repository_ajax.php index 631d4d1a0d0..ba703765f1b 100644 --- a/repository/repository_ajax.php +++ b/repository/repository_ajax.php @@ -226,6 +226,11 @@ switch ($action) { $record->contenthash = $sourcefile->get_contenthash(); $record->filesize = $sourcefile->get_filesize(); } + + // This hook gives the repo a place to do some house cleaning, and update the $reference before it's saved + // to the file store. E.g. transfer ownership of the file to a system account etc. + $reference = $repo->reference_file_selected($reference, $context); + // Check if file exists. if (repository::draftfile_exists($itemid, $saveas_path, $saveas_filename)) { // File name being used, rename it.