[advancedcontentfilter] Add addon files

- Add hooks
- Add mini-API module powered by Slim
- Add addon settings page powered by VueJS
- Add translation strings
- Add help page
This commit is contained in:
Hypolite Petovan 2018-04-16 22:21:51 -04:00
parent 20862be7d0
commit b02724f867
9 changed files with 1396 additions and 0 deletions

View File

@ -0,0 +1,24 @@
Copyright (c) 2011-2018 Hypolite Petovan
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of Friendica nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL FRIENDICA BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,11 @@
Advanced Content Filter
=======================
Main author Hypolite Petovan.
## License
The _Advanced Content Filter_ addon is licensed under the [3-clause BSD license][2] see the LICENSE file in the addons directory.
[1]: http://opensource.org/licenses/BSD-3-Clause

View File

@ -0,0 +1,113 @@
Vue.http.headers.common['X-CSRF-Token'] = document.querySelector('#csrf').getAttribute('value');
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');
}
}
},
//created: function () {
// this.fetchRules();
//},
methods: {
resetForm: function() {
this.rule = {id: '', name: '', expression: '', created: ''};
this.showModal = false;
this.editedIndex = null;
},
//fetchRules: function () {
// this.$http.get('/advancedcontentfilter/api/rules')
// .then(function (response) {
// this.rules = response.body;
// }, function (err) {
// console.log(err);
// });
//},
addRule: function () {
if (this.rule.name.trim()) {
this.errorMessage = '';
this.$http.post('/advancedcontentfilter/api/rules', this.rule)
.then(function (res) {
this.rules.push(res.body.rule);
this.resetForm();
}, function (err) {
this.errorMessage = err.body.message;
});
}
},
editRule: function (rule) {
this.editedIndex = this.rules.indexOf(rule);
this.rule = Object.assign({}, rule);
this.showModal = true;
},
saveRule: function (rule) {
this.errorMessage = '';
this.$http.put('/advancedcontentfilter/api/rules/' + rule.id, rule)
.then(function (res) {
this.rules[this.editedIndex] = rule;
this.resetForm();
}, function (err) {
this.errorMessage = err.body.message;
});
},
toggleActive: function (rule) {
this.$http.put('/advancedcontentfilter/api/rules/' + rule.id, {'active': Math.abs(parseInt(rule.active) - 1)})
.then(function (res) {
this.rules[this.rules.indexOf(rule)].active = Math.abs(parseInt(rule.active) - 1);
}, function (err) {
console.log(err);
});
},
deleteRule: function (rule) {
if (confirm('Are you sure you want to delete this rule?')) {
this.$http.delete('/advancedcontentfilter/api/rules/' + rule.id)
.then(function (res) {
this.rules.splice(this.rules.indexOf(rule), 1);
}, function (err) {
console.log(err);
});
}
},
showVariables: function () {
var guid = '';
var urlParts = this.itemUrl.split('/');
guid = urlParts[urlParts.length - 1];
this.$http.get('/advancedcontentfilter/api/variables/' + guid)
.then(function (response) {
this.itemJson = response.bodyText;
}, function (err) {
console.log(err);
});
return false;
}
}
});

View File

@ -0,0 +1,420 @@
<?php
/**
* Name: Advanced content Filter
* Description: Expression-based content filter
* Version: 1.0
* Author: Hypolite Petovan <https://friendica.mrpetovan.com/profile/hypolite>
* Maintainer: Hypolite Petovan <https://friendica.mrpetovan.com/profile/hypolite>
*
* Copyright (c) 2018 Hypolite Petovan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* * copyright notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* * Neither the name of Friendica nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL FRIENDICA BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
use Friendica\App;
use Friendica\Core\Addon;
use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Database\DBStructure;
use Friendica\Network\HTTPException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\ExpressionLanguage;
require_once 'boot.php';
require_once 'include/conversation.php';
require_once 'include/dba.php';
require_once 'include/security.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
function advancedcontentfilter_install()
{
Addon::registerHook('dbstructure_definition' , __FILE__, 'advancedcontentfilter_dbstructure_definition');
Addon::registerHook('prepare_body_content_filter', __FILE__, 'advancedcontentfilter_prepare_body_content_filter');
Addon::registerHook('addon_settings' , __FILE__, 'advancedcontentfilter_addon_settings');
DBStructure::update(false, true);
logger("installed advancedcontentfilter");
}
function advancedcontentfilter_uninstall()
{
Addon::unregisterHook('dbstructure_definition' , __FILE__, 'advancedcontentfilter_dbstructure_definition');
Addon::unregisterHook('prepare_body_content_filter', __FILE__, 'advancedcontentfilter_prepare_body_content_filter');
Addon::unregisterHook('addon_settings' , __FILE__, 'advancedcontentfilter_addon_settings');
}
/*
* Hooks
*/
function advancedcontentfilter_dbstructure_definition(App $a, &$database)
{
$database["advancedcontentfilter_rules"] = [
"comment" => "Advancedcontentfilter addon rules",
"fields" => [
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "Auto incremented rule id"],
"uid" => ["type" => "int unsigned", "not null" => "1", "comment" => "Owner user id"],
"name" => ["type" => "varchar(255)", "not null" => "1", "comment" => "Rule name"],
"expression" => ["type" => "mediumtext" , "not null" => "1", "comment" => "Expression text"],
"serialized" => ["type" => "mediumtext" , "not null" => "1", "comment" => "Serialized parsed expression"],
"active" => ["type" => "boolean" , "not null" => "1", "default" => "1", "comment" => "Whether the rule is active or not"],
"created" => ["type" => "datetime" , "not null" => "1", "default" => NULL_DATE, "comment" => "Creation date"],
],
"indexes" => [
"PRIMARY" => ["id"],
"uid_active" => ["uid", "active"],
]
];
}
function advancedcontentfilter_prepare_body_content_filter(App $a, &$hook_data)
{
static $expressionLanguage;
if (is_null($expressionLanguage)) {
$expressionLanguage = new ExpressionLanguage\ExpressionLanguage();
}
if (!local_user()) {
return;
}
$vars = [];
foreach ($hook_data['item'] as $key => $value) {
$vars[str_replace('-', '_', $key)] = $value;
}
$rules = Friendica\Core\Cache::get('rules_' . local_user());
if (!isset($rules)) {
$rules = dba::inArray(dba::select(
'advancedcontentfilter_rules',
['name', 'expression', 'serialized'],
['uid' => local_user(), 'active' => true]
));
}
foreach($rules as $rule) {
try {
$serializedParsedExpression = new ExpressionLanguage\SerializedParsedExpression(
$rule['expression'],
$rule['serialized']
);
$found = (bool) $expressionLanguage->evaluate($serializedParsedExpression, $vars);
} catch (Exception $e) {
$found = false;
}
if ($found) {
$hook_data['filter_reasons'][] = L10n::t('Filtered by rule: %s', $rule['name']);
break;
}
}
}
function advancedcontentfilter_addon_settings(App $a, &$s)
{
if (!local_user()) {
return;
}
$advancedcontentfilter = L10n::t('Advanced Content Filter');
$s .= <<<HTML
<span class="settings-block fakelink" style="display: block;"><h3><a href="advancedcontentfilter">$advancedcontentfilter <i class="glyphicon glyphicon-share"></i></a></h3></span>
HTML;
return;
}
/*
* Module
*/
function advancedcontentfilter_module() {}
function advancedcontentfilter_init(App $a)
{
if ($a->argv[1] == 'api') {
$slim = new \Slim\App();
require __DIR__ . '/src/middlewares.php';
require __DIR__ . '/src/routes.php';
$slim->run();
exit;
}
}
function advancedcontentfilter_content(App $a)
{
if (!local_user()) {
return \Friendica\Module\Login::form('/' . implode('/', $a->argv));
}
if ($a->argc > 0 && $a->argv[1] == 'help') {
$lang = $a->user['language'];
$default_dir = 'addon/advancedcontentfilter/doc/';
$help_file = 'advancedcontentfilter.md';
$help_path = $default_dir . $help_file;
if (file_exists($default_dir . $lang . '/' . $help_file)) {
$help_path = $default_dir . $lang . '/' . $help_file;
}
$content = file_get_contents($help_path);
$html = \Friendica\Content\Text\Markdown::convert($content, false);
$html = str_replace('code>', 'key>', $html);
return $html;
} else {
$t = get_markup_template('settings.tpl', 'addon/advancedcontentfilter/');
return replace_macros($t, [
'$backtosettings' => L10n::t('Back to Addon Settings'),
'$title' => L10n::t('Advanced Content Filter'),
'$add_a_rule' => L10n::t('Add a Rule'),
'$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>.'),
'$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.'),
'$disabled' => L10n::t('Disabled'),
'$enabled' => L10n::t('Enabled'),
'$disable_this_rule' => L10n::t('Disable this rule'),
'$enable_this_rule' => L10n::t('Enable this rule'),
'$edit_this_rule' => L10n::t('Edit this rule'),
'$edit_the_rule' => L10n::t('Edit the rule'),
'$save_this_rule' => L10n::t('Save this rule'),
'$delete_this_rule' => L10n::t('Delete this rule'),
'$rule' => L10n::t('Rule'),
'$close' => L10n::t('Close'),
'$addtitle' => L10n::t('Add new rule'),
'$rule_name' => L10n::t('Rule Name'),
'$rule_expression' => L10n::t('Rule Expression'),
'$examples' => L10n::t('<p>Examples:</p><ul><li><pre>author_link == \'https://friendica.mrpetovan.com/profile/hypolite\'</pre></li><li>tags</li></ul>'),
'$cancel' => L10n::t('Cancel'),
'$rules' => advancedcontentfilter_get_rules(),
'$baseurl' => System::baseUrl(true),
'$form_security_token' => get_form_security_token()
]);
}
}
/*
* Common functions
*/
function advancedcontentfilter_build_fields($data)
{
$fields = [];
if (!empty($data['name'])) {
$fields['name'] = $data['name'];
}
if (!empty($data['expression'])) {
$allowed_keys = [
'author_id', 'author_link', 'author_name', 'author_avatar',
'owner_id', 'owner_link', 'owner_name', 'owner_avatar',
'contact_id', 'uid', 'id', 'parent', 'uri',
'thr_parent', 'parent_uri',
'content_warning',
'commented', 'created', 'edited', 'received',
'verb', 'object_type', 'postopts', 'plink', 'guid', 'wall', 'private', 'starred',
'title', 'body',
'file', 'event_id', 'location', 'coord', 'app', 'attach',
'rendered_hash', 'rendered_html', 'object',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'item_id', 'item_network', 'author_thumb', 'owner_thumb',
'network', 'url', 'name', 'writable', 'self',
'cid', 'alias',
'event_created', 'event_edited', 'event_start', 'event_finish', 'event_summary',
'event_desc', 'event_location', 'event_type', 'event_nofinish', 'event_adjust', 'event_ignore',
'children', 'pagedrop', 'tags', 'hashtags', 'mentions',
];
$expressionLanguage = new ExpressionLanguage\ExpressionLanguage();
$parsedExpression = $expressionLanguage->parse($data['expression'], $allowed_keys);
$serialized = serialize($parsedExpression->getNodes());
$fields['expression'] = $data['expression'];
$fields['serialized'] = $serialized;
}
if (isset($data['active'])) {
$fields['active'] = intval($data['active']);
} else {
$fields['active'] = 1;
}
return $fields;
}
/*
* API
*/
function advancedcontentfilter_get_rules()
{
if (!local_user()) {
throw new HTTPException\UnauthorizedException(L10n::t('You must be logged in to use this method'));
}
$rules = dba::inArray(dba::select('advancedcontentfilter_rules', [], ['uid' => local_user()]));
return json_encode($rules);
}
function advancedcontentfilter_get_rules_id(ServerRequestInterface $request, ResponseInterface $response, $args)
{
if (!local_user()) {
throw new HTTPException\UnauthorizedException(L10n::t('You must be logged in to use this method'));
}
$rule = dba::selectFirst('advancedcontentfilter_rules', [], ['id' => $args['id'], 'uid' => local_user()]);
return json_encode($rule);
}
function advancedcontentfilter_post_rules(ServerRequestInterface $request)
{
if (!local_user()) {
throw new HTTPException\UnauthorizedException(L10n::t('You must be logged in to use this method'));
}
if (!check_form_security_token()) {
throw new HTTPException\BadRequestException(L10n::t('Invalid form security token, please refresh the page.'));
}
$data = json_decode($request->getBody(), true);
try {
$fields = advancedcontentfilter_build_fields($data);
} catch (Exception $e) {
throw new HTTPException\BadRequestException($e->getMessage(), 0, $e);
}
if (empty($fields['name']) || empty($fields['expression'])) {
throw new HTTPException\BadRequestException(L10n::t('The rule name and expression are required.'));
}
$fields['uid'] = local_user();
$fields['created'] = \Friendica\Util\DateTimeFormat::utcNow();
if (!dba::insert('advancedcontentfilter_rules', $fields)) {
throw new HTTPException\ServiceUnavaiableException(dba::errorMessage());
}
$rule = dba::selectFirst('advancedcontentfilter_rules', [], ['id' => dba::lastInsertId()]);
return json_encode(['message' => L10n::t('Rule successfully added'), 'rule' => $rule]);
}
function advancedcontentfilter_put_rules_id(ServerRequestInterface $request, ResponseInterface $response, $args)
{
if (!local_user()) {
throw new HTTPException\UnauthorizedException(L10n::t('You must be logged in to use this method'));
}
if (!check_form_security_token()) {
throw new HTTPException\BadRequestException(L10n::t('Invalid form security token, please refresh the page.'));
}
if (!dba::exists('advancedcontentfilter_rules', ['id' => $args['id'], 'uid' => local_user()])) {
throw new HTTPException\NotFoundException(L10n::t('Rule doesn\'t exist or doesn\'t belong to you.'));
}
$data = json_decode($request->getBody(), true);
try {
$fields = advancedcontentfilter_build_fields($data);
} catch (Exception $e) {
throw new HTTPException\BadRequestException($e->getMessage(), 0, $e);
}
if (!dba::update('advancedcontentfilter_rules', $fields, ['id' => $args['id']])) {
throw new HTTPException\ServiceUnavaiableException(dba::errorMessage());
}
return json_encode(['message' => L10n::t('Rule successfully updated')]);
}
function advancedcontentfilter_delete_rules_id(ServerRequestInterface $request, ResponseInterface $response, $args)
{
if (!local_user()) {
throw new HTTPException\UnauthorizedException(L10n::t('You must be logged in to use this method'));
}
if (!check_form_security_token()) {
throw new HTTPException\BadRequestException(L10n::t('Invalid form security token, please refresh the page.'));
}
if (!dba::exists('advancedcontentfilter_rules', ['id' => $args['id'], 'uid' => local_user()])) {
throw new HTTPException\NotFoundException(L10n::t('Rule doesn\'t exist or doesn\'t belong to you.'));
}
if (!dba::delete('advancedcontentfilter_rules', ['id' => $args['id']])) {
throw new HTTPException\ServiceUnavaiableException(dba::errorMessage());
}
return json_encode(['message' => L10n::t('Rule successfully deleted')]);
}
function advancedcontentfilter_get_variables_guid(ServerRequestInterface $request, ResponseInterface $response, $args)
{
if (!local_user()) {
throw new HTTPException\UnauthorizedException(L10n::t('You must be logged in to use this method'));
}
if (!isset($args['guid'])) {
throw new HTTPException\BadRequestException(L10n::t('Missing argument: guid.'));
}
$item = dba::fetch_first(item_query() . " AND `item`.`guid` = ? AND (`item`.`uid` = ? OR `item`.`uid` = 0) ORDER BY `item`.`uid` DESC", $args['guid'], local_user());
if (!\Friendica\Database\DBM::is_result($item)) {
throw new HTTPException\NotFoundException(L10n::t('Unknown post with guid: %s', $args['guid']));
}
$tags = \Friendica\Model\Term::populateTagsFromItem($item);
$item['tags'] = $tags['tags'];
$item['hashtags'] = $tags['hashtags'];
$item['mentions'] = $tags['mentions'];
$return = [];
foreach ($item as $key => $value) {
$return[str_replace('-', '_', $key)] = $value;
}
return str_replace('\\\'', '\'', var_export($return, true));
}

View File

@ -0,0 +1,514 @@
<style>
.advancedcontentfilter-content-wrapper {
min-height: calc(100vh - 150px);
padding: 15px;
padding-bottom: 20px;
margin-bottom: 20px;
border: none;
/*background-color: #fff;*/
background-color: rgba(255,255,255,0.95);
border-radius: 4px;
position: relative;
/*overflow: hidden;*/
color: #555;
box-shadow: 0 0 3px #dadada;
-webkit-box-shadow: 0 0 3px #dadada;
-moz-box-shadow: 0 0 3px #dadada;
}
</style>
<a href="advancedcontentfilter">🔙 Back to Addon Settings</a>
# Advanced Content Filter Help
The advanced Content Filter uses Symfony's Expression Language.
This help page includes a summary of [the Symfony's Expression Language documentation page.](https://symfony.com/doc/current/components/expression_language/syntax.html)
## Basics
The advanced content filter matches each post that is about to be displayed against each enabled rule you set.
A rule is a boolean expression that should return either `true` or `false` depending on post variables.
If the expression using a post variables returns `true`, the post will be collapsed and the matching rule name will be displayed above the collapsed content.
A post will be collapsed if at least one rule matches, but all matching rule names will be displayed above the collapsed content.
## Expression Syntax
### Supported Literals
- **strings** - single and double quotes (e.g. `'hello'`).
- **numbers** - e.g. `103`.
- **arrays** - using JSON-like notation (e.g. `[1, 2]`).
- **hashes** - using JSON-like notation (e.g. `{ foo: 'bar' }`).
- **booleans** - `true` and `false`.
- **null** - `null`.
A backslash (``\``) must be escaped by 4 backslashes (``\\\\``) in a string
and 8 backslashes (``\\\\\\\\``) in a regex::
`"a\\\\b" matches "/^a\\\\\\\\b$/"`
Control characters (e.g. ``\n``) in expressions are replaced with
whitespace. To avoid this, escape the sequence with a single backslash
(e.g. ``\\n``).
### Supported Operators
The component comes with a lot of operators:
#### Arithmetic Operators
* ``+`` (addition)
* ``-`` (subtraction)
* ``*`` (multiplication)
* ``/`` (division)
* ``%`` (modulus)
* ``**`` (pow)
#### Bitwise Operators
* ``&`` (and)
* ``|`` (or)
* ``^`` (xor)
#### Comparison Operators
* ``==`` (equal)
* ``===`` (identical)
* ``!=`` (not equal)
* ``!==`` (not identical)
* ``<`` (less than)
* ``>`` (greater than)
* ``<=`` (less than or equal to)
* ``>=`` (greater than or equal to)
* ``matches`` (regex match)
To test if a string does *not* match a regex, use the logical ``not``
operator in combination with the ``matches`` operator:
'not ("foo" matches "/bar/")'
You must use parenthesis because the unary operator ``not`` has precedence
over the binary operator ``matches``.
#### Logical Operators
* ``not`` or ``!``
* ``and`` or ``&&``
* ``or`` or ``||``
#### String Operators
* ``~`` (concatenation)
For example: ``firstName ~ " " ~ lastName``
#### Array Operators
* ``in`` (contain)
* ``not in`` (does not contain)
For example: ``user.group in ["human_resources", "marketing"]``
#### Numeric Operators
* ``..`` (range)
For example: ``user.age in 18..45``
#### Ternary Operators
* ``foo ? 'yes' : 'no'``
* ``foo ?: 'no'`` (equal to ``foo ? foo : 'no'``)
* ``foo ? 'yes'`` (equal to ``foo ? 'yes' : ''``)
### Supported variables
Here are a sample of the available variables you can use in your expressions.
You can also retrieve the variables of a specific post by pasting its URL below the rule list.
<table class="table-bordered table-condensed table-striped">
<thead>
<tr>
<th>Variable</th>
<th>Type</th>
<th>Sample Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>author-id</th>
<td>number</td>
<td>6</td>
</tr>
<tr>
<th>author-link</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/profile/hypolite</td>
</tr>
<tr>
<th>author-name</th>
<td>string</td>
<td>Hypolite Petovan</td>
</tr>
<tr>
<th>author-avatar</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/photo/41084997915a94a8c83cc39708500207-5.png</td>
</tr>
<tr>
<th>owner-id</th>
<td>number</td>
<td>6</td>
</tr>
<tr>
<th>owner-link</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/profile/hypolite</td>
</tr>
<tr>
<th>owner-name</th>
<td>string</td>
<td>Hypolite Petovan</td>
</tr>
<tr>
<th>owner-avatar</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/photo/41084997915a94a8c83cc39708500207-5.png</td>
</tr>
<tr>
<th>contact-id</th>
<td>number</td>
<td>1</td>
</tr>
<tr>
<th>uid</th>
<td>number</td>
<td>1</td>
</tr>
<tr>
<th>id</th>
<td>number</td>
<td>791875</td>
</tr>
<tr>
<th>parent</th>
<td>number</td>
<td>791875</td>
</tr>
<tr>
<th>uri</th>
<td>string</td>
<td>urn:X-dfrn:friendica.mrpetovan.com:1:twit:978740198937907200</td>
</tr>
<tr>
<th>thr-parent</th>
<td>string</td>
<td>urn:X-dfrn:friendica.mrpetovan.com:1:twit:978740198937907200</td>
</tr>
<tr>
<th>parent-uri</th>
<td>string</td>
<td>urn:X-dfrn:friendica.mrpetovan.com:1:twit:978740198937907200</td>
</tr>
<tr>
<th>content-warning</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>commented</th>
<td>date</td>
<td>2018-03-27 21:10:18</td>
</tr>
<tr>
<th>created</th>
<td>date</td>
<td>2018-03-27 21:10:18</td>
</tr>
<tr>
<th>edited</th>
<td>date</td>
<td>2018-03-27 21:10:18</td>
</tr>
<tr>
<th>received</th>
<td>date</td>
<td>2018-03-27 21:10:18</td>
</tr>
<tr>
<th>verb</th>
<td>string</td>
<td>http://activitystrea.ms/schema/1.0/post</td>
</tr>
<tr>
<th>object-type</th>
<td>string</td>
<td>http://activitystrea.ms/schema/1.0/bookmark</td>
</tr>
<tr>
<th>postopts</th>
<td>string</td>
<td>twitter&lang=pidgin;0.24032407407407:english;0.225:french;0.18055555555556</td>
</tr>
<tr>
<th>plink</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/display/735a2029995abab33a5c006052376776</td>
</tr>
<tr>
<th>guid</th>
<td>string</td>
<td>735a2029995abab33a5c006052376776</td>
</tr>
<tr>
<th>wall</th>
<td>boolean</td>
<td>1</td>
</tr>
<tr>
<th>private</th>
<td>boolean</td>
<td>0</td>
</tr>
<tr>
<th>starred</th>
<td>boolean</td>
<td>0</td>
</tr>
<tr>
<th>title</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>body</th>
<td>string</td>
<td>Over-compensation #[url=https://friendica.mrpetovan.com/search?tag=Street]Street[/url] #[url=https://friendica.mrpetovan.com/search?tag=Night]Night[/url] #[url=https://friendica.mrpetovan.com/search?tag=CarLights]CarLights[/url] #[url=https://friendica.mrpetovan.com/search?tag=Jeep]Jeep[/url] #[url=https://friendica.mrpetovan.com/search?tag=NoPeople]NoPeople[/url] #[url=https://friendica.mrpetovan.com/search?tag=Close]Close[/url]-up
[attachment type='link' url='https://www.eyeem.com/p/120800309' title='Over-compensation Street Night Car Lights Jeep No | EyeEm' image='https://cdn.eyeem.com/thumb/b2f019738cbeef06e2f8c9517c6286a8adcd3a00-1522184820641/640/480']Photo by @[url=https://twitter.com/MrPetovan]MrPetovan[/url][/attachment]</td>
</tr>
<tr>
<th>file</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>event-id</th>
<td>number</td>
<td>null
<tr>
<th>location</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>coord</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>app</th>
<td>string</td>
<td>EyeEm</td>
</tr>
<tr>
<th>attach</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>rendered-hash</th>
<td>string</td>
<td>b70abdea8b362dc5dcf63e1b2836ad89</td>
</tr>
<tr>
<th>rendered-html</th>
<td>string</td>
<td>
Over-compensation #&lt;a href="https://friendica.mrpetovan.com/search?tag=Street" class="tag" title="Street"&gt;Street&lt;/a&gt; #&lt;a href="https://friendica.mrpetovan.com/search?tag=Night" class="tag" title="Night"&gt;Night&lt;/a&gt; #&lt;a href="https://friendica.mrpetovan.com/search?tag=CarLights" class="tag" title="CarLights"&gt;CarLights&lt;/a&gt; #&lt;a href="https://friendica.mrpetovan.com/search?tag=Jeep" class="tag" title="Jeep"&gt;Jeep&lt;/a&gt; #&lt;a href="https://friendica.mrpetovan.com/search?tag=NoPeople" class="tag" title="NoPeople"&gt;NoPeople&lt;/a&gt; #&lt;a href="https://friendica.mrpetovan.com/search?tag=Close" class="tag" title="Close"&gt;Close&lt;/a&gt;-up &lt;div class="type-link"&gt;&lt;a href="https://www.eyeem.com/p/120800309" target="_blank"&gt;&lt;img src="https://friendica.mrpetovan.com/proxy/bb/aHR0cHM6Ly9jZG4uZXllZW0uY29tL3RodW1iL2IyZjAxOTczOGNiZWVmMDZlMmY4Yzk1MTdjNjI4NmE4YWRjZDNhMDAtMTUyMjE4NDgyMDY0MS82NDAvNDgw" alt="" title="Over-compensation Street Night Car Lights Jeep No | EyeEm" class="attachment-image"&gt;&lt;/a&gt;&lt;br&gt;&lt;h4&gt;&lt;a href="https://www.eyeem.com/p/120800309"&gt;Over-compensation Street Night Car Lights Jeep No | EyeEm&lt;/a&gt;&lt;/h4&gt;&lt;blockquote&gt;Photo by @&lt;a href="https://twitter.com/MrPetovan" class="userinfo mention" title="MrPetovan"&gt;MrPetovan&lt;/a&gt;&lt;/blockquote&gt;&lt;sup&gt;&lt;a href="https://www.eyeem.com/p/120800309"&gt;www.eyeem.com&lt;/a&gt;&lt;/sup&gt;&lt;/div&gt;
</td>
</tr>
<tr>
<th>object</th>
<td>string</td>
<td>{"created_at":"Tue Mar 27 21:07:02 +0000 2018","id":978740198937907200,"id_str":"978740198937907200","full_text":"Over-compensation #Street #Night #CarLights #Jeep #NoPeople #Close-up https:\/\/t.co\/7w4ua13QA7","truncated":false,"display_text_range":[0,93],"entities":{"hashtags":[{"text":"Street","indices":[18,25]},{"text":"Night","indices":[26,32]},{"text":"CarLights","indices":[33,43]},{"text":"Jeep","indices":[44,49]},{"text":"NoPeople","indices":[50,59]},{"text":"Close","indices":[60,66]}],"symbols":[],"user_mentions":[],"urls":[{"url":"https:\/\/t.co\/7w4ua13QA7","expanded_url":"http:\/\/EyeEm.com\/p\/120800309","display_url":"EyeEm.com\/p\/120800309","indices":[70,93]}]},"source":"&lt;a href=\"http:\/\/www.eyeem.com\" rel=\"nofollow\"&gt;EyeEm&lt;\/a&gt;","in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":403748896,"id_str":"403748896","name":"\ud83d\udc30yp\ud83e\udd5ali\u271d\ufe0fe Pet\ud83e\udd5avan","screen_name":"MrPetovan","location":"NYC","description":"White male form of milquetoast. Avatar by @DearMsDear inspired by @TSG_LAB.\n\nFriendica\/Diaspora\/Mastodon: hypolite@friendica.mrpetovan.com","url":"https:\/\/t.co\/PcARi5OhQO","entities":{"url":{"urls":[{"url":"https:\/\/t.co\/PcARi5OhQO","expanded_url":"https:\/\/mrpetovan.com","display_url":"mrpetovan.com","indices":[0,23]}]},"description":{"urls":[]}},"protected":false,"followers_count":182,"friends_count":146,"listed_count":15,"created_at":"Wed Nov 02 23:13:14 +0000 2011","favourites_count":45826,"utc_offset":-14400,"time_zone":"Eastern Time (US &amp; Canada)","geo_enabled":false,"verified":false,"statuses_count":15554,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"000000","profile_background_image_url":"http:\/\/pbs.twimg.com\/profile_background_images\/370213187\/fond_twitter_mrpetovan.png","profile_background_image_url_https":"https:\/\/pbs.twimg.com\/profile_background_images\/370213187\/fond_twitter_mrpetovan.png","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/968008546322395136\/6qLCiu0o_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/968008546322395136\/6qLCiu0o_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/403748896\/1464321684","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"000000","profile_use_background_image":true,"has_extended_profile":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"geo":null,"coordinates":null,"place":null,"contributors":null,"is_quote_status":false,"retweet_count":0,"favorite_count":0,"favorited":false,"retweeted":false,"possibly_sensitive":false,"lang":"en"}</td>
</tr>
<tr>
<th>allow_cid</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>allow_gid</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>deny_cid</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>deny_gid</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>item_id</th>
<td>number</td>
<td>791875</td>
</tr>
<tr>
<th>item_network</th>
<td>string</td>
<td>dfrn</td>
</tr>
<tr>
<th>author-thumb</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/photo/0cb3d7231eb751139d7d309c7c686c49-5.png?ts=1522941604</td>
</tr>
<tr>
<th>owner-thumb</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/photo/0cb3d7231eb751139d7d309c7c686c49-5.png?ts=1522941604</td>
</tr>
<tr>
<th>network</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>url</th>
<td>string</td>
<td>https://friendica.mrpetovan.com/profile/hypolite</td>
</tr>
<tr>
<th>name</th>
<td>string</td>
<td>Hypolite Petovan</td>
</tr>
<tr>
<th>writable</th>
<td>boolean</td>
<td>0</td>
</tr>
<tr>
<th>self</th>
<td>boolean</td>
<td>1</td>
</tr>
<tr>
<th>cid</th>
<td>number</td>
<td>1</td>
</tr>
<tr>
<th>alias</th>
<td>string</td>
<td></td>
</tr>
<tr>
<th>event-created</th>
<td>date</td>
<td>null</td>
</tr>
<tr>
<th>event-edited</th>
<td>date</td>
<td>null</td>
</tr>
<tr>
<th>event-start</th>
<td>date</td>
<td>null</td>
</tr>
<tr>
<th>event-finish</th>
<td>date</td>
<td>null</td>
</tr>
<tr>
<th>event-summary</th>
<td>string</td>
<td>null</td>
</tr>
<tr>
<th>event-desc</th>
<td>string</td>
<td>null</td>
</tr>
<tr>
<th>event-location</th>
<td>string</td>
<td>null</td>
</tr>
<tr>
<th>event-type</th>
<td>string</td>
<td>null</td>
</tr>
<tr>
<th>event-nofinish</th>
<td>string</td>
<td>null</td>
</tr>
<tr>
<th>event-adjust</th>
<td>boolean</td>
<td>null</td>
</tr>
<tr>
<th>event-ignore</th>
<td>boolean</td>
<td>null</td>
</tr>
<tr>
<th>pagedrop</th>
<td>string</td>
<td>true</td>
</tr>
<tr>
<th>tags</th>
<td>list</td>
<td>
<ol start="0">
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Street" target="_blank"&gt;street&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Night" target="_blank"&gt;night&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=CarLights" target="_blank"&gt;carlights&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Jeep" target="_blank"&gt;jeep&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=NoPeople" target="_blank"&gt;nopeople&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Close" target="_blank"&gt;close&lt;/a&gt;</li>
<li>@&lt;a href="https://twitter.com/MrPetovan" target="_blank"&gt;mrpetovan&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Close-up" target="_blank"&gt;close-up&lt;/a&gt;</li>
</ol>
</td>
</tr>
<tr>
<th>hashtags</th>
<td>list</td>
<td>
<ol start="0">
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Street" target="_blank"&gt;street&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Night" target="_blank"&gt;night&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=CarLights" target="_blank"&gt;carlights&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Jeep" target="_blank"&gt;jeep&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=NoPeople" target="_blank"&gt;nopeople&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Close" target="_blank"&gt;close&lt;/a&gt;</li>
<li>#&lt;a href="https://friendica.mrpetovan.com/search?tag=Close-up" target="_blank"&gt;close-up&lt;/a&gt;</li>
</ol>
</td>
</tr>
<tr>
<th>mentions</th>
<td>string</td>
<td>
<ol start="0">
<li>@&lt;a href="https://twitter.com/MrPetovan" target="_blank"&gt;mrpetovan&lt;/a&gt;</li>
</ol>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,163 @@
# ADDON advancedcontentfilter
# Copyright (C)
# This file is distributed under the same license as the Friendica advancedcontentfilter addon package.
#
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-17 04:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: advancedcontentfilter.php:134
#, php-format
msgid "Filtered by rule: %s"
msgstr ""
#: advancedcontentfilter.php:147 advancedcontentfilter.php:204
msgid "Advanced Content Filter"
msgstr ""
#: advancedcontentfilter.php:203
msgid "Back to Addon Settings"
msgstr ""
#: advancedcontentfilter.php:205
msgid "Add a Rule"
msgstr ""
#: advancedcontentfilter.php:206
msgid "Help"
msgstr ""
#: advancedcontentfilter.php:207
msgid ""
"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>."
msgstr ""
#: advancedcontentfilter.php:208
msgid "Your rules"
msgstr ""
#: advancedcontentfilter.php:209
msgid ""
"You have no rules yet! Start adding one by clicking on the button above next "
"to the title."
msgstr ""
#: advancedcontentfilter.php:210
msgid "Disabled"
msgstr ""
#: advancedcontentfilter.php:211
msgid "Enabled"
msgstr ""
#: advancedcontentfilter.php:212
msgid "Disable this rule"
msgstr ""
#: advancedcontentfilter.php:213
msgid "Enable this rule"
msgstr ""
#: advancedcontentfilter.php:214
msgid "Edit this rule"
msgstr ""
#: advancedcontentfilter.php:215
msgid "Edit the rule"
msgstr ""
#: advancedcontentfilter.php:216
msgid "Save this rule"
msgstr ""
#: advancedcontentfilter.php:217
msgid "Delete this rule"
msgstr ""
#: advancedcontentfilter.php:218
msgid "Rule"
msgstr ""
#: advancedcontentfilter.php:219
msgid "Close"
msgstr ""
#: advancedcontentfilter.php:220
msgid "Add new rule"
msgstr ""
#: advancedcontentfilter.php:221
msgid "Rule Name"
msgstr ""
#: advancedcontentfilter.php:222
msgid "Rule Expression"
msgstr ""
#: advancedcontentfilter.php:223
msgid ""
"<p>Examples:</p><ul><li><pre>author_link == 'https://friendica.mrpetovan.com/"
"profile/hypolite'</pre></li><li>tags</li></ul>"
msgstr ""
#: advancedcontentfilter.php:224
msgid "Cancel"
msgstr ""
#: advancedcontentfilter.php:290 advancedcontentfilter.php:301
#: advancedcontentfilter.php:312 advancedcontentfilter.php:346
#: advancedcontentfilter.php:375 advancedcontentfilter.php:396
msgid "You must be logged in to use this method"
msgstr ""
#: advancedcontentfilter.php:316 advancedcontentfilter.php:350
#: advancedcontentfilter.php:379
msgid "Invalid form security token, please refresh the page."
msgstr ""
#: advancedcontentfilter.php:328
msgid "The rule name and expression are required."
msgstr ""
#: advancedcontentfilter.php:340
msgid "Rule successfully added"
msgstr ""
#: advancedcontentfilter.php:354 advancedcontentfilter.php:383
msgid "Rule doesn't exist or doesn't belong to you."
msgstr ""
#: advancedcontentfilter.php:369
msgid "Rule successfully updated"
msgstr ""
#: advancedcontentfilter.php:390
msgid "Rule successfully deleted"
msgstr ""
#: advancedcontentfilter.php:400
msgid "Missing argument: guid."
msgstr ""
#: advancedcontentfilter.php:406
#, php-format
msgid "Unknown post with guid: %s"
msgstr ""
#: src/middlewares.php:28
msgid "Method not found"
msgstr ""

View File

@ -0,0 +1,30 @@
<?php
$container = $slim->getContainer();
// Error handler based off https://stackoverflow.com/a/48135009/757392
$container['errorHandler'] = function () {
return function(Psr\Http\Message\RequestInterface $request, Psr\Http\Message\ResponseInterface $response, Exception $exception)
{
$responseCode = 500;
if (is_a($exception, 'Friendica\Network\HTTPException')) {
$responseCode = $exception->httpcode;
}
$errors['message'] = $exception->getMessage();
$errors['responseCode'] = $responseCode;
return $response
->withStatus($responseCode)
->withJson($errors);
};
};
$container['notFoundHandler'] = function () {
return function ()
{
throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('Method not found'));
};
};

View File

@ -0,0 +1,22 @@
<?php
// Routes
/* @var $slim Slim\App */
$slim->group('/advancedcontentfilter/api', function () {
/* @var $this Slim\App */
$this->group('/rules', function () {
/* @var $this Slim\App */
$this->get('', 'advancedcontentfilter_get_rules');
$this->post('', 'advancedcontentfilter_post_rules');
$this->get('/{id}', 'advancedcontentfilter_get_rules_id');
$this->put('/{id}', 'advancedcontentfilter_put_rules_id');
$this->delete('/{id}', 'advancedcontentfilter_delete_rules_id');
});
$this->group('/variables', function () {
/* @var $this Slim\App */
$this->get('/{guid}', 'advancedcontentfilter_get_variables_guid');
});
});

View File

@ -0,0 +1,99 @@
<div id="adminpage">
<style>[v-cloak] { display: none; }</style>
<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">
{{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>
{{ itemJson }}
</pre>
</div>
<script> var existingRules = {{$rules}};</script>
<!-- JS -->
<script src="{{$baseurl}}/addon/advancedcontentfilter/vendor/asset/vue/dist/vue.min.js"></script>
<script src="{{$baseurl}}/addon/advancedcontentfilter/vendor/asset/vue-resource/dist/vue-resource.min.js"></script>
<script src="{{$baseurl}}/addon/advancedcontentfilter/advancedcontentfilter.js"></script>
</div>