[PLUGIN][TagBasedFiltering] Add TagBasedFiltering plugin, which allows filtering feeds by note tags and (soon) actor tags
This commit is contained in:
parent
4f669d4e01
commit
c40866ecf6
153
plugins/TagBasedFiltering/Controller/TagBasedFiltering.php
Normal file
153
plugins/TagBasedFiltering/Controller/TagBasedFiltering.php
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
// {{{ License
|
||||||
|
|
||||||
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||||
|
//
|
||||||
|
// GNU social is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// GNU social is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
namespace Plugin\TagBasedFiltering\Controller;
|
||||||
|
|
||||||
|
use App\Core\Cache;
|
||||||
|
use App\Core\Controller;
|
||||||
|
use App\Core\DB\DB;
|
||||||
|
use App\Core\Form;
|
||||||
|
use function App\Core\I18n\_m;
|
||||||
|
use App\Entity\Language;
|
||||||
|
use App\Entity\Note;
|
||||||
|
use App\Entity\NoteTag;
|
||||||
|
use App\Entity\NoteTagBlock;
|
||||||
|
use App\Util\Common;
|
||||||
|
use App\Util\Exception\NotImplementedException;
|
||||||
|
use App\Util\Exception\RedirectException;
|
||||||
|
use Component\Tag\Tag;
|
||||||
|
use Functional as F;
|
||||||
|
use Plugin\TagBasedFiltering\TagBasedFiltering as TagFilerPlugin;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\SubmitButton;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
class TagBasedFiltering extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Edit the user's list of blocked note tags, with the option of adding the notes from the note $note_id, if given
|
||||||
|
*/
|
||||||
|
public function editBlockedNoteTags(Request $request, ?int $note_id)
|
||||||
|
{
|
||||||
|
$user = Common::ensureLoggedIn();
|
||||||
|
$note = !\is_null($note_id) ? Note::getFromId($note_id) : null;
|
||||||
|
$note_tag_blocks = NoteTagBlock::getFromActorId($user->getId());
|
||||||
|
$note_tags = !\is_null($note_id) ? NoteTag::getFromNoteId($note_id) : [];
|
||||||
|
$blockable_note_tags = F\reject(
|
||||||
|
$note_tags,
|
||||||
|
fn (NoteTag $nt) => NoteTagBlock::checkBlocksNoteTag($nt, $note_tag_blocks),
|
||||||
|
);
|
||||||
|
|
||||||
|
$new_tags_form_definition = [];
|
||||||
|
|
||||||
|
foreach ($blockable_note_tags as $nt) {
|
||||||
|
$canon = $nt->getCanonical();
|
||||||
|
$new_tags_form_definition[] = ["{$canon}:new-tag", TextType::class, ['data' => '#' . $nt->getTag(), 'label' => ' ']];
|
||||||
|
$new_tags_form_definition[] = ["{$canon}:use-canon", CheckboxType::class, ['label' => _m('Use canonical'), 'help' => _m('Block all similar tags'), 'required' => false, 'data' => true]];
|
||||||
|
$new_tags_form_definition[] = ["{$canon}:add", SubmitType::class, ['label' => _m('Block')]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing_tags_form_definition = [];
|
||||||
|
foreach ($note_tag_blocks as $ntb) {
|
||||||
|
$canon = $ntb->getCanonical();
|
||||||
|
$existing_tags_form_definition[] = ["{$canon}:old-tag", TextType::class, ['data' => '#' . $ntb->getTag(), 'label' => ' ', 'disabled' => true]];
|
||||||
|
$existing_tags_form_definition[] = ["{$canon}:toggle-canon", SubmitType::class, ['label' => $ntb->getUseCanonical() ? _m('Set non-canonical') : _m('Set canonical')]];
|
||||||
|
$existing_tags_form_definition[] = ["{$canon}:remove", SubmitType::class, ['label' => _m('Unblock')]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_tags_form = null;
|
||||||
|
if (!empty($new_tags_form_definition)) {
|
||||||
|
$new_tags_form = Form::create($new_tags_form_definition);
|
||||||
|
$new_tags_form->handleRequest($request);
|
||||||
|
if ($new_tags_form->isSubmitted() && $new_tags_form->isValid()) {
|
||||||
|
$data = $new_tags_form->getData();
|
||||||
|
foreach ($new_tags_form_definition as [$id, $_, $opts]) {
|
||||||
|
[$canon, $type] = explode(':', $id);
|
||||||
|
if ($type === 'add') {
|
||||||
|
/** @var SubmitButton $button */
|
||||||
|
$button = $new_tags_form->get($id);
|
||||||
|
if ($button->isClicked()) {
|
||||||
|
Cache::delete(NoteTagBlock::cacheKey($user->getId()));
|
||||||
|
Cache::delete(TagFilerPlugin::cacheKeys($user->getId())['note']);
|
||||||
|
$new_tag = Tag::ensureValid($data[$canon . ':new-tag']);
|
||||||
|
$canonical_tag = Tag::canonicalTag($new_tag, Language::getFromNote($note)->getLocale());
|
||||||
|
DB::persist(NoteTagBlock::create([
|
||||||
|
'blocker' => $user->getId(),
|
||||||
|
'tag' => $new_tag,
|
||||||
|
'canonical' => $canonical_tag,
|
||||||
|
'use_canonical' => $data[$canon . ':use-canon'],
|
||||||
|
]));
|
||||||
|
DB::flush();
|
||||||
|
throw new RedirectException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing_tags_form = null;
|
||||||
|
if (!empty($existing_tags_form_definition)) {
|
||||||
|
$existing_tags_form = Form::create($existing_tags_form_definition);
|
||||||
|
$existing_tags_form->handleRequest($request);
|
||||||
|
if ($existing_tags_form->isSubmitted() && $existing_tags_form->isValid()) {
|
||||||
|
$data = $existing_tags_form->getData();
|
||||||
|
foreach ($existing_tags_form_definition as [$id, $_, $opts]) {
|
||||||
|
[$canon, $type] = explode(':', $id);
|
||||||
|
if (\in_array($type, ['remove', 'toggle-canon'])) {
|
||||||
|
/** @var SubmitButton $button */
|
||||||
|
$button = $existing_tags_form->get($id);
|
||||||
|
if ($button->isClicked()) {
|
||||||
|
Cache::delete(NoteTagBlock::cacheKey($user->getId()));
|
||||||
|
Cache::delete(TagFilerPlugin::cacheKeys($user->getId())['note']);
|
||||||
|
switch ($type) {
|
||||||
|
case 'toggle-canon':
|
||||||
|
$ntb = DB::getReference('note_tag_block', ['blocker' => $user->getId(), 'canonical' => $canon]);
|
||||||
|
$ntb->setUseCanonical(!$ntb->getUseCanonical());
|
||||||
|
DB::flush();
|
||||||
|
throw new RedirectException;
|
||||||
|
case 'remove':
|
||||||
|
$old_tag = $data[$canon . ':old-tag'];
|
||||||
|
DB::removeBy('note_tag_block', ['blocker' => $user->getId(), 'canonical' => $canon]);
|
||||||
|
throw new RedirectException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'_template' => 'tag-based-filtering/edit-tags.html.twig',
|
||||||
|
'note' => !\is_null($note_id) ? Note::getFromId($note_id) : null,
|
||||||
|
'new_tags_form' => $new_tags_form?->createView(),
|
||||||
|
'existing_tags_form' => $existing_tags_form?->createView(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editBlockedActorTags(Request $request, ?int $note_id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException;
|
||||||
|
}
|
||||||
|
}
|
91
plugins/TagBasedFiltering/TagBasedFiltering.php
Normal file
91
plugins/TagBasedFiltering/TagBasedFiltering.php
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
// {{{ License
|
||||||
|
|
||||||
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||||
|
//
|
||||||
|
// GNU social is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// GNU social is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
namespace Plugin\TagBasedFiltering;
|
||||||
|
|
||||||
|
use App\Core\Cache;
|
||||||
|
use App\Core\DB\DB;
|
||||||
|
use App\Core\Event;
|
||||||
|
use function App\Core\I18n\_m;
|
||||||
|
use App\Core\Modules\Plugin;
|
||||||
|
use App\Core\Router\RouteLoader;
|
||||||
|
use App\Core\Router\Router;
|
||||||
|
use App\Entity\Actor;
|
||||||
|
use App\Entity\LocalUser;
|
||||||
|
use App\Entity\Note;
|
||||||
|
use App\Entity\NoteTag;
|
||||||
|
use App\Entity\NoteTagBlock;
|
||||||
|
use Functional as F;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
class TagBasedFiltering extends Plugin
|
||||||
|
{
|
||||||
|
public const NOTE_TAG_FILTER_ROUTE = 'filter_feeds_edit_blocked_note_tags';
|
||||||
|
public const ACTOR_TAG_FILTER_ROUTE = 'filter_feeds_edit_blocked_actor_tags';
|
||||||
|
|
||||||
|
public static function cacheKeys(int|LocalUser|Actor $actor_id): array
|
||||||
|
{
|
||||||
|
if (!\is_int($actor_id)) {
|
||||||
|
$actor_id = $actor_id->getId();
|
||||||
|
}
|
||||||
|
return ['note' => "filtered-tags-{$actor_id}"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAddRoute(RouteLoader $r)
|
||||||
|
{
|
||||||
|
$r->connect(id: self::NOTE_TAG_FILTER_ROUTE, uri_path: '/filter/edit-blocked-note-tags/{note_id<\d+>?}', target: [Controller\TagBasedFiltering::class, 'editBlockedNoteTags']);
|
||||||
|
$r->connect(id: self::ACTOR_TAG_FILTER_ROUTE, uri_path: '/filter/edit-blocked-actor-tags/{note_id<\d+>?}', target: [Controller\TagBasedFiltering::class, 'editBlockedActorTags']);
|
||||||
|
return Event::next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAddExtraNoteActions(Request $request, Note $note, array &$actions)
|
||||||
|
{
|
||||||
|
$actions[] = [
|
||||||
|
'title' => _m('Block note tags'),
|
||||||
|
'classes' => '',
|
||||||
|
'url' => Router::url(self::NOTE_TAG_FILTER_ROUTE, ['note_id' => $note->getId()]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$actions[] = [
|
||||||
|
'title' => _m('Block people tags'),
|
||||||
|
'classes' => '',
|
||||||
|
'url' => Router::url(self::ACTOR_TAG_FILTER_ROUTE, ['note_id' => $note->getId()]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onFilterNoteList(Actor $actor, array $notes, ?array &$notes_out)
|
||||||
|
{
|
||||||
|
$blocked_note_tags = Cache::get(
|
||||||
|
self::cacheKeys($actor)['note'],
|
||||||
|
fn () => DB::dql('select ntb from note_tag_block ntb where ntb.blocker = :blocker', ['blocker' => $actor->getId()]),
|
||||||
|
);
|
||||||
|
$notes_out = F\reject(
|
||||||
|
$notes,
|
||||||
|
fn (Note $n) => F\some(
|
||||||
|
dump(NoteTag::getFromNoteId($n->getId())),
|
||||||
|
fn ($nt) => NoteTagBlock::checkBlocksNoteTag($nt, $blocked_note_tags),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return Event::next;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% import '/cards/note/view.html.twig' as noteView %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% if new_tags_form is not null %}
|
||||||
|
<div class="section-widget">
|
||||||
|
{{ noteView.macro_note(note, {}) }}
|
||||||
|
</div>
|
||||||
|
<p>{% trans %}Tags in the note above:{% endtrans %}</p>
|
||||||
|
{{ form(new_tags_form) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if existing_tags_form is not null %}
|
||||||
|
<p>{% trans %}Tags you already blocked:{% endtrans %}</p>
|
||||||
|
{{ form(existing_tags_form) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user