mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
MDL-67975 nextcloud: Add support for Link to file
This change set would bring the following new additions to the nextcloud repo: 1. Create a new radio button in filepicker: "Link to file" 2. When user clicks this radio button a warning message would be created, saying this file would become public. Meaning a public link is created in the nextcloud server. 3. Created a sync_reference method to sync the files downloaded from nextcloud server. The sync/refresh time given is 1 day/24 hours. 4. Made sure that when the file is downloaded, we use the file from moodledata file pool. Signed-off-by: Sujith Haridasan <sujith@moodle.com>
This commit is contained in:
parent
411150a424
commit
0845f53d14
@ -1224,6 +1224,7 @@ M.core_filepicker.init = function(Y, options) {
|
||||
var client_id = this.options.client_id;
|
||||
var selectnode = this.selectnode;
|
||||
var getfile = selectnode.one('.fp-select-confirm');
|
||||
var filePickerHelper = this;
|
||||
// bind labels with corresponding inputs
|
||||
selectnode.all('.fp-saveas,.fp-linktype-2,.fp-linktype-1,.fp-linktype-4,fp-linktype-8,.fp-setauthor,.fp-setlicense').each(function (node) {
|
||||
node.all('label').set('for', node.one('input,select').generateID());
|
||||
@ -1239,6 +1240,28 @@ M.core_filepicker.init = function(Y, options) {
|
||||
node.addClassIf('uneditable', !allowinputs);
|
||||
node.all('input,select').set('disabled', allowinputs?'':'disabled');
|
||||
});
|
||||
|
||||
// If the link to the file is selected, only then.
|
||||
// Remember: this is not to be done for all repos.
|
||||
// Only for those repos where the filereferencewarning is set.
|
||||
// The value 4 represents FILE_REFERENCE here.
|
||||
if (e.currentTarget.get('value') === '4') {
|
||||
var filereferencewarning = filePickerHelper.active_repo.filereferencewarning;
|
||||
if (filereferencewarning) {
|
||||
var fileReferenceNode = e.currentTarget.ancestor('.fp-linktype-4');
|
||||
var fileReferenceWarningNode = Y.Node.create('<div/>').
|
||||
addClass('alert alert-warning px-3 py-1 my-1 small').
|
||||
setAttrs({role: 'alert'}).
|
||||
setContent(filereferencewarning);
|
||||
fileReferenceNode.append(fileReferenceWarningNode);
|
||||
}
|
||||
} else {
|
||||
var fileReferenceInput = selectnode.one('.fp-linktype-4 input');
|
||||
var fileReferenceWarningNode = fileReferenceInput.ancestor('.fp-linktype-4').one('.alert-warning');
|
||||
if (fileReferenceWarningNode) {
|
||||
fileReferenceWarningNode.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
selectnode.all('.fp-linktype-2,.fp-linktype-1,.fp-linktype-4,.fp-linktype-8').each(function (node) {
|
||||
@ -1574,6 +1597,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;
|
||||
// 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();
|
||||
},
|
||||
print_login: function(data) {
|
||||
|
@ -62,3 +62,6 @@ $string['invalidresponse'] = 'Invalid server response.';
|
||||
$string['noclientconnection'] = 'The OAuth clients could not be connected.';
|
||||
$string['pathnotcreated'] = 'Folder path {$a} could not be created in the system account.';
|
||||
$string['endpointnotdefined'] = 'Endpoint {$a} not defined.';
|
||||
|
||||
// Warnings.
|
||||
$string['externalpubliclinkwarning'] = '<b>Warning:</b> This file will become public.';
|
||||
|
@ -92,6 +92,12 @@ class repository_nextcloud extends repository {
|
||||
*/
|
||||
private $controlledlinkfoldername;
|
||||
|
||||
/**
|
||||
* Curl instance that can be used to fetch file from nextcloud instance.
|
||||
* @var curl
|
||||
*/
|
||||
private $curl;
|
||||
|
||||
/**
|
||||
* repository_nextcloud constructor.
|
||||
*
|
||||
@ -143,6 +149,7 @@ class repository_nextcloud extends repository {
|
||||
}
|
||||
|
||||
$this->ocsclient = new ocs_client($this->get_user_oauth_client());
|
||||
$this->curl = new curl();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -291,6 +298,7 @@ class repository_nextcloud extends repository {
|
||||
*
|
||||
*/
|
||||
public function get_link($url) {
|
||||
// Create a read only public link, remember no update possible in this file/folder.
|
||||
$ocsparams = [
|
||||
'path' => $url,
|
||||
'shareType' => ocs_client::SHARE_TYPE_PUBLIC,
|
||||
@ -319,9 +327,16 @@ class repository_nextcloud extends repository {
|
||||
* This method does not do any translation of the file source.
|
||||
*
|
||||
* @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
|
||||
* @return string file reference, ready to be stored
|
||||
* @return string file reference, ready to be stored or json encoded string for public link reference
|
||||
*/
|
||||
public function get_file_reference($source) {
|
||||
$usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
|
||||
if ($usefilereference) {
|
||||
return json_encode([
|
||||
'type' => 'FILE_REFERENCE',
|
||||
'link' => $this->get_link($source),
|
||||
]);
|
||||
}
|
||||
// The simple relative path to the file is enough.
|
||||
return $source;
|
||||
}
|
||||
@ -420,6 +435,12 @@ class repository_nextcloud extends repository {
|
||||
$repositoryname = $this->get_name();
|
||||
$reference = json_decode($storedfile->get_reference());
|
||||
|
||||
// If the file is a reference which means its a public link in nextcloud.
|
||||
if ($reference->type === 'FILE_REFERENCE') {
|
||||
// This file points to the public link just fetch the latest one from nextcloud repo.
|
||||
redirect($reference->link);
|
||||
}
|
||||
|
||||
// 1. assure the client and user is logged in.
|
||||
if (empty($this->client) || $this->get_system_oauth_client() === false || $this->get_system_ocs_client() === null) {
|
||||
$details = get_string('contactadminwith', 'repository_nextcloud',
|
||||
@ -751,10 +772,10 @@ class repository_nextcloud extends repository {
|
||||
} else if ($setting === 'external') {
|
||||
return FILE_CONTROLLED_LINK;
|
||||
} else {
|
||||
return FILE_CONTROLLED_LINK | FILE_INTERNAL;
|
||||
return FILE_CONTROLLED_LINK | FILE_INTERNAL | FILE_REFERENCE;
|
||||
}
|
||||
} else {
|
||||
return FILE_INTERNAL;
|
||||
return FILE_INTERNAL | FILE_REFERENCE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,6 +887,7 @@ class repository_nextcloud extends repository {
|
||||
'defaultreturntype' => $this->default_returntype(),
|
||||
'manage' => $this->issuer->get('baseurl'), // Provide button to go into file management interface quickly.
|
||||
'list' => array(), // Contains all file/folder information and is required to build the file/folder tree.
|
||||
'filereferencewarning' => get_string('externalpubliclinkwarning', 'repository_nextcloud'),
|
||||
];
|
||||
|
||||
// If relative path is a non-top-level path, calculate all its parents' paths.
|
||||
@ -909,4 +931,65 @@ class repository_nextcloud extends repository {
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize the external file if there is an update happened to it.
|
||||
*
|
||||
* If the file has been updated in the nextcloud instance, this method
|
||||
* would take care of the file we copy into the moodle file pool.
|
||||
*
|
||||
* The call to this method reaches from stored_file::sync_external_file()
|
||||
*
|
||||
* @param stored_file $file
|
||||
* @return bool true if synced successfully else false if not ready to sync or reference link not set
|
||||
*/
|
||||
public function sync_reference(stored_file $file):bool {
|
||||
global $CFG;
|
||||
|
||||
if ($file->get_referencelastsync() + DAYSECS > time()) {
|
||||
// Synchronize once per day.
|
||||
return false;
|
||||
}
|
||||
|
||||
$reference = json_decode($file->get_reference());
|
||||
|
||||
if (!isset($reference->link)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url = $reference->link;
|
||||
if (file_extension_in_typegroup($file->get_filepath() . $file->get_filename(), 'web_image')) {
|
||||
$saveas = $this->prepare_file(uniqid());
|
||||
try {
|
||||
$result = $this->curl->download_one($url, [], [
|
||||
'filepath' => $saveas,
|
||||
'timeout' => $CFG->repositorysyncimagetimeout,
|
||||
'followlocation' => true,
|
||||
]);
|
||||
|
||||
$info = $this->curl->get_info();
|
||||
|
||||
if ($result === true && isset($info['http_code']) && $info['http_code'] === 200) {
|
||||
$file->set_synchronised_content_from_file($saveas);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// If the download fails lets download with get().
|
||||
$this->curl->get($url, null, ['timeout' => $CFG->repositorysyncimagetimeout, 'followlocation' => true, 'nobody' => true]);
|
||||
$info = $this->curl->get_info();
|
||||
|
||||
if (isset($info['http_code']) && $info['http_code'] === 200 &&
|
||||
array_key_exists('download_content_length', $info) &&
|
||||
$info['download_content_length'] >= 0) {
|
||||
$filesize = (int)$info['download_content_length'];
|
||||
$file->set_synchronized(null, $filesize);
|
||||
return true;
|
||||
}
|
||||
|
||||
$file->set_missingsource();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -623,12 +623,12 @@ JSON;
|
||||
|
||||
/**
|
||||
* Test supported_returntypes.
|
||||
* FILE_INTERNAL when no system account is connected.
|
||||
* FILE_INTERNAL | FILE_CONTROLLED_LINK when a system account is connected.
|
||||
* FILE_INTERNAL | FILE_REFERENCE when no system account is connected.
|
||||
* FILE_INTERNAL | FILE_CONTROLLED_LINK | FILE_REFERENCE when a system account is connected.
|
||||
*/
|
||||
public function test_supported_returntypes() {
|
||||
global $DB;
|
||||
$this->assertEquals(FILE_INTERNAL, $this->repo->supported_returntypes());
|
||||
$this->assertEquals(FILE_INTERNAL | FILE_REFERENCE, $this->repo->supported_returntypes());
|
||||
$dataobject = new stdClass();
|
||||
$dataobject->timecreated = time();
|
||||
$dataobject->timemodified = time();
|
||||
@ -641,12 +641,12 @@ JSON;
|
||||
|
||||
$DB->insert_record('oauth2_system_account', $dataobject);
|
||||
// When a system account is registered the file_type FILE_CONTROLLED_LINK is supported.
|
||||
$this->assertEquals(FILE_INTERNAL | FILE_CONTROLLED_LINK,
|
||||
$this->assertEquals(FILE_INTERNAL | FILE_CONTROLLED_LINK | FILE_REFERENCE,
|
||||
$this->repo->supported_returntypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* The reference_file_selected() methode is called every time a FILE_CONTROLLED_LINK is chosen for upload.
|
||||
* The reference_file_selected() method is called every time a FILE_CONTROLLED_LINK is chosen for upload.
|
||||
* Since the function is very long the private function are tested separately, and merely the abortion of the
|
||||
* function are tested.
|
||||
*
|
||||
@ -844,6 +844,150 @@ XML;
|
||||
$this->repo->send_file('', '', '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function provides the data for test_sync_reference
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function sync_reference_provider():array {
|
||||
return [
|
||||
'referecncelastsync done recently' => [
|
||||
[
|
||||
'storedfile_record' => [
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => 'testfile.txt',
|
||||
],
|
||||
'storedfile_reference' => json_encode(
|
||||
[
|
||||
'type' => 'FILE_REFERENCE',
|
||||
'link' => 'https://test.local/fakelink/',
|
||||
'usesystem' => true,
|
||||
'referencelastsync' => DAYSECS + time()
|
||||
]
|
||||
),
|
||||
],
|
||||
'mockfunctions' => ['get_referencelastsync'],
|
||||
'expectedresult' => false
|
||||
],
|
||||
'file without link' => [
|
||||
[
|
||||
'storedfile_record' => [
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => 'testfile.txt',
|
||||
],
|
||||
'storedfile_reference' => json_encode(
|
||||
[
|
||||
'type' => 'FILE_REFERENCE',
|
||||
'usesystem' => true,
|
||||
]
|
||||
),
|
||||
],
|
||||
'mockfunctions' => [],
|
||||
'expectedresult' => false
|
||||
],
|
||||
'file extenstion to exclude' => [
|
||||
[
|
||||
'storedfile_record' => [
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => 'testfile.txt',
|
||||
],
|
||||
'storedfile_reference' => json_encode(
|
||||
[
|
||||
'link' => 'https://test.local/fakelink/',
|
||||
'type' => 'FILE_REFERENCE',
|
||||
'usesystem' => true,
|
||||
]
|
||||
),
|
||||
],
|
||||
'mockfunctions' => [],
|
||||
'expectedresult' => false
|
||||
],
|
||||
'file extenstion for image' => [
|
||||
[
|
||||
'storedfile_record' => [
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => 'testfile.png',
|
||||
],
|
||||
'storedfile_reference' => json_encode(
|
||||
[
|
||||
'link' => 'https://test.local/fakelink/',
|
||||
'type' => 'FILE_REFERENCE',
|
||||
'usesystem' => true,
|
||||
]
|
||||
),
|
||||
'mock_curl' => true,
|
||||
],
|
||||
'mockfunctions' => [''],
|
||||
'expectedresult' => true
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing sync_reference
|
||||
*
|
||||
* @dataProvider sync_reference_provider
|
||||
* @param array $storedfileargs
|
||||
* @param array $storedfilemethodsmock
|
||||
* @param bool $expectedresult
|
||||
* @return void
|
||||
*/
|
||||
public function test_sync_reference(array $storedfileargs, $storedfilemethodsmock, bool $expectedresult):void {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
if (isset($storedfilemethodsmock[0])) {
|
||||
$storedfile = $this->createMock(stored_file::class);
|
||||
|
||||
if ($storedfilemethodsmock[0] === 'get_referencelastsync') {
|
||||
if (!$expectedresult) {
|
||||
$storedfile->method('get_referencelastsync')->willReturn(DAYSECS + time());
|
||||
}
|
||||
} else {
|
||||
$storedfile->method('get_referencelastsync')->willReturn(null);
|
||||
}
|
||||
|
||||
$storedfile->method('get_reference')->willReturn($storedfileargs['storedfile_reference']);
|
||||
$storedfile->method('get_filepath')->willReturn($storedfileargs['storedfile_record']['filepath']);
|
||||
$storedfile->method('get_filename')->willReturn($storedfileargs['storedfile_record']['filename']);
|
||||
|
||||
if ((isset($storedfileargs['mock_curl']) && $storedfileargs)) {
|
||||
// Lets mock curl, else it would not serve the purpose here.
|
||||
$curl = $this->createMock(curl::class);
|
||||
$curl->method('download_one')->willReturn(true);
|
||||
$curl->method('get_info')->willReturn(['http_code' => 200]);
|
||||
|
||||
$reflectionproperty = new \ReflectionProperty($this->repo, 'curl');
|
||||
$reflectionproperty->setAccessible(true);
|
||||
$reflectionproperty->setValue($this->repo, $curl);
|
||||
}
|
||||
} else {
|
||||
$fs = get_file_storage();
|
||||
$storedfile = $fs->create_file_from_reference(
|
||||
$storedfileargs['storedfile_record'],
|
||||
$this->repo->id,
|
||||
$storedfileargs['storedfile_reference']);
|
||||
}
|
||||
|
||||
$actualresult = $this->repo->sync_reference($storedfile);
|
||||
$this->assertEquals($expectedresult, $actualresult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method, which inserts a given mock value into the repository_nextcloud object.
|
||||
*
|
||||
@ -879,6 +1023,8 @@ XML;
|
||||
$ret['defaultreturntype'] = FILE_INTERNAL;
|
||||
$ret['list'] = array();
|
||||
|
||||
$ret['filereferencewarning'] = get_string('externalpubliclinkwarning', 'repository_nextcloud');
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user