1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-12 09:04:37 +02:00

Version 1.4.6: Change content, CSRF-notification, fix blox editor, alert for raw editor, cyanine optimizations

This commit is contained in:
trendschau
2021-06-03 13:33:12 +02:00
parent 156e2c6951
commit 14ea7c7995
22 changed files with 299 additions and 110 deletions

View File

@@ -76,6 +76,12 @@ class AuthController extends Controller
public function login(Request $request, Response $response)
{
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
/* log user attemps to authenticate */
$yaml = new WriteYaml();
$logins = $yaml->getYaml('settings/users', '.logins');

View File

@@ -325,7 +325,6 @@ class BlockApiController extends ContentController
{
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
}
/* parse markdown-file to content-array, if title parse title. */
if($this->params['block_id'] == 0)

View File

@@ -14,12 +14,19 @@ class FormController extends Controller
public function savePublicForm($request, $response, $args)
{
if($request->isPost())
{
{
$params = $request->getParams();
reset($params);
$pluginName = key($params);
$referer = $request->getHeader('HTTP_REFERER');
# check csrf protection
if($request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout. Please try again.');
return $response->withRedirect($referer[0]);
}
# simple bot check with honeypot
if(isset($params[$pluginName]['personal-mail']))
{

View File

@@ -67,7 +67,14 @@ class SettingsController extends Controller
public function saveSettings($request, $response, $args)
{
if($request->isPost())
{
{
if( $request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('settings.show'));
}
$settings = \Typemill\Settings::getUserSettings();
$defaultSettings = \Typemill\Settings::getDefaultSettings();
$params = $request->getParams();
@@ -378,7 +385,13 @@ class SettingsController extends Controller
public function saveThemes($request, $response, $args)
{
if($request->isPost())
{
{
if( $request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('themes.show'));
}
$userSettings = \Typemill\Settings::getUserSettings();
$params = $request->getParams();
$themeName = isset($params['theme']) ? $params['theme'] : false;
@@ -465,6 +478,13 @@ class SettingsController extends Controller
{
if($request->isPost())
{
if( $request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('plugins.show'));
}
$userSettings = \Typemill\Settings::getUserSettings();
$pluginSettings = array();
$userInput = $request->getParams();
@@ -764,6 +784,12 @@ class SettingsController extends Controller
{
if($request->isPost())
{
if( $request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('user.new'));
}
$params = $request->getParams();
$user = new User();
$validate = new Validation();
@@ -793,6 +819,12 @@ class SettingsController extends Controller
if($request->isPost())
{
if( $request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('user.account'));
}
$params = $request->getParams();
$userdata = $params['user'];
$user = new User();
@@ -886,7 +918,13 @@ class SettingsController extends Controller
public function deleteUser($request, $response, $args)
{
if($request->isPost())
{
{
if( $request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('user.account'));
}
$params = $request->getParams();
$validate = new Validation();
$user = new User();

View File

@@ -62,6 +62,12 @@ class SetupController extends Controller
{
if($request->isPost())
{
if( $request->getattribute('csrf_result') === false )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('setup.welcome'));
}
$params = $request->getParams();
$validate = new Validation();
$user = new User();

View File

@@ -19,8 +19,14 @@ class RestrictApiAccess
{
if(!isset($_SESSION['login']) || !isset($_SESSION['role']))
{
return $response->withJson(['errors' => ['access denied']], 403);
return $response->withJson(['data' => false, 'errors' => ['message' => 'You are probably logged out. Please login and try again.']], 403);
}
# check csrf protection
if( $request->getattribute('csrf_result') === false )
{
return $response->withJson(array('data' => false, 'errors' => ['message' => 'The form has a timeout. Please reload the page and try again.']), 403);
}
return $next($request, $response);
}
}

View File

@@ -226,7 +226,7 @@ h3{ font-size: 1em; margin: 0.6em 0 0.6em; text-transform: uppercase; font-weigh
}
section{
background: #FFFFFF;
box-shadow: 0 0 4px #ddd;
box-shadow: 0 4px 4px #ddd;
position: relative;
margin-bottom: 40px;
padding: 20px 5px 40px;
@@ -949,7 +949,7 @@ header a.button{
position: absolute;
width: 100%;
margin-top: -35px;
z-index: 1;
/* z-index: 1; */
}
.tab-button{
font-size: 0.9em;
@@ -968,7 +968,7 @@ header a.button{
outline: none;
}
section.tab{
z-index: -1;
/* z-index: -1; */
padding: 40px;
}
/********************
@@ -2358,16 +2358,18 @@ button.format-item.close:hover{
display: inline-block;
margin-top: 15px;
}
input.definitionTerm{
.definitionTerm{
width: 29%;
display: inline-block;
vertical-align: top;
}
.definitionDescription{
width: 60%;
display: inline-block;
vertical-align: top;
background: #fff;
}
textarea.definitionDescription{
width: 60%;
display: inline-block;
vertical-align: top;
.definitionRow input, .definitionRow textarea{
background: #fff;
}
button.addDL,

View File

@@ -20,29 +20,37 @@
{% endif %}
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
<a v-if="visual" href="{{ base_url }}/tm/content/raw{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('raw mode') }}</span><span class="mobile">{{ __('raw') }}</span></a>
<a v-if="raw" href="{{ base_url }}/tm/content/visual{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('visual mode') }}</span><span class="mobile">{{ __('visual') }}</span></a>
<a v-if="raw" @click="checkUnsafedContent(event)" href="{{ base_url }}/tm/content/visual{{ itemurl }}" class="button--secondary"><span class="desktop">{{ __('visual mode') }}</span><span class="mobile">{{ __('visual') }}</span></a>
{% endif %}
<a target="_blank" class="button--secondary" href="{{ item.urlAbs }}"><svg class="icon baseline icon-external-link"><use xlink:href="#icon-external-link"></use></svg></a>
</div>
<transition name="fade">
<div v-if="modalWindow" id="modalWindow" class="modalWindow">
<div class="modalInner">
<div @click="hideModal" id="closeModal" class="closeModal">X</div>
{% if (acl.isAllowed(get_role(), 'content', 'delete')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'delete')) ) %}
<div v-if="modalType == 'delete'">
<h2>{{ __('Delete page') }}</h2>
<p>{{ __('Do you really want to delete this page') }} {{ __('Please confirm') }}</p>
<button @click.prevent="deleteArticle" class="large alert" :class="deleteResult" :disabled="deleteDisabled">{{ __('Delete Page') }}</button>
</div>
{% endif %}
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
<div v-if="modalType == 'discard'">
<h2>{{ __('Discard Changes') }}</h2>
<p>{{ __('Do you want to discard your changes and set the content back to the live version') }}</p>
<button @click.prevent="discardDraft" class="large fullwidth" :class="discardResult" :disabled="publishDisabled">{{ __('Discard Changes') }}</button>
</div>
{% endif %}
</div>
<transition name="fade">
<div v-if="modalWindow" id="modalWindow" class="modalWindow">
<div class="modalInner">
<div @click="hideModal" id="closeModal" class="closeModal">X</div>
{% if (acl.isAllowed(get_role(), 'content', 'delete')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'delete')) ) %}
<div v-if="modalType == 'delete'">
<h2>{{ __('Delete page') }}</h2>
<p>{{ __('Do you really want to delete this page') }} {{ __('Please confirm') }}</p>
<button @click.prevent="deleteArticle" class="large alert" :class="deleteResult" :disabled="deleteDisabled">{{ __('Delete Page') }}</button>
</div>
{% endif %}
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
<div v-if="modalType == 'discard'">
<h2>{{ __('Discard Changes') }}</h2>
<p>{{ __('Do you want to discard your changes and set the content back to the live version') }}</p>
<button @click.prevent="discardDraft" class="large fullwidth" :class="discardResult" :disabled="publishDisabled">{{ __('Discard Changes') }}</button>
</div>
{% endif %}
{% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %}
<div v-if="modalType == 'unsafed'">
<h2>{{ __('Save Your Changes') }}</h2>
<p>{{ __('You have unsaved changes. Go back and save your changes. If you proceed to the visual mode now, then you will loose your changes.') }}</p>
<button @click="hideModal" class="large fullwidth">{{ __('Go back and save') }}</button>
<a class="w-100 tc ma2 tm-red hover-bg-tm-red hover-white" href="{{ base_url }}/tm/content/visual{{ itemurl }}">{{ __('go to visual mode') }}</a>
</div>
{% endif %}
</div>
</transition>
</div>
</transition>
</div>

View File

@@ -718,33 +718,60 @@ const codeComponent = Vue.component('code-component', {
template: '<div>' +
'<input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown" />' +
'<div class="contenttype"><svg class="icon icon-embed"><use xlink:href="#icon-embed"></use></svg></div>' +
'<div class="w-100 pa2 white bg-tm-green dib">' +
'<label for="language" class="w-10">{{ \'Language\'|translate }}: </label>' +
'<input class="pa1 w-90 min-h-100" name="language" type="text" v-model="language" :disabled="disabled" @input="createlanguage">' +
'</div>' +
'<textarea class="mdcontent" ref="markdown" v-model="codeblock" :disabled="disabled" @input="createmarkdown"></textarea>' +
'</div>',
data: function(){
return {
codeblock: ''
prefix: '```',
language: '',
codeblock: '',
}
},
mounted: function(){
this.$refs.markdown.focus();
if(this.compmarkdown)
{
var codeblock = this.compmarkdown.replace("````\n", "");
codeblock = codeblock.replace("```\n", "");
codeblock = codeblock.replace("\n````", "");
codeblock = codeblock.replace("\n```", "");
codeblock = codeblock.replace("\n\n", "\n");
this.codeblock = codeblock;
var codelines = this.compmarkdown.split(/\r\n|\n\r|\n|\r/);
var linelength = codelines.length;
var codeblock = '';
for(i=0;i<linelength;i++)
{
if(codelines[i].substring(0,3) == '```')
{
if(i==0)
{
var prefixlength = (codelines[i].match(/`/g)).length;
this.prefix = codelines[i].slice(0, prefixlength);
this.language = codelines[i].replace(/`/g, '');
}
}
else
{
this.codeblock += codelines[i] + "\n";
}
}
this.codeblock = this.codeblock.replace(/^\s+|\s+$/g, '');
}
this.$nextTick(function () {
autosize(document.querySelectorAll('textarea'));
this.$parent.setComponentSize();
});
},
methods: {
createlanguage: function()
{
var codeblock = this.prefix + this.language + '\n' + this.codeblock + '\n' + this.prefix;
this.updatemarkdown(codeblock);
},
createmarkdown: function(event)
{
this.codeblock = event.target.value;
var codeblock = '````\n' + event.target.value + '\n````';
var codeblock = this.prefix + this.language + '\n' + this.codeblock + '\n' + this.prefix;
this.updatemarkdown(codeblock);
},
updatemarkdown: function(codeblock)
@@ -1243,43 +1270,76 @@ const definitionComponent = Vue.component('definition-component', {
}
},
template: '<div class="definitionList">' +
'<div class="contenttype"><svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg></div>' +
'<draggable v-model="definitionList" :animation="150" @end="moveDefinition">' +
'<div class="definitionRow" v-for="(definition, dindex) in definitionList" :key="definition.id">' +
'<svg class="icon icon-arrows-v"><use xlink:href="#icon-arrows-v"></use></svg>' +
'<input type="text" class="definitionTerm" v-bind:placeholder="\'term\'|translate" :value="definition.term" :disabled="disabled" @input="updateterm($event,dindex)" @blur="updateMarkdown">' +
'<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>' +
'<textarea class="definitionDescription" v-bind:placeholder="\'description\'|translate" v-html="definition.description" :disabled="disabled" @input="updatedescription($event, dindex)" @keydown.13.prevent="enter" @blur="updateMarkdown"></textarea>' +
'<button class="delDL" @click.prevent="deleteDefinition(dindex)"><svg class="icon icon-minus"><use xlink:href="#icon-minus"></use></svg></button>' +
'</div>' +
'</draggable>' +
'<button class="addDL" @click.prevent="addDefinition()"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg> {{ \'add definition\'|translate }}</button>' +
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
'</div>',
'<div class="contenttype"><svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg></div>' +
'<draggable v-model="definitionList" :animation="150" @end="moveDefinition">' +
'<div class="definitionRow relative" v-for="(definition, dtindex) in definitionList" :key="definition.id">' +
'<div class="definitionTerm">' +
'<svg class="icon icon-arrows-v"><use xlink:href="#icon-arrows-v"></use></svg>' +
'<input type="text" class="w-90" v-bind:placeholder="\'term\'|translate" :value="definition.term" :disabled="disabled" @input="updateterm($event,dtindex)" @blur="updateMarkdown">' +
'</div>' +
'<div class="dib w-60">' +
'<div class="flex" v-for="(description,ddindex) in definition.descriptions">' +
'<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>' +
'<textarea class="w-90" v-bind:placeholder="\'description\'|translate" v-html="definition.descriptions[ddindex]" :disabled="disabled" @input="updatedescription($event, dtindex, ddindex)" @keydown.13.prevent="enter" @blur="updateMarkdown"></textarea>' +
'<button title="delete description" class="ba bw1 b--tm-gray white bg-tm-red w2 h2 f7 lh-solid dim" @click.prevent="deleteItem(dtindex,ddindex)"><svg class="icon icon-minus baseline"><use xlink:href="#icon-minus"></use></svg></button>' +
'</div>' +
'<button title="add description" class="ml3 ba bw1 b--tm-gray white bg-tm-green w2 h2 f7 lh-solid dim" @click.prevent="addItem(dtindex)"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg></button>' +
'</div>' +
'<button title="delete definition" class="absolute top-1 right-2 bottom-1 tc pa2 ba br1 b--tm-red tm-red bg-tm-gray hover-bg-tm-red hover-white" @click.prevent="deleteDefinition(dtindex)">x</button>' +
'<hr></div>' +
'</draggable>' +
'<button class="ml3 mb3 mr1 ba bw1 b--tm-gray white bg-tm-green w2 h2 f7 lh-solid dim" @click.prevent="addDefinition()"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg></button><span class="f6">{{ \'add definition\'|translate }}</span>' +
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
'</div>',
mounted: function(){
if(this.compmarkdown)
{
var definitionList = this.compmarkdown.replace("\r\n", "\n");
definitionList = definitionList.replace("\r", "\n");
definitionList = definitionList.split("\n\n");
var definitionList = this.compmarkdown.replace("\r\n", "\n");
definitionList = definitionList.replace("\r", "\n");
definitionList = definitionList.split("\n\n");
for(var i=0; i < definitionList.length; i++)
{
var definition = definitionList[i].split("\n");
var term = definition[0];
var description = definition[1];
var id = i;
var definitionBlock = definitionList[i].split("\n");
var term = definitionBlock[0];
var descriptions = [];
var description = false;
if(description && description.substring(0, 2) == ": ")
/* parse one or more descriptions */
for(var y=0; y < definitionBlock.length; y++)
{
this.definitionList.push({'term': term ,'description': description.substring(2), 'id': id});
if(y == 0)
{
continue;
}
if(definitionBlock[y].substring(0, 2) == ": ")
{
/* if there is another description in the loop, then push that first then start a new one */
if(description)
{
descriptions.push(description);
}
var cleandefinition = definitionBlock[y].substr(1).trim();
var description = cleandefinition;
}
else
{
description += "\n" + definitionBlock[y];
}
}
if(description)
{
descriptions.push(description);
}
this.definitionList.push({'term': term ,'descriptions': descriptions, 'id': i});
}
console.info(this.definitionList);
}
else
{
this.definitionList.push({'term': '', 'description': '', 'id': 0});
this.addDefinition();
}
},
methods: {
@@ -1287,23 +1347,31 @@ const definitionComponent = Vue.component('definition-component', {
{
return false;
},
updateterm: function(event, dindex)
updateterm: function(event, dtindex)
{
this.definitionList[dindex].term = event.target.value;
this.definitionList[dtindex].term = event.target.value;
},
updatedescription: function(event, dindex)
updatedescription: function(event, dtindex, ddindex)
{
this.definitionList[dindex].description = event.target.value;
this.definitionList[dtindex].descriptions[ddindex] = event.target.value;
},
addDefinition: function()
{
var id = this.definitionList.length;
this.definitionList.push({'term': '', 'description': '', 'id': id});
this.definitionList.push({'term': '', 'descriptions': [''], 'id': id});
},
deleteDefinition: function(dtindex)
{
this.definitionList.splice(dtindex,1);
this.updateMarkdown();
},
deleteDefinition: function(dindex)
addItem: function(dtindex)
{
this.definitionList.splice(dindex,1);
this.definitionList[dtindex].descriptions.push('');
},
deleteItem: function(dtindex,ddindex)
{
this.definitionList[dtindex].descriptions.splice(ddindex,1);
this.updateMarkdown();
},
moveDefinition: function(evt)
@@ -1312,11 +1380,18 @@ const definitionComponent = Vue.component('definition-component', {
},
updateMarkdown: function()
{
var length = this.definitionList.length;
var dllength = this.definitionList.length;
var markdown = '';
for(i = 0; i < length; i++)
for(i = 0; i < dllength; i++)
{
markdown = markdown + this.definitionList[i].term + "\n: " + this.definitionList[i].description + "\n\n";
markdown = markdown + this.definitionList[i].term;
var ddlength = this.definitionList[i].descriptions.length;
for(y = 0; y < ddlength; y++)
{
markdown = markdown + "\n: " + this.definitionList[i].descriptions[y];
}
markdown = markdown + "\n\n";
}
this.$emit('updatedMarkdown', markdown);
},

View File

@@ -193,6 +193,14 @@ let publishController = new Vue({
self.handleErrors(error);
});
},
checkUnsafedContent(event){
console.info(this.draftDisabled);
if(!this.draftDisabled)
{
event.preventDefault();
this.showModal('unsafed');
}
},
showModal: function(type){
this.modalType = type;
this.modalWindow = true;

View File

@@ -210,7 +210,12 @@ foreach($session_segments as $segment)
{
$guard = new \Slim\Csrf\Guard();
$guard->setPersistentTokenMode(true);
$guard->setfailurecallable(function ($request, $response, $next)
{
$request = $request->withattribute("csrf_result", false);
return $next($request, $response);
});
return $guard;
};