Merge branch 'MDL-80447-405-4' of https://github.com/raortegar/moodle into MOODLE_405_STABLE
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/google/lib.php');
|
||||
|
||||
/**
|
||||
* Callback to get the required scopes for system account.
|
||||
*
|
||||
@@ -32,7 +34,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
function fileconverter_googledrive_oauth2_system_scopes(\core\oauth2\issuer $issuer) {
|
||||
if ($issuer->get('id') == get_config('fileconverter_googledrive', 'issuerid')) {
|
||||
return 'https://www.googleapis.com/auth/drive';
|
||||
return Google_Service_Drive::DRIVE_FILE;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -244,6 +244,7 @@ $string['type'] = 'Type';
|
||||
$string['typenotvisible'] = 'Type not visible';
|
||||
$string['unknownoriginal'] = 'Unknown';
|
||||
$string['upload'] = 'Upload this file';
|
||||
$string['uploaderror'] = 'File upload error';
|
||||
$string['uploading'] = 'Uploading...';
|
||||
$string['uploadsucc'] = 'The file has been uploaded successfully';
|
||||
$string['unknownsource'] = 'Unknown source';
|
||||
|
||||
@@ -56,6 +56,11 @@
|
||||
{{#pix}}a/setting{{/pix}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="fp-tb-uploadfile">
|
||||
<a title="{{#str}}upload{{/str}}" class="btn btn-secondary btn-sm" href="#">
|
||||
{{#pix}}i/upload{{/pix}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="fp-tb-help">
|
||||
<a title="{{#str}}help, repository{{/str}}" class="btn btn-secondary btn-sm" href="#">
|
||||
{{#pix}}a/help{{/pix}}
|
||||
|
||||
@@ -1624,6 +1624,8 @@ M.core_filepicker.init = function(Y, options) {
|
||||
this.active_repo.message = (data.message || '');
|
||||
this.active_repo.help = data.help?data.help:null;
|
||||
this.active_repo.manage = data.manage?data.manage:null;
|
||||
this.active_repo.uploadfile = data.uploadfile ? data.uploadfile : null;
|
||||
this.active_repo.uploadevent = data.uploadevent ? data.uploadevent : null;
|
||||
// Warning message related to the file reference option, if applicable to the given repository.
|
||||
this.active_repo.filereferencewarning = data.filereferencewarning ? data.filereferencewarning : null;
|
||||
this.print_header();
|
||||
@@ -1987,6 +1989,28 @@ M.core_filepicker.init = function(Y, options) {
|
||||
managelnk.simulate('click')
|
||||
});
|
||||
|
||||
// Repository upload.
|
||||
// If repository supports upload, it needs to provide 'uploadevent' in its response to 'list' command.
|
||||
// This event will be used to trigger the upload process.
|
||||
// Repository will need to subscribe to this event and launch its own upload process.
|
||||
toolbar.one('.fp-tb-uploadfile').one('a,button').on('click', (e) => {
|
||||
if (this.active_repo.uploadevent) {
|
||||
e.preventDefault();
|
||||
require(['core/pubsub'], (PubSub) => {
|
||||
PubSub.publish(this.active_repo.uploadevent, {
|
||||
repoId: this.active_repo.id,
|
||||
contextId: this.options.context.id,
|
||||
callback: () => {
|
||||
// Refresh the file list after upload is done.
|
||||
if (!this.active_repo.norefresh) {
|
||||
this.list({path: this.currentpath});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
// same with .fp-tb-help
|
||||
var helplnk = Y.Node.create('<a/>').
|
||||
setAttrs({id:'fp-tb-help-'+client_id+'-link', target:'_blank'}).
|
||||
@@ -2063,6 +2087,9 @@ M.core_filepicker.init = function(Y, options) {
|
||||
enable_tb_control(toolbar.one('.fp-tb-manage'), r.manage);
|
||||
Y.one('#fp-tb-manage-'+client_id+'-link').set('href', r.manage);
|
||||
|
||||
// Upload file.
|
||||
enable_tb_control(toolbar.one('.fp-tb-uploadfile'), r.uploadfile);
|
||||
|
||||
// help url
|
||||
enable_tb_control(toolbar.one('.fp-tb-help'), r.help);
|
||||
Y.one('#fp-tb-help-'+client_id+'-link').set('href', r.help);
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
define("repository_googledocs/upload",["exports","core/pubsub","core/modal_save_cancel","core/str","core/templates","core/modal_events","core/dropzone","core/config","core/notification"],(function(_exports,_pubsub,_modal_save_cancel,_str,_templates,_modal_events,_dropzone,config,_notification){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Upload module for Google Docs repository.
|
||||
*
|
||||
* @module repository_googledocs/upload
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_templates=_interopRequireDefault(_templates),_modal_events=_interopRequireDefault(_modal_events),_dropzone=_interopRequireDefault(_dropzone),config=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(config),_notification=_interopRequireDefault(_notification);let listenersRegistered=!1,droppedFiles=[];const initDropzone=modal=>{const $body=modal.getBody(),dropzoneContainer=$body.find(".repository_googledocs_dropzone_container").get(0),fileListContainer=$body.find("ul.repository_googledocs_files_list").get(0),dz=new _dropzone.default(dropzoneContainer,"*",(files=>{droppedFiles.push(...files);let fileListHTML="";for(let i=0;i<droppedFiles.length;i++)fileListHTML+="<li>"+droppedFiles[i].name+"</li>";fileListContainer.innerHTML=fileListHTML}));(0,_str.getString)("dropfiles","repository").then((label=>{dz.setLabel(label)})),dz.init()},commitFiles=(repoId,contextId,callback,files,modal)=>{const saveButton=modal.getFooter().find('[data-action="save"]'),formData=new FormData;formData.append("action","upload"),formData.append("repo_id",repoId),formData.append("contextid",contextId),formData.append("sesskey",config.sesskey);for(let i=0;i<files.length;i++)formData.append("files[]",files[i]);const xhr=new XMLHttpRequest;xhr.open("POST",config.wwwroot+"/repository/googledocs/repository_ajax.php",!1),xhr.onload=function(){const response=JSON.parse(xhr.responseText);response.error?(saveButton.removeAttr("disabled"),_notification.default.alert((0,_str.getString)("uploaderror","repository"),response.error,(0,_str.getString)("close","repository"))):(saveButton.removeAttr("disabled"),modal.hide(),callback())},xhr.send(formData)},registerEventListeners=()=>{listenersRegistered||((0,_pubsub.subscribe)("repository_googledocs_upload",(data=>{(data=>{_templates.default.render("repository_googledocs/upload_dialogue",{}).then((function(bodyHtml){return _modal_save_cancel.default.create({title:(0,_str.getString)("upload"),body:bodyHtml,large:!0}).then((function(modal){return modal.getRoot().on(_modal_events.default.shown,(()=>{droppedFiles=[],initDropzone(modal)})),modal.getRoot().on(_modal_events.default.hidden,(()=>{modal.destroy()})),modal.getRoot().on(_modal_events.default.save,(e=>{e.preventDefault(),commitFiles(data.repoId,data.contextId,data.callback,droppedFiles,modal)})),modal.getRoot().on(_modal_events.default.cancel,(()=>{modal.hide()})),modal.show(),modal}))}))})(data)})),listenersRegistered=!0)};_exports.init=()=>{registerEventListeners()}}));
|
||||
|
||||
//# sourceMappingURL=upload.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,150 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Upload module for Google Docs repository.
|
||||
*
|
||||
* @module repository_googledocs/upload
|
||||
* @copyright 2025 Huong Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {subscribe} from 'core/pubsub';
|
||||
import SaveCancelModal from 'core/modal_save_cancel';
|
||||
import {getString} from 'core/str';
|
||||
import Templates from 'core/templates';
|
||||
import ModalEvents from 'core/modal_events';
|
||||
import Dropzone from 'core/dropzone';
|
||||
import * as config from 'core/config';
|
||||
import Notification from 'core/notification';
|
||||
|
||||
let listenersRegistered = false;
|
||||
let droppedFiles = [];
|
||||
|
||||
/**
|
||||
* Open the upload modal.
|
||||
*
|
||||
* @param {object} data Data passed from the event
|
||||
*/
|
||||
const openUploadModal = (data) => {
|
||||
Templates.render('repository_googledocs/upload_dialogue', {}).then(function(bodyHtml) {
|
||||
return SaveCancelModal.create({
|
||||
title: getString('upload'),
|
||||
body: bodyHtml,
|
||||
large: true,
|
||||
}).then(function(modal) {
|
||||
modal.getRoot().on(ModalEvents.shown, () => {
|
||||
droppedFiles = [];
|
||||
initDropzone(modal);
|
||||
});
|
||||
modal.getRoot().on(ModalEvents.hidden, () => {
|
||||
modal.destroy();
|
||||
});
|
||||
modal.getRoot().on(ModalEvents.save, (e) => {
|
||||
e.preventDefault();
|
||||
commitFiles(data.repoId, data.contextId, data.callback, droppedFiles, modal);
|
||||
});
|
||||
modal.getRoot().on(ModalEvents.cancel, () => {
|
||||
modal.hide();
|
||||
});
|
||||
modal.show();
|
||||
|
||||
return modal;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the dropzone inside the modal.
|
||||
*
|
||||
* @param {Modal} modal Modal instance
|
||||
*/
|
||||
const initDropzone = (modal) => {
|
||||
const $body = modal.getBody();
|
||||
const dropzoneContainer = $body.find('.repository_googledocs_dropzone_container').get(0);
|
||||
const fileListContainer = $body.find('ul.repository_googledocs_files_list').get(0);
|
||||
const dz = new Dropzone(dropzoneContainer, '*', (files) => {
|
||||
droppedFiles.push(...files);
|
||||
let fileListHTML = '';
|
||||
for (let i = 0; i < droppedFiles.length; i++) {
|
||||
fileListHTML += '<li>' + droppedFiles[i].name + '</li>';
|
||||
}
|
||||
fileListContainer.innerHTML = fileListHTML;
|
||||
});
|
||||
|
||||
getString('dropfiles', 'repository').then((label) => {
|
||||
dz.setLabel(label);
|
||||
});
|
||||
|
||||
dz.init();
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload files to server.
|
||||
*
|
||||
* @param {Integer} repoId Repository ID
|
||||
* @param {Integer} contextId Context ID
|
||||
* @param {function} callback Callback function
|
||||
* @param {array} files Files to be uploaded
|
||||
* @param {Modal} modal Modal instance
|
||||
*/
|
||||
const commitFiles = (repoId, contextId, callback, files, modal) => {
|
||||
const saveButton = modal.getFooter().find('[data-action="save"]');
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'upload');
|
||||
formData.append('repo_id', repoId);
|
||||
formData.append('contextid', contextId);
|
||||
formData.append('sesskey', config.sesskey);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append("files[]", files[i]);
|
||||
}
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', config.wwwroot + '/repository/googledocs/repository_ajax.php', false);
|
||||
xhr.onload = function() {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.error) {
|
||||
saveButton.removeAttr('disabled');
|
||||
Notification.alert(
|
||||
getString('uploaderror', 'repository'),
|
||||
response.error,
|
||||
getString('close', 'repository'),
|
||||
);
|
||||
} else {
|
||||
saveButton.removeAttr('disabled');
|
||||
modal.hide();
|
||||
callback();
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register events.
|
||||
*/
|
||||
const registerEventListeners = () => {
|
||||
if (!listenersRegistered) {
|
||||
subscribe('repository_googledocs_upload', (data) => {
|
||||
openUploadModal(data);
|
||||
});
|
||||
listenersRegistered = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the upload module.
|
||||
*/
|
||||
export const init = () => {
|
||||
registerEventListeners();
|
||||
};
|
||||
@@ -48,17 +48,11 @@ class googledocs_content_search extends googledocs_content {
|
||||
'q' => $q,
|
||||
'fields' => $fields,
|
||||
'spaces' => 'drive',
|
||||
'supportsAllDrives' => 'true',
|
||||
'includeItemsFromAllDrives' => 'true',
|
||||
'corpora' => 'allDrives',
|
||||
];
|
||||
|
||||
// If shared drives exist, include the additional required parameters in order to extend the content search
|
||||
// into the shared drives area as well.
|
||||
$response = helper::request($this->service, 'shared_drives_list', []);
|
||||
if (!empty($response->drives)) {
|
||||
$params['supportsAllDrives'] = 'true';
|
||||
$params['includeItemsFromAllDrives'] = 'true';
|
||||
$params['corpora'] = 'allDrives';
|
||||
}
|
||||
|
||||
// Request the content through the API call.
|
||||
$response = helper::request($this->service, 'list', $params);
|
||||
|
||||
|
||||
@@ -54,18 +54,11 @@ class googledocs_drive_content extends googledocs_content {
|
||||
'q' => $q,
|
||||
'fields' => $fields,
|
||||
'spaces' => 'drive',
|
||||
'supportsAllDrives' => 'true',
|
||||
'includeItemsFromAllDrives' => 'true',
|
||||
'corpora' => 'allDrives',
|
||||
];
|
||||
|
||||
// Check whether there are any shared drives.
|
||||
$response = helper::request($this->service, 'shared_drives_list', []);
|
||||
if (!empty($response->drives)) {
|
||||
// To be able to include content from shared drives, we need to enable 'supportsAllDrives' and
|
||||
// 'includeItemsFromAllDrives'. The Google Drive API requires explicit request for inclusion of content from
|
||||
// shared drives and also a confirmation that the application is designed to handle files on shared drives.
|
||||
$params['supportsAllDrives'] = 'true';
|
||||
$params['includeItemsFromAllDrives'] = 'true';
|
||||
}
|
||||
|
||||
// Request the content through the API call.
|
||||
$response = helper::request($this->service, 'list', $params);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
namespace repository_googledocs\local\browser;
|
||||
|
||||
use repository_googledocs\googledocs_content;
|
||||
use repository_googledocs\helper;
|
||||
|
||||
/**
|
||||
* Utility class for browsing the content within the googledocs repository root.
|
||||
@@ -41,7 +40,7 @@ class googledocs_root_content extends googledocs_content {
|
||||
*/
|
||||
protected function get_contents(string $query): array {
|
||||
// Add 'My drive' folder into the displayed contents.
|
||||
$contents = [
|
||||
return [
|
||||
(object)[
|
||||
'id' => \repository_googledocs::MY_DRIVE_ROOT_ID,
|
||||
'name' => get_string('mydrive', 'repository_googledocs'),
|
||||
@@ -49,19 +48,5 @@ class googledocs_root_content extends googledocs_content {
|
||||
'modifiedTime' => '',
|
||||
],
|
||||
];
|
||||
|
||||
// If shared drives exists, include 'Shared drives' folder to the displayed contents.
|
||||
$response = helper::request($this->service, 'shared_drives_list', []);
|
||||
|
||||
if (!empty($response->drives)) {
|
||||
$contents[] = (object)[
|
||||
'id' => \repository_googledocs::SHARED_DRIVES_ROOT_ID,
|
||||
'name' => get_string('shareddrives', 'repository_googledocs'),
|
||||
'mimeType' => 'application/vnd.google-apps.folder',
|
||||
'modifiedTime' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,43 +54,37 @@ class rest extends \core\oauth2\rest {
|
||||
'spaces' => PARAM_RAW,
|
||||
'supportsAllDrives' => PARAM_RAW,
|
||||
'includeItemsFromAllDrives' => PARAM_RAW,
|
||||
'corpora' => PARAM_RAW
|
||||
'corpora' => PARAM_RAW,
|
||||
],
|
||||
'response' => 'json'
|
||||
'response' => 'json',
|
||||
],
|
||||
'get' => [
|
||||
'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}',
|
||||
'method' => 'get',
|
||||
'args' => [
|
||||
'fields' => PARAM_RAW,
|
||||
'fileid' => PARAM_RAW
|
||||
'fileid' => PARAM_RAW,
|
||||
'supportsAllDrives' => 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'
|
||||
'response' => 'json',
|
||||
],
|
||||
'delete' => [
|
||||
'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}',
|
||||
'method' => 'delete',
|
||||
'args' => [
|
||||
'fileid' => PARAM_RAW
|
||||
'fileid' => PARAM_RAW,
|
||||
'supportsAllDrives' => PARAM_RAW,
|
||||
],
|
||||
'response' => 'json'
|
||||
'response' => 'json',
|
||||
],
|
||||
'create' => [
|
||||
'endpoint' => 'https://www.googleapis.com/drive/v3/files',
|
||||
'method' => 'post',
|
||||
'args' => [
|
||||
'fields' => PARAM_RAW
|
||||
'fields' => PARAM_RAW,
|
||||
'supportsAllDrives' => PARAM_RAW,
|
||||
],
|
||||
'response' => 'json'
|
||||
'response' => 'json',
|
||||
],
|
||||
'update' => [
|
||||
'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}',
|
||||
@@ -99,9 +93,10 @@ class rest extends \core\oauth2\rest {
|
||||
'fileid' => PARAM_RAW,
|
||||
'fields' => PARAM_RAW,
|
||||
'addParents' => PARAM_RAW,
|
||||
'removeParents' => PARAM_RAW
|
||||
'removeParents' => PARAM_RAW,
|
||||
'supportsAllDrives' => PARAM_RAW,
|
||||
],
|
||||
'response' => 'json'
|
||||
'response' => 'json',
|
||||
],
|
||||
'create_permission' => [
|
||||
'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}/permissions',
|
||||
@@ -111,8 +106,9 @@ class rest extends \core\oauth2\rest {
|
||||
'emailMessage' => PARAM_RAW,
|
||||
'sendNotificationEmail' => PARAM_RAW,
|
||||
'transferOwnership' => PARAM_RAW,
|
||||
'supportsAllDrives' => PARAM_RAW,
|
||||
],
|
||||
'response' => 'json'
|
||||
'response' => 'json',
|
||||
],
|
||||
'update_permission' => [
|
||||
'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}/permissions/{permissionid}',
|
||||
@@ -123,17 +119,25 @@ class rest extends \core\oauth2\rest {
|
||||
'emailMessage' => PARAM_RAW,
|
||||
'sendNotificationEmail' => PARAM_RAW,
|
||||
'transferOwnership' => PARAM_RAW,
|
||||
'supportsAllDrives' => PARAM_RAW,
|
||||
],
|
||||
'response' => 'json'
|
||||
'response' => 'json',
|
||||
],
|
||||
'shared_drives_list' => [
|
||||
'endpoint' => 'https://www.googleapis.com/drive/v3/drives',
|
||||
'method' => 'get',
|
||||
'upload' => [
|
||||
'endpoint' => 'https://www.googleapis.com/upload/drive/v3/files',
|
||||
'method' => 'post',
|
||||
'args' => [
|
||||
'pageSize' => PARAM_INT,
|
||||
'pageToken' => PARAM_RAW,
|
||||
'q' => PARAM_RAW,
|
||||
'useDomainAdminAccess' => PARAM_RAW,
|
||||
'uploadType' => PARAM_RAW,
|
||||
'fields' => PARAM_RAW,
|
||||
'supportsAllDrives' => PARAM_RAW,
|
||||
],
|
||||
'response' => 'headers',
|
||||
],
|
||||
'upload_content' => [
|
||||
'endpoint' => '{uploadurl}',
|
||||
'method' => 'put',
|
||||
'args' => [
|
||||
'uploadurl' => PARAM_URL,
|
||||
],
|
||||
'response' => 'json',
|
||||
],
|
||||
|
||||
+119
-47
@@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/repository/lib.php');
|
||||
require_once($CFG->libdir . '/filebrowser/file_browser.php');
|
||||
require_once($CFG->libdir . '/google/lib.php');
|
||||
|
||||
use repository_googledocs\helper;
|
||||
use repository_googledocs\googledocs_content_search;
|
||||
@@ -53,11 +54,6 @@ class repository_googledocs extends repository {
|
||||
*/
|
||||
private $issuer = null;
|
||||
|
||||
/**
|
||||
* Additional scopes required for drive.
|
||||
*/
|
||||
const SCOPES = 'https://www.googleapis.com/auth/drive';
|
||||
|
||||
/** @var string Defines the path node identifier for the repository root. */
|
||||
const REPOSITORY_ROOT_ID = 'repository_root';
|
||||
|
||||
@@ -80,6 +76,7 @@ class repository_googledocs extends repository {
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
|
||||
global $PAGE;
|
||||
parent::__construct($repositoryid, $context, $options, $readonly = 0);
|
||||
|
||||
try {
|
||||
@@ -91,6 +88,8 @@ class repository_googledocs extends repository {
|
||||
if ($this->issuer && !$this->issuer->get('enabled')) {
|
||||
$this->disabled = true;
|
||||
}
|
||||
|
||||
$PAGE->requires->js_call_amd('repository_googledocs/upload', 'init');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +98,7 @@ class repository_googledocs extends repository {
|
||||
* @param moodle_url $overrideurl - Use this url instead of the repo callback.
|
||||
* @return \core\oauth2\client
|
||||
*/
|
||||
protected function get_user_oauth_client($overrideurl = false) {
|
||||
public function get_user_oauth_client($overrideurl = false) {
|
||||
if ($this->client) {
|
||||
return $this->client;
|
||||
}
|
||||
@@ -112,7 +111,7 @@ class repository_googledocs extends repository {
|
||||
$returnurl->param('sesskey', sesskey());
|
||||
}
|
||||
|
||||
$this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, self::SCOPES, true);
|
||||
$this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, Google_Service_Drive::DRIVE_FILE, true);
|
||||
|
||||
return $this->client;
|
||||
}
|
||||
@@ -270,6 +269,13 @@ class repository_googledocs extends repository {
|
||||
$path = helper::build_node_path('repository_root', $pluginname);
|
||||
}
|
||||
|
||||
// Make sure the current path points to "My Drive" before listing files.
|
||||
$mydrive = get_string('mydrive', 'repository_googledocs');
|
||||
$mydrivepath = helper::build_node_path('root', $mydrive);
|
||||
if (!str_contains($path, $mydrivepath)) {
|
||||
$path = $path . '/' . $mydrivepath;
|
||||
}
|
||||
|
||||
if (!$this->issuer->get('enabled')) {
|
||||
// Empty list of files for disabled repository.
|
||||
return [
|
||||
@@ -306,7 +312,11 @@ class repository_googledocs extends repository {
|
||||
'defaultreturntype' => $this->default_returntype(),
|
||||
'path' => $contentobj->get_navigation(),
|
||||
'list' => $contentobj->get_content_nodes($query, [$this, 'filter']),
|
||||
'manage' => 'https://drive.google.com/',
|
||||
'uploadfile' => true,
|
||||
'uploadevent' => 'repository_googledocs_upload',
|
||||
'repo_id' => $this->id,
|
||||
'contextid' => $this->context->id,
|
||||
'sesskey' => sesskey(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -708,10 +718,10 @@ class repository_googledocs extends repository {
|
||||
$storedfile->get_filename(),
|
||||
$forcedownload);
|
||||
$url->param('sesskey', sesskey());
|
||||
$param = ($options['embed'] == true) ? false : $url;
|
||||
$param = (isset($options['embed']) && $options['embed'] == true) ? false : $url;
|
||||
$userauth = $this->get_user_oauth_client($param);
|
||||
if (!$userauth->is_logged_in()) {
|
||||
if ($options['embed'] == true) {
|
||||
if (isset($options['embed']) && $options['embed'] == true) {
|
||||
// Due to Same-origin policy, we cannot redirect to googledocs login page.
|
||||
// If the requested file is embed and the user is not logged in, add option to log in using a popup.
|
||||
$this->print_login_popup(['style' => 'margin-top: 250px']);
|
||||
@@ -798,7 +808,7 @@ class repository_googledocs extends repository {
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function get_file_summary(\repository_googledocs\rest $client, $fileid) {
|
||||
$fields = "id,name,owners,parents";
|
||||
$fields = "id,name,owners,parents,webContentLink,webViewLink";
|
||||
$params = [
|
||||
'fileid' => $fileid,
|
||||
'fields' => $fields
|
||||
@@ -806,35 +816,6 @@ class repository_googledocs extends repository {
|
||||
return $client->call('get', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 \repository_googledocs\rest $client Authenticated client.
|
||||
* @param string $fileid The file we are copying.
|
||||
* @param string $name The original filename (don't change it).
|
||||
*
|
||||
* @return stdClass file details.
|
||||
*/
|
||||
protected function copy_file(\repository_googledocs\rest $client, $fileid, $name) {
|
||||
$fields = "id,name,mimeType,webContentLink,webViewLink,size,thumbnailLink,iconLink";
|
||||
$params = [
|
||||
'fileid' => $fileid,
|
||||
'fields' => $fields,
|
||||
];
|
||||
// Keep the original name (don't put copy at the end of it).
|
||||
$copyinfo = [];
|
||||
if (!empty($name)) {
|
||||
$copyinfo = [ 'name' => $name ];
|
||||
}
|
||||
$fileinfo = $client->call('copy', $params, json_encode($copyinfo));
|
||||
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 (temporary).
|
||||
*
|
||||
@@ -854,7 +835,7 @@ class repository_googledocs extends repository {
|
||||
'type' => 'user',
|
||||
'expirationTime' => $expires->format(DateTime::RFC3339)
|
||||
];
|
||||
$params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false'];
|
||||
$params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false', 'supportsAllDrives' => 'true'];
|
||||
$response = $client->call('create_permission', $params, json_encode($updateeditor));
|
||||
if (empty($response->id)) {
|
||||
$details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
|
||||
@@ -878,7 +859,7 @@ class repository_googledocs extends repository {
|
||||
'role' => 'writer',
|
||||
'type' => 'user'
|
||||
];
|
||||
$params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false'];
|
||||
$params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false', 'supportsAllDrives' => 'true'];
|
||||
$response = $client->call('create_permission', $params, json_encode($updateeditor));
|
||||
if (empty($response->id)) {
|
||||
$details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
|
||||
@@ -944,7 +925,7 @@ class repository_googledocs extends repository {
|
||||
'role' => 'reader',
|
||||
'allowFileDiscovery' => 'false'
|
||||
];
|
||||
$params = ['fileid' => $fileid];
|
||||
$params = ['fileid' => $fileid, 'supportsAllDrives' => 'true'];
|
||||
$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;
|
||||
@@ -1008,7 +989,6 @@ class repository_googledocs extends repository {
|
||||
$userservice = new repository_googledocs\rest($userauth);
|
||||
$systemservice = new repository_googledocs\rest($systemauth);
|
||||
|
||||
// Add Moodle as writer.
|
||||
$this->add_writer_to_file($userservice, $source->id, $systemuseremail);
|
||||
|
||||
// Now move it to a sensible folder.
|
||||
@@ -1063,8 +1043,25 @@ class repository_googledocs extends repository {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the file so we get a snapshot file owned by Moodle.
|
||||
$newsource = $this->copy_file($systemservice, $source->id, $source->name);
|
||||
$originalfile = $this->get_file_summary($userservice, $source->id);
|
||||
// Use the user service to download the file.
|
||||
$downloadedfile = $this->download_file(
|
||||
$userservice,
|
||||
$source->id,
|
||||
$originalfile->name,
|
||||
);
|
||||
// Upload the user file to the system drive.
|
||||
$uploaded = $this->upload_file(
|
||||
$systemservice,
|
||||
$downloadedfile['path'],
|
||||
$downloadedfile['newfilename'],
|
||||
$source->exportformat,
|
||||
$parentid,
|
||||
);
|
||||
// Add the original file owner as a writer to the file.
|
||||
$this->add_writer_to_file($systemservice, $uploaded->id, $originalfile->owners[0]->emailAddress);
|
||||
$newsource = $this->get_file_summary($systemservice, $uploaded->id);
|
||||
|
||||
// Move the copied file to the correct folder.
|
||||
$this->move_file_from_root_to_folder($systemservice, $newsource->id, $parentid);
|
||||
|
||||
@@ -1084,6 +1081,81 @@ class repository_googledocs extends repository {
|
||||
return $reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file to Google Docs using the provided REST client.
|
||||
*
|
||||
* @param \repository_googledocs\rest $client The REST client for Google Docs API communication.
|
||||
* @param string $filepath The local path to the file to be uploaded.
|
||||
* @param string $filename The name to assign to the uploaded file.
|
||||
* @param string $exportformat The export format for the file (e.g., 'pdf', 'docx').
|
||||
* @param string $parentid The ID of the parent folder in Google Drive where the file will be uploaded.
|
||||
* @return stdClass|coding_exception Returns the response from the Google Docs API after uploading the file.
|
||||
*/
|
||||
public function upload_file(
|
||||
\repository_googledocs\rest $client,
|
||||
string $filepath,
|
||||
string $filename,
|
||||
string $exportformat,
|
||||
string $parentid
|
||||
): stdClass {
|
||||
$fileinfo = [
|
||||
'name' => $filename,
|
||||
'mimeType' => $exportformat,
|
||||
'parents' => [$parentid], // We will move it later.
|
||||
];
|
||||
$params = [
|
||||
'supportsAllDrives' => 'true', // Support shared drives.
|
||||
'uploadType' => 'resumable', // Use resumable upload.
|
||||
];
|
||||
|
||||
$headers = $client->call('upload', $params, json_encode($fileinfo));
|
||||
|
||||
$uploadurl = '';
|
||||
// Google returns a location header with the location for the upload.
|
||||
foreach ($headers as $header) {
|
||||
if (stripos($header, 'Location:') === 0) {
|
||||
$uploadurl = trim(substr($header, strpos($header, ':') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
$params = ['uploadurl' => $uploadurl];
|
||||
return $client->call('upload_content', $params, file_get_contents($filepath), mime_content_type($filepath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file from Google Docs using the provided user service.
|
||||
*
|
||||
* @param \repository_googledocs\rest $userservice The user service instance for Google Docs REST API.
|
||||
* @param string $fileid The ID of the file to download.
|
||||
* @param string $originalfilename The file original name
|
||||
* @return array|repository_exception The downloaded file content or relevant response.
|
||||
*/
|
||||
protected function download_file(
|
||||
\repository_googledocs\rest $userservice,
|
||||
string $fileid,
|
||||
string $originalfilename
|
||||
): array|repository_exception {
|
||||
global $CFG;
|
||||
|
||||
$client = $this->get_user_oauth_client();
|
||||
$base = 'https://www.googleapis.com/drive/v3';
|
||||
$params = ['alt' => 'media'];
|
||||
$sourceurl = new moodle_url($base . '/files/' . $fileid, $params);
|
||||
|
||||
$path = $this->prepare_file($originalfilename);
|
||||
$options = ['filepath' => $path, 'timeout' => $CFG->repositorygetfiletimeout, 'followlocation' => true, 'maxredirs' => 5];
|
||||
$success = $client->download_one($sourceurl->out(false), null, $options);
|
||||
if ($success) {
|
||||
@chmod($path, $CFG->filepermissions);
|
||||
return [
|
||||
'path' => $path,
|
||||
'newfilename' => $originalfilename,
|
||||
];
|
||||
}
|
||||
|
||||
throw new repository_exception('cannotdownload', 'repository');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable file info from a the reference.
|
||||
*
|
||||
@@ -1223,7 +1295,7 @@ class repository_googledocs extends repository {
|
||||
*/
|
||||
function repository_googledocs_oauth2_system_scopes(\core\oauth2\issuer $issuer) {
|
||||
if ($issuer->get('id') == get_config('googledocs', 'issuerid')) {
|
||||
return 'https://www.googleapis.com/auth/drive';
|
||||
return Google_Service_Drive::DRIVE_FILE;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* The Web service script that is called from the filepicker upload.
|
||||
*
|
||||
* @package repository_googledocs
|
||||
* @copyright 2025 Raquel Ortega <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('AJAX_SCRIPT', true);
|
||||
|
||||
require(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/lib/filelib.php');
|
||||
require_once($CFG->dirroot . '/repository/lib.php');
|
||||
|
||||
|
||||
$action = required_param('action', PARAM_ALPHA);
|
||||
$repoid = optional_param('repo_id', 0, PARAM_INT);
|
||||
$contextid = optional_param('contextid', 0, PARAM_INT);
|
||||
$itemid = optional_param('itemid', 0, PARAM_INT);
|
||||
|
||||
require_login();
|
||||
if (!confirm_sesskey()) {
|
||||
die(json_encode(['error' => get_string('invalidsesskey', 'error')]));
|
||||
}
|
||||
|
||||
$context = context::instance_by_id($contextid, true);
|
||||
|
||||
$repo = repository::get_repository_by_id($repoid, $contextid);
|
||||
if (!$repo) {
|
||||
die(json_encode(['error' => get_string('invalidrepositoryid', 'repository')]));
|
||||
}
|
||||
// Check permissions.
|
||||
$repo->check_capability();
|
||||
$repo->check_login();
|
||||
|
||||
switch ($action) {
|
||||
case 'upload':
|
||||
$files = $_FILES['files'];
|
||||
$tmp = make_request_directory();
|
||||
$savedfiles = [];
|
||||
if (is_array($files['name'])) {
|
||||
// Multiple files.
|
||||
foreach ($files['name'] as $idx => $name) {
|
||||
$dest = $tmp . '/' . basename($name);
|
||||
move_uploaded_file($files['tmp_name'][$idx], $dest);
|
||||
$savedfiles[] = $dest;
|
||||
}
|
||||
} else {
|
||||
// Single file.
|
||||
$dest = $tmp . '/' . basename($files['name']);
|
||||
move_uploaded_file($files['tmp_name'], $dest);
|
||||
$savedfiles[] = $dest;
|
||||
}
|
||||
|
||||
// Upload the file to Google Drive repository.
|
||||
$ha = new repository_googledocs($repoid, $context);
|
||||
$userauth = $ha->get_user_oauth_client();
|
||||
$userservice = new repository_googledocs\rest($userauth);
|
||||
foreach ($savedfiles as $file) {
|
||||
$filename = basename($file);
|
||||
$ha->upload_file($userservice, $file, $filename, 'download', "root");
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['error' => get_string('error', 'repository')]);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template repository_googledocs/upload_dialogue
|
||||
|
||||
Google repository upload dialogue.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
|
||||
}
|
||||
}}
|
||||
<div class="repository_googledocs_dropzone_container"></div>
|
||||
<div class="repository_googledocs_files_container">
|
||||
<h5>
|
||||
{{#str}} attachedfiles, repository {{/str}}
|
||||
</h5>
|
||||
<ul class="repository_googledocs_files_list"></ul>
|
||||
</div>
|
||||
@@ -34,16 +34,21 @@ final class googledocs_search_content_test extends \googledocs_content_testcase
|
||||
/**
|
||||
* Test get_content_nodes().
|
||||
*
|
||||
* @covers \repository_googledocs\googledocs_content_search
|
||||
* @dataProvider get_content_nodes_provider
|
||||
* @param string $query The query string
|
||||
* @param bool $sortcontent Whether the contents should be sorted in alphabetical order
|
||||
* @param array $filterextensions The array containing file extensions that should be disallowed (filtered)
|
||||
* @param array $shareddrives The array containing the existing shared drives
|
||||
* @param array $searccontents The array containing the fetched google drive contents that match the search criteria
|
||||
* @param array $expected The expected array which contains the generated repository content nodes
|
||||
*/
|
||||
public function test_get_content_nodes(string $query, bool $sortcontent, array $filterextensions,
|
||||
array $shareddrives, array $searccontents, array $expected): void {
|
||||
public function test_get_content_nodes(
|
||||
string $query,
|
||||
bool $sortcontent,
|
||||
array $filterextensions,
|
||||
array $searccontents,
|
||||
array $expected
|
||||
): void {
|
||||
|
||||
// Mock the service object.
|
||||
$servicemock = $this->createMock(rest::class);
|
||||
@@ -52,45 +57,27 @@ final class googledocs_search_content_test extends \googledocs_content_testcase
|
||||
'q' => "fullText contains '" . str_replace("'", "\'", $query) . "' AND trashed = false",
|
||||
'fields' => 'files(id,name,mimeType,webContentLink,webViewLink,fileExtension,modifiedTime,size,iconLink)',
|
||||
'spaces' => 'drive',
|
||||
'supportsAllDrives' => 'true',
|
||||
'includeItemsFromAllDrives' => 'true',
|
||||
'corpora' => 'allDrives',
|
||||
];
|
||||
|
||||
if (!empty($shareddrives)) {
|
||||
$searchparams['supportsAllDrives'] = 'true';
|
||||
$searchparams['includeItemsFromAllDrives'] = 'true';
|
||||
$searchparams['corpora'] = 'allDrives';
|
||||
}
|
||||
|
||||
// Assert that the call() method is being called twice with the given arguments consecutively. In the first
|
||||
// instance it is being called to fetch the shared drives (shared_drives_list), while in the second instance
|
||||
// to fetch the relevant drive contents (list) that match the search criteria. Also, define the returned
|
||||
// data objects by these calls.
|
||||
$callinvocations = $this->exactly(2);
|
||||
// Assert that the call() method is being called to fetch the relevant drive contents (list),
|
||||
// that match the search criteria. Also, define the returned data objects by these calls.
|
||||
// This simulates the more restricted drive.file scope behavior.
|
||||
$callinvocations = $this->exactly(1);
|
||||
$servicemock->expects($callinvocations)
|
||||
->method('call')
|
||||
->willReturnCallback(function(string $method, array $params) use (
|
||||
$callinvocations,
|
||||
$shareddrives,
|
||||
$searccontents,
|
||||
$searchparams,
|
||||
) {
|
||||
switch (self::getInvocationCount($callinvocations)) {
|
||||
case 1:
|
||||
$this->assertEquals('shared_drives_list', $method);
|
||||
$this->assertEquals('list', $method);
|
||||
$this->assertEquals($searchparams, $params);
|
||||
|
||||
$this->assertEmpty($params);
|
||||
return (object) [
|
||||
'kind' => 'drive#driveList',
|
||||
'nextPageToken' => 'd838181f30b0f5',
|
||||
'drives' => $shareddrives,
|
||||
];
|
||||
case 2:
|
||||
$this->assertEquals('list', $method);
|
||||
$this->assertEquals($searchparams, $params);
|
||||
|
||||
return (object) [
|
||||
'files' => $searccontents,
|
||||
];
|
||||
}
|
||||
return (object) [
|
||||
'files' => $searccontents,
|
||||
];
|
||||
});
|
||||
|
||||
// Construct the node path.
|
||||
@@ -118,14 +105,11 @@ final class googledocs_search_content_test extends \googledocs_content_testcase
|
||||
$searchforstring = get_string('searchfor', 'repository_googledocs');
|
||||
|
||||
return [
|
||||
'Folders and files match the search criteria; shared drives exist; ordering applied.' =>
|
||||
'Folders and files match the search criteria; ordering applied.' =>
|
||||
[
|
||||
'test',
|
||||
true,
|
||||
[],
|
||||
[
|
||||
self::create_google_drive_shared_drive_object('d85b21c0f86cb5', 'Shared Drive 1'),
|
||||
],
|
||||
[
|
||||
self::create_google_drive_file_object('d85b21c0f86cb0', 'Test file 3.pdf',
|
||||
'application/pdf', 'pdf', '1000', '',
|
||||
@@ -148,12 +132,11 @@ final class googledocs_search_content_test extends \googledocs_content_testcase
|
||||
'https://drive.google.com/uc?id=d85b21c0f86cb0&export=download', 'download'),
|
||||
],
|
||||
],
|
||||
'Only folders match the search criteria; shared drives do not exist; ordering not applied.' =>
|
||||
'Only folders match the search criteria; ordering not applied.' =>
|
||||
[
|
||||
'testing',
|
||||
false,
|
||||
[],
|
||||
[],
|
||||
[
|
||||
self::create_google_drive_folder_object('0c4ad262c65333', 'Testing folder 3'),
|
||||
self::create_google_drive_folder_object('d85b21c0f86cb0', 'Testing folder 1'),
|
||||
@@ -168,14 +151,11 @@ final class googledocs_search_content_test extends \googledocs_content_testcase
|
||||
"{$rootid}|Google+Drive/{$searchnodeid}|" . urlencode("{$searchforstring} 'testing'")),
|
||||
],
|
||||
],
|
||||
'Only files match the search criteria; shared drives exist; ordering not applied; filter .doc and .txt.' =>
|
||||
'Only files match the search criteria; ordering not applied; filter .doc and .txt.' =>
|
||||
[
|
||||
'root',
|
||||
false,
|
||||
['doc', 'txt'],
|
||||
[
|
||||
self::create_google_drive_shared_drive_object('d85b21c0f86cb5', 'Shared Drive 1'),
|
||||
],
|
||||
[
|
||||
self::create_google_drive_file_object('d85b21c0f86cb0', 'Testing file 3.pdf',
|
||||
'application/pdf', 'pdf', '1000'),
|
||||
@@ -190,14 +170,13 @@ final class googledocs_search_content_test extends \googledocs_content_testcase
|
||||
'https://googleusercontent.com/type/application/pdf', '', 'download'),
|
||||
],
|
||||
],
|
||||
'No content that matches the search criteria; shared drives do not exist.' =>
|
||||
'No content that matches the search criteria.' =>
|
||||
[
|
||||
'root',
|
||||
false,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
namespace repository_googledocs\local\browser;
|
||||
|
||||
use Google_Service_Drive;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
@@ -53,46 +55,67 @@ final class googledocs_drive_content_test extends \googledocs_content_testcase {
|
||||
'q' => "'" . str_replace("'", "\'", $query) . "' in parents AND trashed = false",
|
||||
'fields' => 'files(id,name,mimeType,webContentLink,webViewLink,fileExtension,modifiedTime,size,iconLink)',
|
||||
'spaces' => 'drive',
|
||||
'supportsAllDrives' => 'true',
|
||||
'includeItemsFromAllDrives' => 'true',
|
||||
'corpora' => 'allDrives',
|
||||
];
|
||||
|
||||
if (!empty($shareddrives)) {
|
||||
$listparams['supportsAllDrives'] = 'true';
|
||||
$listparams['includeItemsFromAllDrives'] = 'true';
|
||||
if (!$this->shared_drives_supported()) {
|
||||
$callinvocations = $this->exactly(1);
|
||||
$servicemock->expects($callinvocations)
|
||||
->method('call')
|
||||
->willReturnCallback(function (
|
||||
string $method,
|
||||
array $params
|
||||
) use (
|
||||
$listparams,
|
||||
$drivecontents,
|
||||
) {
|
||||
$this->assertEquals('list', $method);
|
||||
$this->assertEquals($listparams, $params);
|
||||
|
||||
return (object) [
|
||||
'files' => $drivecontents,
|
||||
];
|
||||
});
|
||||
} else {
|
||||
// Assert that the call() method is being called twice with the given arguments consecutively. In the first
|
||||
// instance it is being called to fetch the shared drives (shared_drives_list), while in the second instance
|
||||
// to fetch the relevant drive contents (list). Also, define the returned data objects by these calls.
|
||||
$callinvocations = $this->exactly(2);
|
||||
$servicemock->expects($callinvocations)
|
||||
->method('call')
|
||||
->willReturnCallback(function (
|
||||
string $method,
|
||||
array $params
|
||||
) use (
|
||||
$callinvocations,
|
||||
$shareddrives,
|
||||
$listparams,
|
||||
$drivecontents,
|
||||
) {
|
||||
switch (self::getInvocationCount($callinvocations)) {
|
||||
case 1:
|
||||
$this->assertEquals('shared_drives_list', $method);
|
||||
$this->assertEquals([], $params);
|
||||
|
||||
return (object) [
|
||||
'kind' => 'drive#driveList',
|
||||
'nextPageToken' => 'd838181f30b0f5',
|
||||
'drives' => $shareddrives,
|
||||
];
|
||||
case 2:
|
||||
$this->assertEquals('list', $method);
|
||||
$this->assertEquals($listparams, $params);
|
||||
return (object)[
|
||||
'files' => $drivecontents,
|
||||
];
|
||||
default:
|
||||
$this->fail('Unexpected call to the call() method.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Assert that the call() method is being called twice with the given arguments consecutively. In the first
|
||||
// instance it is being called to fetch the shared drives (shared_drives_list), while in the second instance
|
||||
// to fetch the relevant drive contents (list). Also, define the returned data objects by these calls.
|
||||
$callinvocations = $this->exactly(2);
|
||||
$servicemock->expects($callinvocations)
|
||||
->method('call')
|
||||
->willReturnCallback(function(string $method, array $params) use (
|
||||
$callinvocations,
|
||||
$shareddrives,
|
||||
$listparams,
|
||||
$drivecontents,
|
||||
) {
|
||||
switch (self::getInvocationCount($callinvocations)) {
|
||||
case 1:
|
||||
$this->assertEquals('shared_drives_list', $method);
|
||||
$this->assertEquals([], $params);
|
||||
|
||||
return (object) [
|
||||
'kind' => 'drive#driveList',
|
||||
'nextPageToken' => 'd838181f30b0f5',
|
||||
'drives' => $shareddrives,
|
||||
];
|
||||
case 2:
|
||||
$this->assertEquals('list', $method);
|
||||
$this->assertEquals($listparams, $params);
|
||||
return (object)[
|
||||
'files' => $drivecontents,
|
||||
];
|
||||
default:
|
||||
$this->fail('Unexpected call to the call() method.');
|
||||
}
|
||||
});
|
||||
|
||||
// Set the disallowed file types (extensions).
|
||||
$this->disallowedextensions = $filterextensions;
|
||||
$drivebrowser = new googledocs_drive_content($servicemock, $path, $sortcontent);
|
||||
@@ -272,4 +295,16 @@ final class googledocs_drive_content_test extends \googledocs_content_testcase {
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether shared drives are supported under the current Google Drive scope.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shared_drives_supported(): bool {
|
||||
$scopes = Google_Service_Drive::DRIVE_FILE;
|
||||
|
||||
// Full access is needed for shared drives (not just drive.file).
|
||||
return str_contains($scopes, 'auth/drive') && !str_contains($scopes, 'auth/drive.file');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
namespace repository_googledocs\local\browser;
|
||||
|
||||
use Google_Service_Drive;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
@@ -36,24 +38,43 @@ final class googledocs_root_content_test extends \googledocs_content_testcase {
|
||||
* @dataProvider get_content_nodes_provider
|
||||
* @param array $shareddrives The array containing the existing shared drives
|
||||
* @param array $expected The expected array which contains the generated repository content nodes
|
||||
* @param bool $expectshared Whether shared drives should be tested
|
||||
* @covers \repository_googledocs
|
||||
*/
|
||||
public function test_get_content_nodes(array $shareddrives, array $expected): void {
|
||||
public function test_get_content_nodes(array $shareddrives, array $expected, bool $expectshared): void {
|
||||
$scopessupportshared = $this->shared_drives_supported();
|
||||
|
||||
if ($expectshared && !$scopessupportshared) {
|
||||
$this->markTestSkipped('Shared drives not supported in current OAuth scope.');
|
||||
}
|
||||
|
||||
// Mock the service object.
|
||||
$servicemock = $this->createMock(\repository_googledocs\rest::class);
|
||||
|
||||
// Assert that the call() method is being called only once with the given arguments to fetch the existing
|
||||
// shared drives. Define the returned data object by this call.
|
||||
$servicemock->expects($this->once())
|
||||
->method('call')
|
||||
->with('shared_drives_list', [])
|
||||
->willReturn((object)[
|
||||
'kind' => 'drive#driveList',
|
||||
'nextPageToken' => 'd838181f30b0f5',
|
||||
'drives' => $shareddrives,
|
||||
]);
|
||||
if ($expectshared && $scopessupportshared) {
|
||||
// Expect shared drives API to be called.
|
||||
// Assert that the call() method is being called only once with the given arguments to fetch the existing
|
||||
// shared drives. Define the returned data object by this call.
|
||||
$servicemock->expects($this->once())
|
||||
->method('call')
|
||||
->with('shared_drives_list', [])
|
||||
->willReturn((object)[
|
||||
'kind' => 'drive#driveList',
|
||||
'nextPageToken' => 'd838181f30b0f5',
|
||||
'drives' => $shareddrives,
|
||||
]);
|
||||
} else {
|
||||
// If shared drives are not expected or not supported, call() should not be invoked.
|
||||
$servicemock->expects($this->never())
|
||||
->method('call');
|
||||
}
|
||||
|
||||
$rootbrowser = new googledocs_root_content($servicemock,
|
||||
\repository_googledocs::REPOSITORY_ROOT_ID . '|Google+Drive', false);
|
||||
$showshared = $expectshared && $scopessupportshared;
|
||||
$rootbrowser = new googledocs_root_content(
|
||||
$servicemock,
|
||||
\repository_googledocs::REPOSITORY_ROOT_ID . '|Google+Drive',
|
||||
$showshared
|
||||
);
|
||||
$contentnodes = $rootbrowser->get_content_nodes('', [$this, 'filter']);
|
||||
|
||||
// Assert that the returned array of repository content nodes is equal to the expected one.
|
||||
@@ -84,6 +105,7 @@ final class googledocs_root_content_test extends \googledocs_content_testcase {
|
||||
get_string('shareddrives', 'repository_googledocs'),
|
||||
"{$rootid}|Google+Drive"),
|
||||
],
|
||||
true, // Expect shared drives.
|
||||
],
|
||||
'Shared drives do not exist.' => [
|
||||
[],
|
||||
@@ -92,7 +114,20 @@ final class googledocs_root_content_test extends \googledocs_content_testcase {
|
||||
get_string('mydrive', 'repository_googledocs'),
|
||||
"{$rootid}|Google+Drive"),
|
||||
],
|
||||
false, // Do not expect shared drives.
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether shared drives are supported under the current Google Drive scope.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shared_drives_supported(): bool {
|
||||
$scopes = Google_Service_Drive::DRIVE_FILE;
|
||||
|
||||
// Full access is needed for shared drives (not just drive.file).
|
||||
return str_contains($scopes, 'auth/drive') && !str_contains($scopes, 'auth/drive.file');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user