[advancedcontentfilter] Add addon files
authorHypolite Petovan <mrpetovan@gmail.com>
Tue, 17 Apr 2018 02:21:51 +0000 (22:21 -0400)
committerHypolite Petovan <mrpetovan@gmail.com>
Tue, 17 Apr 2018 02:21:51 +0000 (22:21 -0400)
- Add hooks
- Add mini-API module powered by Slim
- Add addon settings page powered by VueJS
- Add translation strings
- Add help page

advancedcontentfilter/LICENSE [new file with mode: 0644]
advancedcontentfilter/README.md [new file with mode: 0644]
advancedcontentfilter/advancedcontentfilter.js [new file with mode: 0644]
advancedcontentfilter/advancedcontentfilter.php [new file with mode: 0644]
advancedcontentfilter/doc/advancedcontentfilter.md [new file with mode: 0644]
advancedcontentfilter/lang/C/messages.po [new file with mode: 0644]
advancedcontentfilter/src/middlewares.php [new file with mode: 0644]
advancedcontentfilter/src/routes.php [new file with mode: 0644]
advancedcontentfilter/templates/settings.tpl [new file with mode: 0644]

diff --git a/advancedcontentfilter/LICENSE b/advancedcontentfilter/LICENSE
new file mode 100644 (file)
index 0000000..e877cc1
--- /dev/null
@@ -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.
diff --git a/advancedcontentfilter/README.md b/advancedcontentfilter/README.md
new file mode 100644 (file)
index 0000000..de16d6f
--- /dev/null
@@ -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
diff --git a/advancedcontentfilter/advancedcontentfilter.js b/advancedcontentfilter/advancedcontentfilter.js
new file mode 100644 (file)
index 0000000..531aec7
--- /dev/null
@@ -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;
+               }
+       }
+});
\ No newline at end of file
diff --git a/advancedcontentfilter/advancedcontentfilter.php b/advancedcontentfilter/advancedcontentfilter.php
new file mode 100644 (file)
index 0000000..bc95cfa
--- /dev/null
@@ -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));
+}
\ No newline at end of file
diff --git a/advancedcontentfilter/doc/advancedcontentfilter.md b/advancedcontentfilter/doc/advancedcontentfilter.md
new file mode 100644 (file)
index 0000000..4fcaf68
--- /dev/null
@@ -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>
\ No newline at end of file
diff --git a/advancedcontentfilter/lang/C/messages.po b/advancedcontentfilter/lang/C/messages.po
new file mode 100644 (file)
index 0000000..74bbaeb
--- /dev/null
@@ -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 ""
diff --git a/advancedcontentfilter/src/middlewares.php b/advancedcontentfilter/src/middlewares.php
new file mode 100644 (file)
index 0000000..4cc4a15
--- /dev/null
@@ -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'));
+    };
+};
diff --git a/advancedcontentfilter/src/routes.php b/advancedcontentfilter/src/routes.php
new file mode 100644 (file)
index 0000000..969ced6
--- /dev/null
@@ -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');
+       });
+});
diff --git a/advancedcontentfilter/templates/settings.tpl b/advancedcontentfilter/templates/settings.tpl
new file mode 100644 (file)
index 0000000..51d2e40
--- /dev/null
@@ -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>