1
0
mirror of https://github.com/typemill/typemill.git synced 2025-03-14 17:19:39 +01:00

Version 1.5.1: File Restrictions, Download Controller and Shared File-Component

This commit is contained in:
trendschau 2021-11-07 17:40:24 +01:00
parent 43d31a61cd
commit 1a09c7767b
7 changed files with 279 additions and 82 deletions

View File

@ -1,8 +1,10 @@
# Content Elements
# Content Elements
Cyanine provides a lot of other settings for your content area. For example:
[ebook (EPUB, 496.65 KB)](media/files/ebook.epub){.tm-download file-epub}
The Cyanine theme supports all content elements like tables, images, notices or downloads. It also supports anchor-links next to headlines, so you can deep link to certain content sections of your page. You can activate the anchors in the system settings of Typemill.
* Add an edit-button for github, gitlab or other plattforms.
* Show the author.
* Show the publish date.
* Show the chapter numbers in the navigation.
The Cyanine theme supports all content elements like tables, images, notices or downloads. It also supports anchor-links next to headlines, so you can deep link to certain content sections of your page. You can activate the anchors in the system settings of Typemill.

View File

@ -0,0 +1,2 @@
media/files/preview.pdf: member
media/files/ebook.epub: author

View File

@ -267,8 +267,15 @@ class ControllerAuthorMediaApi extends ControllerAuthor
}
$fileinfo = $fileProcessor->storeFile($this->params['file'], $this->params['name']);
if($fileinfo)
{
# publish file directly, used for example by file field for meta-tabs
if(isset($this->params['publish']) && $this->params['publish'])
{
$fileProcessor->publishFile();
}
return $response->withJson(['errors' => false, 'info' => $fileinfo]);
}

View File

@ -35,7 +35,8 @@ class ControllerDownload extends ControllerShared
if(!isset($_SESSION['role']))
{
die("You have to be an authenticated $allowedrole to download this file.");
$this->c->flash->addMessage('error', "You have to be an authenticated $allowedrole to download this file.");
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
elseif(
$_SESSION['role'] != 'administrator'
@ -43,7 +44,8 @@ class ControllerDownload extends ControllerShared
AND !$this->c->acl->inheritsRole($_SESSION['role'], $allowedrole)
)
{
die("You have to be a $allowedrole to download this file.");
$this->c->flash->addMessage('error', "You have to be a $allowedrole to download this file.");
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
}
@ -129,10 +131,11 @@ class ControllerDownload extends ControllerShared
header('Pragma: public');
header('Content-Encoding: none');
header('Expires: 0');
header('Accept-Ranges: bytes'); # Allow support for download resume
header('Expires: 0');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header_remove("Last-Modified");
header('Cache-Control: max-age=0, no-cache, no-store, must-revalidate');
header('Cache-Control: private', false); # required for some browsers
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="'.basename($file).'";'); # Make the browser display the Save As dialog

View File

@ -124,7 +124,7 @@ class ProcessFile extends ProcessAssets
foreach ($files as $key => $name)
{
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
if (!in_array($name, array(".","..","filerestrictions.yaml")) && file_exists($this->fileFolder . $name))
{
$filelist[] = [
'name' => $name,

View File

@ -98,7 +98,7 @@ const contentComponent = Vue.component('content-block', {
},
setComponentSize: function()
{
if(this.componentType == 'image-component')
if(this.componentType == 'image-component' || this.componentType == 'file-component')
{
myself = this;
setTimeout(function(){
@ -1822,26 +1822,7 @@ const fileComponent = Vue.component('file-component', {
}
}
self = this;
myaxios.get('/api/v1/filerestrictions',{
params: {
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
'filename': this.fileurl,
}
})
.then(function (response) {
self.userroles = self.userroles.concat(response.data.userroles);
self.selectedrole = response.data.restriction;
})
.catch(function (error)
{
if(error.response)
{
}
});
this.getrestriction();
},
methods: {
openmedialib: function()
@ -1855,30 +1836,10 @@ const fileComponent = Vue.component('file-component', {
return ' checked';
}
},
updatemarkdown: function(event)
updatemarkdown: function(event, url)
{
this.$emit('updatedMarkdown', event.target.value);
},
updaterestriction: function()
{
myaxios.post('/api/v1/filerestrictions',{
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
'filename': this.fileurl,
'role': this.selectedrole,
})
.then(function (response) {
})
.catch(function (error)
{
if(error.response)
{
}
});
console.info(this.selectedrole);
this.getrestriction(url);
},
createmarkdown: function()
{
@ -1920,6 +1881,55 @@ const fileComponent = Vue.component('file-component', {
this.$emit('updatedMarkdown', filemarkdown);
}
},
getrestriction: function(url)
{
var fileurl = this.fileurl;
if(url)
{
fileurl = url;
}
var myself = this;
myaxios.get('/api/v1/filerestrictions',{
params: {
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
'filename': fileurl,
}
})
.then(function (response) {
myself.userroles = ['all'];
myself.userroles = myself.userroles.concat(response.data.userroles);
myself.selectedrole = response.data.restriction;
})
.catch(function (error)
{
if(error.response)
{
}
});
},
updaterestriction: function()
{
myaxios.post('/api/v1/filerestrictions',{
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
'filename': this.fileurl,
'role': this.selectedrole,
})
.then(function (response) {
})
.catch(function (error)
{
if(error.response)
{
}
});
},
onFileChange: function( e )
{
if(e.target.files.length > 0)
@ -1954,10 +1964,12 @@ const fileComponent = Vue.component('file-component', {
self.load = false;
self.$parent.activatePage();
self.filemeta = true;
self.filetitle = response.data.info.title;
self.fileextension = response.data.info.extension;
self.fileurl = response.data.info.url;
self.filemeta = true;
self.filetitle = response.data.info.title;
self.fileextension = response.data.info.extension;
self.fileurl = response.data.info.url;
self.selectedrole = '';
self.createmarkdown();
})
.catch(function (error)

View File

@ -627,37 +627,26 @@ Vue.component('component-image', {
reader.onload = function(e)
{
sharedself.imgpreview = e.target.result;
/* load image to server */
var url = sharedself.$root.$data.root + '/api/v1/image';
var params = {
myaxios.post('/api/v1/image',{
'url': document.getElementById("path").value,
'image': e.target.result,
'name': imageFile.name,
'publish': true,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
};
var method = 'POST';
sendJson(function(response, httpStatus)
{
if(response)
{
var result = JSON.parse(response);
if(result.errors)
{
publishController.errors.message = result.errors.message;
}
else
{
sharedself.update(result.name);
}
}
}, method, url, params);
})
.then(function (response) {
sharedself.update(response.data.name);
})
.catch(function (error)
{
sharedself.load = false;
if(error.response)
{
publishController.errors.message = error.response.data.errors;
}
});
}
}
}
@ -665,6 +654,188 @@ Vue.component('component-image', {
},
})
Vue.component('component-file', {
props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'errors'],
template: '<div class="large">' +
'<transition name="fade-editor">' +
'<div v-if="showmedialib" class="modalWindow">' +
'<medialib parentcomponent="files"></medialib>' +
'</div>' +
'</transition>' +
'<label>{{ label|translate }}</label>' +
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
'<div class="ba b--moon-gray">' +
'<div class="medium">' +
'<label>{{ \'File URL (read only)\'|translate }}</label>' +
'<div class="flex">' +
'<button @click.prevent="deleteFile()" class="w-10 bn hover-bg-tm-red hover-white">x</button>' +
'<input class="w-90" type="text"' +
' :id="id"' +
' :maxlength="maxlength"' +
' readonly="readonly"' +
' :hidden="hidden"' +
' :required="required"' +
' :disabled="disabled"' +
' :name="name"' +
' :placeholder="placeholder"' +
' :value="value"' +
'@input="update($event, name)">' +
'</div>' +
'<div class="flex">' +
'<div class="relative dib w-100 br b--white bg-tm-green dim">' +
'<input type="file" accept="*" name="file" @change="onFileChange( $event )" class="absolute o-0 w-100 top-0 z-1 pointer h2" />' +
'<p class="relative w-100 bn br1 white pa1 h2 ma0 tc"><svg class="icon icon-upload baseline"><use xlink:href="#icon-upload"></use></svg> {{ \'upload\'|translate }}</p>' +
'</div>' +
'<div class="dib w-100 bl b--white">' +
'<button @click.prevent="openmedialib()" class="w-100 pointer bn bg-tm-green white pa0 h2 ma0 tc dim"><svg class="icon icon-paperclip baseline"><use xlink:href="#icon-paperclip"></use></svg> {{ \'medialib\'|translate }}</button>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="medium">' +
'<input title="fileid" type="hidden" placeholder="id" v-model="fileid" @input="createmarkdown" max="140" />' +
'<label for="filerestriction">{{ \'Access for\'|translate }}: </label>' +
'<select name="filerestriction" v-model="selectedrole" @change="updaterestriction">' +
'<option disabled value="">{{ \'Please select\'|translate }}</option>' +
'<option v-for="role in userroles">{{ role }}</option>' +
'</select>' +
'</div>' +
'</div>' +
'</div>',
data: function(){
return {
maxsize: 20, // megabyte
showmedialib: false,
fileid: '',
load: false,
userroles: ['all'],
selectedrole: '',
}
},
mounted: function(){
this.getrestriction();
},
methods: {
update: function(value)
{
FormBus.$emit('forminput', {'name' : this.name, 'value' : value});
},
updatemarkdown: function(markdown, url)
{
/* is called from child component medialib if file has been selected */
this.update(url);
this.getrestriction(url);
},
createmarkdown: function(url)
{
/* is called from child component medialib */
this.update(url);
},
openmedialib: function()
{
this.showmedialib = true;
},
deleteFile: function()
{
this.update('');
this.selectedrole = 'all';
},
getrestriction: function(url)
{
var filename = this.value;
if(url)
{
filename = url;
}
var myself = this;
myaxios.get('/api/v1/filerestrictions',{
params: {
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
'filename': filename,
}
})
.then(function (response) {
myself.userroles = ['all'];
myself.userroles = myself.userroles.concat(response.data.userroles);
myself.selectedrole = response.data.restriction;
})
.catch(function (error)
{
if(error.response)
{
}
});
},
updaterestriction: function()
{
myaxios.post('/api/v1/filerestrictions',{
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
'filename': this.value,
'role': this.selectedrole,
})
.then(function (response) {
})
.catch(function (error)
{
if(error.response)
{
}
});
},
onFileChange: function( e )
{
if(e.target.files.length > 0)
{
let uploadedFile = e.target.files[0];
let size = uploadedFile.size / 1024 / 1024;
if (size > this.maxsize)
{
publishController.errors.message = "The maximal size of a file is " + this.maxsize + " MB";
}
else
{
sharedself = this;
sharedself.load = true;
let reader = new FileReader();
reader.readAsDataURL(uploadedFile);
reader.onload = function(e) {
myaxios.post('/api/v1/file',{
'url': document.getElementById("path").value,
'file': e.target.result,
'name': uploadedFile.name,
'publish': true,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
})
.then(function (response) {
sharedself.load = false;
sharedself.selectedrole = 'all';
sharedself.update(response.data.info.url);
})
.catch(function (error)
{
sharedself.load = false;
if(error.response)
{
publishController.errors.message = error.response.data.errors;
}
});
}
}
}
}
}
})
const medialib = Vue.component('medialib', {
props: ['parentcomponent'],
template: '<div class="medialib">' +