--- /dev/null
+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.
--- /dev/null
+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
--- /dev/null
+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;
+ }
+ }
+});
\ No newline at end of file
--- /dev/null
+<?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));
+}
\ No newline at end of file
--- /dev/null
+<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 #<a href="https://friendica.mrpetovan.com/search?tag=Street" class="tag" title="Street">Street</a> #<a href="https://friendica.mrpetovan.com/search?tag=Night" class="tag" title="Night">Night</a> #<a href="https://friendica.mrpetovan.com/search?tag=CarLights" class="tag" title="CarLights">CarLights</a> #<a href="https://friendica.mrpetovan.com/search?tag=Jeep" class="tag" title="Jeep">Jeep</a> #<a href="https://friendica.mrpetovan.com/search?tag=NoPeople" class="tag" title="NoPeople">NoPeople</a> #<a href="https://friendica.mrpetovan.com/search?tag=Close" class="tag" title="Close">Close</a>-up <div class="type-link"><a href="https://www.eyeem.com/p/120800309" target="_blank"><img src="https://friendica.mrpetovan.com/proxy/bb/aHR0cHM6Ly9jZG4uZXllZW0uY29tL3RodW1iL2IyZjAxOTczOGNiZWVmMDZlMmY4Yzk1MTdjNjI4NmE4YWRjZDNhMDAtMTUyMjE4NDgyMDY0MS82NDAvNDgw" alt="" title="Over-compensation Street Night Car Lights Jeep No | EyeEm" class="attachment-image"></a><br><h4><a href="https://www.eyeem.com/p/120800309">Over-compensation Street Night Car Lights Jeep No | EyeEm</a></h4><blockquote>Photo by @<a href="https://twitter.com/MrPetovan" class="userinfo mention" title="MrPetovan">MrPetovan</a></blockquote><sup><a href="https://www.eyeem.com/p/120800309">www.eyeem.com</a></sup></div>
+ </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":"<a href=\"http:\/\/www.eyeem.com\" rel=\"nofollow\">EyeEm<\/a>","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 & 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>#<a href="https://friendica.mrpetovan.com/search?tag=Street" target="_blank">street</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Night" target="_blank">night</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=CarLights" target="_blank">carlights</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Jeep" target="_blank">jeep</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=NoPeople" target="_blank">nopeople</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Close" target="_blank">close</a></li>
+ <li>@<a href="https://twitter.com/MrPetovan" target="_blank">mrpetovan</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Close-up" target="_blank">close-up</a></li>
+ </ol>
+ </td>
+ </tr>
+ <tr>
+ <th>hashtags</th>
+ <td>list</td>
+ <td>
+ <ol start="0">
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Street" target="_blank">street</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Night" target="_blank">night</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=CarLights" target="_blank">carlights</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Jeep" target="_blank">jeep</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=NoPeople" target="_blank">nopeople</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Close" target="_blank">close</a></li>
+ <li>#<a href="https://friendica.mrpetovan.com/search?tag=Close-up" target="_blank">close-up</a></li>
+ </ol>
+ </td>
+ </tr>
+ <tr>
+ <th>mentions</th>
+ <td>string</td>
+ <td>
+ <ol start="0">
+ <li>@<a href="https://twitter.com/MrPetovan" target="_blank">mrpetovan</a></li>
+ </ol>
+ </td>
+ </tr>
+</tbody>
+</table>
\ No newline at end of file
--- /dev/null
+# 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 ""
--- /dev/null
+<?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'));
+ };
+};
--- /dev/null
+<?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');
+ });
+});
--- /dev/null
+<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">×</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>