Merge pull request #682 from MrPetovan/bug/5341-advancedcontentfilter-honors-csp

Advancedcontentfilter honors CSP
This commit is contained in:
Michael Vogel 2018-08-04 16:48:56 +02:00 committed by GitHub
commit e79d738437
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 238 additions and 213 deletions

View file

@ -1,122 +0,0 @@
$.ajaxSetup({headers: {'X-CSRF-Token': document.querySelector('#csrf').getAttribute('value')}});
$.extend({
ajaxJSON: function(method, url, data) {
return $.ajax({
type: method.toUpperCase(),
url: url,
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
dataType: 'json'
});
}
});
new Vue({
el: '#rules',
data: {
showModal: false,
errorMessage: '',
editedIndex: null,
rule: {id: '', name: '', expression: '', created: ''},
rules: existingRules || [],
itemUrl: '',
itemJson: ''
},
watch: {
showModal: function () {
if (this.showModal) {
$(this.$refs.vuemodal).modal('show');
} else {
$(this.$refs.vuemodal).modal('hide');
}
}
},
methods: {
resetForm: function() {
this.rule = {id: '', name: '', expression: '', created: ''};
this.showModal = false;
this.editedIndex = null;
},
addRule: function () {
if (this.rule.name.trim()) {
this.errorMessage = '';
var self = this;
$.ajaxJSON('post', '/advancedcontentfilter/api/rules', this.rule)
.then(function (responseJSON) {
self.rules.push(responseJSON.rule);
self.resetForm();
}, function (response) {
self.errorMessage = response.responseJSON.message;
});
}
},
editRule: function (rule) {
this.editedIndex = this.rules.indexOf(rule);
this.rule = Object.assign({}, rule);
this.showModal = true;
},
saveRule: function (rule) {
this.errorMessage = '';
var self = this;
$.ajaxJSON('put', '/advancedcontentfilter/api/rules/' + rule.id, rule)
.then(function () {
self.rules[self.editedIndex] = rule;
self.resetForm();
}, function (response) {
self.errorMessage = response.responseJSON.message;
});
},
toggleActive: function (rule) {
var previousValue = this.rules[this.rules.indexOf(rule)].active;
var newValue = Math.abs(parseInt(rule.active) - 1);
this.rules[this.rules.indexOf(rule)].active = newValue;
var self = this;
$.ajaxJSON('put', '/advancedcontentfilter/api/rules/' + rule.id, {'active': newValue})
.fail(function (response) {
self.rules[self.rules.indexOf(rule)].active = previousValue;
console.log(response.responseJSON.message);
});
},
deleteRule: function (rule) {
if (confirm('Are you sure you want to delete this rule?')) {
var self = this;
$.ajaxJSON('delete', '/advancedcontentfilter/api/rules/' + rule.id)
.then(function () {
self.rules.splice(self.rules.indexOf(rule), 1);
}, function (response) {
console.log(response.responseJSON.message);
});
}
},
showVariables: function () {
var urlParts = this.itemUrl.split('/');
var guid = urlParts[urlParts.length - 1];
this.itemJson = '';
var self = this;
$.ajaxJSON('get', '/advancedcontentfilter/api/variables/' + guid)
.then(function (responseJSON) {
self.itemJson = responseJSON.variables;
}, function (response) {
self.itemJson = response.responseJSON.message;
});
return false;
}
}
});

View file

@ -213,7 +213,7 @@ function advancedcontentfilter_content(App $a)
'$title' => L10n::t('Advanced Content Filter'), '$title' => L10n::t('Advanced Content Filter'),
'$add_a_rule' => L10n::t('Add a Rule'), '$add_a_rule' => L10n::t('Add a Rule'),
'$help' => L10n::t('Help'), '$help' => L10n::t('Help'),
'$advanced_content_filter_intro' => L10n::t('Add and manage your personal content filter rules in this screen. Rules have a name and an arbitrary expression that will be matched against post data. For a complete reference of the available operations and variables, check the <a href="advancedcontentfilter/help">help page</a>.'), '$advanced_content_filter_intro' => addslashes(L10n::t('Add and manage your personal content filter rules in this screen. Rules have a name and an arbitrary expression that will be matched against post data. For a complete reference of the available operations and variables, check the <a href="advancedcontentfilter/help">help page</a>.')),
'$your_rules' => L10n::t('Your rules'), '$your_rules' => L10n::t('Your rules'),
'$no_rules' => L10n::t('You have no rules yet! Start adding one by clicking on the button above next to the title.'), '$no_rules' => L10n::t('You have no rules yet! Start adding one by clicking on the button above next to the title.'),
'$disabled' => L10n::t('Disabled'), '$disabled' => L10n::t('Disabled'),

View file

@ -1,98 +1,149 @@
<div id="adminpage"> <div id="adminpage">
<style>[v-cloak] { display: none; }</style> <style>[v-cloak] { display: none; }</style>
<div id="rules"> <div id="rules"></div>
<p><a href="settings/addon">🔙 {{$backtosettings}}</a></p>
<h1>
{{$title}}
<a href="{{$baseurl}}/advancedcontentfilter/help" class="btn btn-default btn-sm" title="{{$help}}">
<i class="fa fa-question fa-2x" aria-hidden="true"></i>
</a>
</h1>
<div>{{$advanced_content_filter_intro}}</div>
<h2>
{{$your_rules}}
<button class="btn btn-primary btn-sm" title="{{$add_a_rule}}" @click="showModal = true">
<i class="fa fa-plus fa-2x" aria-hidden="true"></i>
</button>
</h2>
<div v-if="rules.length === 0" v-cloak>
{{$no_rules}}
</div>
<ul class="list-group" v-cloak>
<li class="list-group-item" v-for="rule in rules">
<p class="pull-right">
<button type="button" class="btn btn-xs btn-primary" v-on:click="toggleActive(rule)" aria-label="{{$disable_this_rule}}" title="{{$disable_this_rule}}" v-if="parseInt(rule.active)">
<i class="fa fa-toggle-on" aria-hidden="true"></i> {{$enabled}}
</button>
<button type="button" class="btn btn-xs btn-default" v-on:click="toggleActive(rule)" aria-label="{{$enable_this_rule}}" title="{{$enable_this_rule}}" v-else>
<i class="fa fa-toggle-off" aria-hidden="true"></i> {{$disabled}}
</button>
<button type="button" class="btn btn-xs btn-primary" v-on:click="editRule(rule)" aria-label="{{$edit_this_rule}}" title="{{$edit_this_rule}}">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-xs btn-default" v-on:click="deleteRule(rule)" aria-label="{{$delete_this_rule}}" title="{{$delete_this_rule}}">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
</p>
<h3 class="list-group-item-heading">
{{$rule}} #{{ rule.id }}: {{ rule.name }}
</h3>
<pre class="list-group-item-text" v-if="rule.expression">{{ rule.expression }}</pre>
</li>
</ul>
<div class="modal fade" ref="vuemodal" tabindex="-1" role="dialog" v-cloak>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
{{if $current_theme == 'frio'}}
<button type="button" class="close" data-dismiss="modal" aria-label="{{$close}}" @click="showModal = false"><span aria-hidden="true">&times;</span></button>
{{/if}}
<h3 v-if="rule.id">{{$edit_the_rule}} "{{ rule.name }}"</h3>
<h3 v-if="!rule.id">{{$add_a_rule}}</h3>
</div>
<div class="modal-body">
<form>
<input type="hidden" name="form_security_token" id="csrf" value="{{$form_security_token}}" />
<div class="alert alert-danger" role="alert" v-if="errorMessage">{{ errorMessage }}</div>
<div class="form-group">
<input class="form-control" placeholder="{{$rule_name}}" v-model="rule.name">
</div>
<div class="form-group">
<input class="form-control" placeholder="{{$rule_expression}}" v-model="rule.expression">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" aria-label="Close" @click="resetForm()">{{$cancel}}</button>
<button slot="button" class="btn btn-primary" type="button" v-if="rule.id" v-on:click="saveRule(rule)">{{$save_this_rule}}</button>
<button slot="button" class="btn btn-primary" type="button" v-if="!rule.id" v-on:click="addRule()">{{$add_a_rule}}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<form class="form-inline" v-on:submit.prevent="showVariables()">
<fieldset>
<legend>Show post variables</legend>
<div class="form-group" style="width: 50%">
<label for="itemUrl" class="sr-only">Post URL or item guid</label>
<input class="form-control" id="itemUrl" placeholder="Post URL or item guid" v-model="itemUrl" style="width: 100%">
</div>
<button type="submit" class="btn btn-primary">Show Variables</button>
</fieldset>
</form>
<pre v-cloak>
{{ itemJson }}
</pre>
</div>
<script> var existingRules = {{$rules}};</script> <script> var existingRules = {{$rules}};</script>
<!-- JS --> <!-- JS -->
<script src="{{$baseurl}}/view/asset/vue/dist/vue.min.js"></script> <script src="{{$baseurl}}/view/asset/vue/dist/vue.min.js"></script>
<script src="{{$baseurl}}/addon/advancedcontentfilter/advancedcontentfilter.js"></script> <script>
$.extend({
ajaxJSON: function(method, url, data) {
return $.ajax({
type: method.toUpperCase(),
url: url,
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
dataType: 'json'
});
}
});
new Vue({
el: '#rules',
data: {
showModal: false,
errorMessage: '',
editedIndex: null,
rule: {id: '', name: '', expression: '', created: ''},
rules: existingRules || [],
itemUrl: '',
itemJson: ''
},
watch: {
showModal: function () {
if (this.showModal) {
$(this.$refs.vuemodal).modal('show');
} else {
$(this.$refs.vuemodal).modal('hide');
}
}
},
mounted: function() {
$.ajaxSetup({headers: {'X-CSRF-Token': document.querySelector('#csrf').getAttribute('value')}});
},
methods: {
resetForm: function() {
this.rule = {id: '', name: '', expression: '', created: ''};
this.showModal = false;
this.editedIndex = null;
},
addRule: function () {
if (this.rule.name.trim()) {
this.errorMessage = '';
var self = this;
$.ajaxJSON('post', '/advancedcontentfilter/api/rules', this.rule)
.then(function (responseJSON) {
self.rules.push(responseJSON.rule);
self.resetForm();
}, function (response) {
self.errorMessage = response.responseJSON.message;
});
}
},
editRule: function (rule) {
this.editedIndex = this.rules.indexOf(rule);
this.rule = Object.assign({}, rule);
this.showModal = true;
},
saveRule: function (rule) {
this.errorMessage = '';
var self = this;
$.ajaxJSON('put', '/advancedcontentfilter/api/rules/' + rule.id, rule)
.then(function () {
self.rules[self.editedIndex] = rule;
self.resetForm();
}, function (response) {
self.errorMessage = response.responseJSON.message;
});
},
toggleActive: function (rule) {
var previousValue = this.rules[this.rules.indexOf(rule)].active;
var newValue = Math.abs(parseInt(rule.active) - 1);
this.rules[this.rules.indexOf(rule)].active = newValue;
var self = this;
$.ajaxJSON('put', '/advancedcontentfilter/api/rules/' + rule.id, {'active': newValue})
.fail(function (response) {
self.rules[self.rules.indexOf(rule)].active = previousValue;
console.log(response.responseJSON.message);
});
},
deleteRule: function (rule) {
if (confirm('Are you sure you want to delete this rule?')) {
var self = this;
$.ajaxJSON('delete', '/advancedcontentfilter/api/rules/' + rule.id)
.then(function () {
self.rules.splice(self.rules.indexOf(rule), 1);
}, function (response) {
console.log(response.responseJSON.message);
});
}
},
showVariables: function () {
var urlParts = this.itemUrl.split('/');
var guid = urlParts[urlParts.length - 1];
this.itemJson = '';
var self = this;
$.ajaxJSON('get', '/advancedcontentfilter/api/variables/' + guid)
.then(function (responseJSON) {
self.itemJson = responseJSON.variables;
}, function (response) {
self.itemJson = response.responseJSON.message;
});
return false;
}
},
// These render function have been compiled from templates/vue_dom.tpl, check this file out for instructions
render: function () {
with(this){return _c('div',{attrs:{"id":"rules"}},[_m(0),_m(1),_c('div',[_v("{{$advanced_content_filter_intro}}")]),_c('h2',[_v("{{$your_rules}}"),_c('button',{staticClass:"btn btn-primary btn-sm",attrs:{"title":"{{$add_a_rule}}"},on:{"click":function($event){showModal = true}}},[_c('i',{staticClass:"fa fa-plus fa-2x",attrs:{"aria-hidden":"true"}})])]),(rules.length === 0)?_c('div',{},[_v("{{$no_rules}}")]):_e(),_c('ul',{staticClass:"list-group"},_l((rules),function(rule){return _c('li',{staticClass:"list-group-item"},[_c('p',{staticClass:"pull-right"},[(parseInt(rule.active))?_c('button',{staticClass:"btn btn-xs btn-primary",attrs:{"type":"button","aria-label":"{{$disable_this_rule}}","title":"{{$disable_this_rule}}"},on:{"click":function($event){toggleActive(rule)}}},[_c('i',{staticClass:"fa fa-toggle-on",attrs:{"aria-hidden":"true"}}),_v(" {{$enabled}}")]):_c('button',{staticClass:"btn btn-xs btn-default",attrs:{"type":"button","aria-label":"{{$enable_this_rule}}","title":"{{$enable_this_rule}}"},on:{"click":function($event){toggleActive(rule)}}},[_c('i',{staticClass:"fa fa-toggle-off",attrs:{"aria-hidden":"true"}}),_v(" {{$disabled}}")]),_c('button',{staticClass:"btn btn-xs btn-primary",attrs:{"type":"button","aria-label":"{{$edit_this_rule}}","title":"{{$edit_this_rule}}"},on:{"click":function($event){editRule(rule)}}},[_c('i',{staticClass:"fa fa-pencil",attrs:{"aria-hidden":"true"}})]),_c('button',{staticClass:"btn btn-xs btn-default",attrs:{"type":"button","aria-label":"{{$delete_this_rule}}","title":"{{$delete_this_rule}}"},on:{"click":function($event){deleteRule(rule)}}},[_c('i',{staticClass:"fa fa-trash-o",attrs:{"aria-hidden":"true"}})])]),_c('h3',{staticClass:"list-group-item-heading"},[_v("{{$rule}} #"+_s(rule.id)+": "+_s(rule.name))]),(rule.expression)?_c('pre',{staticClass:"list-group-item-text"},[_v(_s(rule.expression))]):_e()])})),_c('div',{ref:"vuemodal",staticClass:"modal fade",attrs:{"tabindex":"-1","role":"dialog"}},[_c('div',{staticClass:"modal-dialog",attrs:{"role":"document"}},[_c('div',{staticClass:"modal-content"},[_c('div',{staticClass:"modal-header"},[_c('button',{staticClass:"close",attrs:{"type":"button","data-dismiss":"modal","aria-label":"{{$close}}"},on:{"click":function($event){showModal = false}}},[_c('span',{attrs:{"aria-hidden":"true"}},[_v("×")])]),(rule.id)?_c('h3',[_v("{{$edit_the_rule}} \""+_s(rule.name)+"\"")]):_e(),(!rule.id)?_c('h3',[_v("{{$add_a_rule}}")]):_e()]),_c('div',{staticClass:"modal-body"},[_c('form',[_c('input',{attrs:{"type":"hidden","name":"form_security_token","id":"csrf","value":"{{$form_security_token}}"}}),(errorMessage)?_c('div',{staticClass:"alert alert-danger",attrs:{"role":"alert"}},[_v(_s(errorMessage))]):_e(),_c('div',{staticClass:"form-group"},[_c('input',{directives:[{name:"model",rawName:"v-model",value:(rule.name),expression:"rule.name"}],staticClass:"form-control",attrs:{"placeholder":"{{$rule_name}}"},domProps:{"value":(rule.name)},on:{"input":function($event){if($event.target.composing)return;$set(rule, "name", $event.target.value)}}})]),_c('div',{staticClass:"form-group"},[_c('input',{directives:[{name:"model",rawName:"v-model",value:(rule.expression),expression:"rule.expression"}],staticClass:"form-control",attrs:{"placeholder":"{{$rule_expression}}"},domProps:{"value":(rule.expression)},on:{"input":function($event){if($event.target.composing)return;$set(rule, "expression", $event.target.value)}}})])])]),_c('div',{staticClass:"modal-footer"},[_c('button',{staticClass:"btn btn-default",attrs:{"type":"button","data-dismiss":"modal","aria-label":"Close"},on:{"click":function($event){resetForm()}}},[_v("{{$cancel}}")]),(rule.id)?_c('button',{staticClass:"btn btn-primary",attrs:{"slot":"button","type":"button"},on:{"click":function($event){saveRule(rule)}},slot:"button"},[_v("{{$save_this_rule}}")]):_e(),(!rule.id)?_c('button',{staticClass:"btn btn-primary",attrs:{"slot":"button","type":"button"},on:{"click":function($event){addRule()}},slot:"button"},[_v("{{$add_a_rule}}")]):_e()])])])]),_c('form',{staticClass:"form-inline",on:{"submit":function($event){$event.preventDefault();showVariables()}}},[_c('fieldset',[_c('legend',[_v("Show post variables")]),_c('div',{staticClass:"form-group",staticStyle:{"width":"50%"}},[_c('label',{staticClass:"sr-only",attrs:{"for":"itemUrl"}},[_v("Post URL or item guid")]),_c('input',{directives:[{name:"model",rawName:"v-model",value:(itemUrl),expression:"itemUrl"}],staticClass:"form-control",staticStyle:{"width":"100%"},attrs:{"id":"itemUrl","placeholder":"Post URL or item guid"},domProps:{"value":(itemUrl)},on:{"input":function($event){if($event.target.composing)return;itemUrl=$event.target.value}}})]),_c('button',{staticClass:"btn btn-primary",attrs:{"type":"submit"}},[_v("Show Variables")])])]),_c('pre',{},[_v(_s(itemJson))])])}
},
staticRenderFns: [
function () {
with(this){return _c('p',[_c('a',{attrs:{"href":"settings/addon"}},[_v("🔙 {{$backtosettings}}")])])}
},
function () {
with(this){return _c('h1',[_v("{{$title}}"),_c('a',{staticClass:"btn btn-default btn-sm",attrs:{"href":"{{$baseurl}}/advancedcontentfilter/help","title":"{{$help}}"}},[_c('i',{staticClass:"fa fa-question fa-2x",attrs:{"aria-hidden":"true"}})])])}
}
]
});
</script>
</div> </div>

View file

@ -0,0 +1,96 @@
<!--
This the the source HTML for the render functions defined in settings.tpl
This file is only for reference only and editing it won't change the addon display.
Here's the workflow to change the actual display:
1. Edit this file
2. Run it through https://vuejs.org/v2/guide/render-function.html#Template-Compilation
3. In the results, replace ##$ by {{$ and ## by }} in order to restore smarty variable interpolation
4. Replace the render and staticRenderFns members in settings.tpl by the contents of the output anonymous() functions
-->
<div id="rules">
<p><a href="settings/addon">🔙 ##$backtosettings##</a></p>
<h1>
##$title##
<a href="##$baseurl##/advancedcontentfilter/help" class="btn btn-default btn-sm" title="##$help##">
<i class="fa fa-question fa-2x" aria-hidden="true"></i>
</a>
</h1>
<div>##$advanced_content_filter_intro##</div>
<h2>
##$your_rules##
<button class="btn btn-primary btn-sm" title="##$add_a_rule##" @click="showModal = true">
<i class="fa fa-plus fa-2x" aria-hidden="true"></i>
</button>
</h2>
<div v-if="rules.length === 0" v-cloak>
##$no_rules##
</div>
<ul class="list-group" v-cloak>
<li class="list-group-item" v-for="rule in rules">
<p class="pull-right">
<button type="button" class="btn btn-xs btn-primary" v-on:click="toggleActive(rule)" aria-label="##$disable_this_rule##" title="##$disable_this_rule##" v-if="parseInt(rule.active)">
<i class="fa fa-toggle-on" aria-hidden="true"></i> ##$enabled##
</button>
<button type="button" class="btn btn-xs btn-default" v-on:click="toggleActive(rule)" aria-label="##$enable_this_rule##" title="##$enable_this_rule##" v-else>
<i class="fa fa-toggle-off" aria-hidden="true"></i> ##$disabled##
</button>
<button type="button" class="btn btn-xs btn-primary" v-on:click="editRule(rule)" aria-label="##$edit_this_rule##" title="##$edit_this_rule##">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-xs btn-default" v-on:click="deleteRule(rule)" aria-label="##$delete_this_rule##" title="##$delete_this_rule##">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
</p>
<h3 class="list-group-item-heading">
##$rule## #{{ rule.id }}: {{ rule.name }}
</h3>
<pre class="list-group-item-text" v-if="rule.expression">{{ rule.expression }}</pre>
</li>
</ul>
<div class="modal fade" ref="vuemodal" tabindex="-1" role="dialog" v-cloak>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="##$close##" @click="showModal = false"><span aria-hidden="true">&times;</span></button>
<h3 v-if="rule.id">##$edit_the_rule## "{{ rule.name }}"</h3>
<h3 v-if="!rule.id">##$add_a_rule##</h3>
</div>
<div class="modal-body">
<form>
<input type="hidden" name="form_security_token" id="csrf" value="##$form_security_token##" />
<div class="alert alert-danger" role="alert" v-if="errorMessage">{{ errorMessage }}</div>
<div class="form-group">
<input class="form-control" placeholder="##$rule_name##" v-model="rule.name">
</div>
<div class="form-group">
<input class="form-control" placeholder="##$rule_expression##" v-model="rule.expression">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" aria-label="Close" @click="resetForm()">##$cancel##</button>
<button slot="button" class="btn btn-primary" type="button" v-if="rule.id" v-on:click="saveRule(rule)">##$save_this_rule##</button>
<button slot="button" class="btn btn-primary" type="button" v-if="!rule.id" v-on:click="addRule()">##$add_a_rule##</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<form class="form-inline" v-on:submit.prevent="showVariables()">
<fieldset>
<legend>Show post variables</legend>
<div class="form-group" style="width: 50%">
<label for="itemUrl" class="sr-only">Post URL or item guid</label>
<input class="form-control" id="itemUrl" placeholder="Post URL or item guid" v-model="itemUrl" style="width: 100%">
</div>
<button type="submit" class="btn btn-primary">Show Variables</button>
</fieldset>
</form>
<pre v-cloak>
{{ itemJson }}
</pre>
</div>