diff --git a/plugins/TagBasedFiltering/Controller/TagBasedFiltering.php b/plugins/TagBasedFiltering/Controller/TagBasedFiltering.php index 4f05bc893d..3bd523ee70 100644 --- a/plugins/TagBasedFiltering/Controller/TagBasedFiltering.php +++ b/plugins/TagBasedFiltering/Controller/TagBasedFiltering.php @@ -28,12 +28,14 @@ use App\Core\Controller; use App\Core\DB\DB; use App\Core\Form; use function App\Core\I18n\_m; +use App\Entity\Actor; +use App\Entity\ActorTag; +use App\Entity\ActorTagBlock; 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; @@ -46,23 +48,24 @@ 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::getById($note_id) : null; - $note_tag_blocks = NoteTagBlock::getByActorId($user->getId()); - $note_tags = !\is_null($note_id) ? NoteTag::getByNoteId($note_id) : []; - $blockable_note_tags = F\reject( - $note_tags, - fn (NoteTag $nt) => NoteTagBlock::checkBlocksNoteTag($nt, $note_tag_blocks), - ); + private function editBlocked( + Request $request, + ?int $id, + string $type_name, + callable $calculate_target, + callable $calculate_blocks, + callable $calculate_tags, + string $new_label, + string $existing_label, + string $block_class, + ) { + $user = Common::ensureLoggedIn(); + $target = $calculate_target(); + $tag_blocks = $calculate_blocks($user); + $blockable_tags = $calculate_tags($tag_blocks); $new_tags_form_definition = []; - - foreach ($blockable_note_tags as $nt) { + foreach ($blockable_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]]; @@ -70,7 +73,7 @@ class TagBasedFiltering extends Controller } $existing_tags_form_definition = []; - foreach ($note_tag_blocks as $ntb) { + foreach ($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')]]; @@ -78,7 +81,7 @@ class TagBasedFiltering extends Controller } $new_tags_form = null; - if (!empty($new_tags_form_definition)) { + if (!empty($new_tags_form_definition) && $user->getId() !== $target->getActorId()) { $new_tags_form = Form::create($new_tags_form_definition); $new_tags_form->handleRequest($request); if ($new_tags_form->isSubmitted() && $new_tags_form->isValid()) { @@ -89,11 +92,12 @@ class TagBasedFiltering extends Controller /** @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']); + Cache::delete($block_class::cacheKey($user->getId())); + Cache::delete(TagFilerPlugin::cacheKeys($user->getId())[$type_name]); $new_tag = Tag::ensureValid($data[$canon . ':new-tag']); - $canonical_tag = Tag::canonicalTag($new_tag, Language::getByNote($note)->getLocale()); - DB::persist(NoteTagBlock::create([ + $language = $target instanceof Note ? Language::getByNote($target)->getLocale() : $user->getActor()->getTopLanguage()->getLocale(); + $canonical_tag = Tag::canonicalTag($new_tag, $language); + DB::persist($block_class::create([ 'blocker' => $user->getId(), 'tag' => $new_tag, 'canonical' => $canonical_tag, @@ -119,17 +123,17 @@ class TagBasedFiltering extends Controller /** @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']); + Cache::delete($block_class::cacheKey($user->getId())); + Cache::delete(TagFilerPlugin::cacheKeys($user->getId())[$type_name]); switch ($type) { case 'toggle-canon': - $ntb = DB::getReference('note_tag_block', ['blocker' => $user->getId(), 'canonical' => $canon]); + $ntb = DB::getReference($block_class, ['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]); + DB::removeBy($block_class, ['blocker' => $user->getId(), 'canonical' => $canon]); throw new RedirectException; } } @@ -140,14 +144,50 @@ class TagBasedFiltering extends Controller return [ '_template' => 'tag-based-filtering/edit-tags.html.twig', - 'note' => !\is_null($note_id) ? Note::getById($note_id) : null, + $type_name => $target, 'new_tags_form' => $new_tags_form?->createView(), 'existing_tags_form' => $existing_tags_form?->createView(), + 'new_label' => $new_label, + 'existing_label' => $existing_label, ]; } - public function editBlockedActorTags(Request $request, ?int $note_id) + /** + * 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) { - throw new NotImplementedException; + return $this->editBlocked( + request: $request, + id: $note_id, + type_name: 'note', + calculate_target: fn () => !\is_null($note_id) ? Note::getById($note_id) : null, + calculate_blocks: fn ($user) => NoteTagBlock::getByActorId($user->getId()), + calculate_tags: fn ($tag_blocks) => F\reject( + !\is_null($note_id) ? NoteTag::getByNoteId($note_id) : [], + fn (NoteTag $nt) => NoteTagBlock::checkBlocksNoteTag($nt, $tag_blocks), + ), + new_label: _m('Tags in the note above:'), + existing_label: _m('Tags you already blocked:'), + block_class: NoteTagBlock::class, + ); + } + + public function editBlockedActorTags(Request $request, ?int $actor_id) + { + return $this->editBlocked( + request: $request, + id: $actor_id, + type_name: 'actor', + calculate_target: fn () => !\is_null($actor_id) ? Actor::getById($actor_id) : null, + calculate_blocks: fn ($user) => ActorTagBlock::getByActorId($user->getId()), + calculate_tags: fn ($tag_blocks) => F\reject( + !\is_null($actor_id) ? ActorTag::getByActorId($actor_id) : [], + fn (ActorTag $nt) => ActorTagBlock::checkBlocksActorTag($nt, $tag_blocks), + ), + new_label: _m('Tags of the account above:'), + existing_label: _m('Tags you already blocked:'), + block_class: ActorTagBlock::class, + ); } } diff --git a/plugins/TagBasedFiltering/TagBasedFiltering.php b/plugins/TagBasedFiltering/TagBasedFiltering.php index bfa528da72..a476fc6005 100644 --- a/plugins/TagBasedFiltering/TagBasedFiltering.php +++ b/plugins/TagBasedFiltering/TagBasedFiltering.php @@ -48,13 +48,13 @@ class TagBasedFiltering extends Plugin if (!\is_int($actor_id)) { $actor_id = $actor_id->getId(); } - return ['note' => "filtered-tags-{$actor_id}"]; + return ['note' => "blocked-note-tags-{$actor_id}", 'actor' => "blocked-actor-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']); + $r->connect(id: self::ACTOR_TAG_FILTER_ROUTE, uri_path: '/filter/edit-blocked-actor-tags/{actor_id<\d+>?}', target: [Controller\TagBasedFiltering::class, 'editBlockedActorTags']); return Event::next; } @@ -69,7 +69,7 @@ class TagBasedFiltering extends Plugin $actions[] = [ 'title' => _m('Block people tags'), 'classes' => '', - 'url' => Router::url(self::ACTOR_TAG_FILTER_ROUTE, ['note_id' => $note->getId()]), + 'url' => Router::url(self::ACTOR_TAG_FILTER_ROUTE, ['actor_id' => $note->getActor()->getId()]), ]; } diff --git a/plugins/TagBasedFiltering/templates/tag-based-filtering/edit-tags.html.twig b/plugins/TagBasedFiltering/templates/tag-based-filtering/edit-tags.html.twig index 8bf2fd9b13..67ab29d301 100644 --- a/plugins/TagBasedFiltering/templates/tag-based-filtering/edit-tags.html.twig +++ b/plugins/TagBasedFiltering/templates/tag-based-filtering/edit-tags.html.twig @@ -2,15 +2,22 @@ {% import '/cards/note/view.html.twig' as noteView %} {% block body %} - {% if new_tags_form is not null %} -
- {{ noteView.macro_note(note, {}) }} + {% if note is defined or actor is defined %} +
+ {% if note is defined %} + {{ noteView.macro_note(note, {}) }} + {% elseif actor is defined %} + {% include 'cards/profile/view.html.twig' with {'actor': actor} only %} + {% endif %}
-

{% trans %}Tags in the note above:{% endtrans %}

+ {% endif %} + {% if new_tags_form is not null %} +

{{ new_label }}

{{ form(new_tags_form) }} +
{% endif %} {% if existing_tags_form is not null %} -

{% trans %}Tags you already blocked:{% endtrans %}

+

{{ existing_label }}

{{ form(existing_tags_form) }} {% endif %} {% endblock %} diff --git a/src/Controller/UserPanel.php b/src/Controller/UserPanel.php index fdbcaec58f..b8de5fcd7a 100644 --- a/src/Controller/UserPanel.php +++ b/src/Controller/UserPanel.php @@ -263,6 +263,7 @@ class UserPanel extends Controller $extra_step = function ($data, $extra_args) use ($user, $actor) { $user->setNicknameSanitizedAndCached($data['nickname'], $actor->getId()); }; + return Form::handle($form_definition, $request, $actor, $extra, $extra_step, [['self_tags' => $extra['self_tags']]]); } diff --git a/src/Core/DB/DB.php b/src/Core/DB/DB.php index 0ef5aadc16..6d6aaa5ba6 100644 --- a/src/Core/DB/DB.php +++ b/src/Core/DB/DB.php @@ -58,7 +58,7 @@ use Functional as F; * @method static void persist(object $entity) // Tells the EntityManager to make an instance managed and persistent. * @method static bool contains(object $entity) // Determines whether an entity instance is managed in this EntityManager. * @method static void flush() // Flushes the in-memory state of persisted objects to the database. - * @method mixed wrapInTransaction(callable $func) // Executes a function in a transaction. Warning: suppresses exceptions + * @method mixed wrapInTransaction(callable $func) // Executes a function in a transaction. Warning: suppresses exceptions */ class DB { @@ -263,9 +263,14 @@ class DB } } - public static function removeBy(string $table, array $criteria) + public static function removeBy(string $table, array $criteria): void { - self::remove(self::getReference($table, $criteria)); + $class = self::$table_map[$table]; + if (empty(array_intersect(self::getPKForClass($class), array_keys($criteria)))) { + self::remove(self::findOneBy($class, $criteria)); + } else { + self::remove(self::getReference($table, $criteria)); + } } public static function count(string $table, array $criteria) diff --git a/src/Entity/Actor.php b/src/Entity/Actor.php index 6fc4c1faff..5e66c55e10 100644 --- a/src/Entity/Actor.php +++ b/src/Entity/Actor.php @@ -270,6 +270,14 @@ class Actor extends Entity return Cache::get('actor-fullname-id-' . $id, fn () => self::getById($id)->getFullname()); } + /** + * For consistency with Note + */ + public function getActorId(): int + { + return $this->getId(); + } + /** * Tags attributed to self, shortcut function for increased legibility * diff --git a/src/Entity/ActorCircle.php b/src/Entity/ActorCircle.php index e4718ae0c7..7c555cd0d4 100644 --- a/src/Entity/ActorCircle.php +++ b/src/Entity/ActorCircle.php @@ -165,7 +165,7 @@ class ActorCircle extends Entity 'name' => 'actor_circle', 'description' => 'a actor can have lists of actors, to separate their feed', 'fields' => [ - 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'], + 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'], 'tagger' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'many to one', 'name' => 'actor_list_tagger_fkey', 'not null' => true, 'description' => 'user making the tag'], 'tag' => ['type' => 'varchar', 'length' => 64, 'foreign key' => true, 'target' => 'ActorTag.canonical', 'multiplicity' => 'many to one', 'not null' => true, 'description' => 'actor tag'], // Join with ActorTag // // so, Doctrine doesn't like that the target is not unique, even though the pair is 'description' => ['type' => 'text', 'description' => 'description of the people tag'], diff --git a/src/Entity/ActorTag.php b/src/Entity/ActorTag.php index 2d8de319f5..22c89e2aa6 100644 --- a/src/Entity/ActorTag.php +++ b/src/Entity/ActorTag.php @@ -21,6 +21,7 @@ declare(strict_types = 1); namespace App\Entity; +use App\Core\Cache; use App\Core\DB\DB; use App\Core\Entity; use App\Core\Router\Router; @@ -109,6 +110,19 @@ class ActorTag extends Entity // @codeCoverageIgnoreEnd // }}} Autocode + public static function cacheKey(int|Actor $actor_id) + { + if (!\is_int($actor_id)) { + $actor_id = $actor_id->getId(); + } + return "actor-tags-{$actor_id}"; + } + + public static function getByActorId(int $actor_id): array + { + return Cache::getList(self::cacheKey($actor_id), fn () => DB::dql('select at from actor_tag at join actor a with a.id = at.tagger where a.id = :id', ['id' => $actor_id])); + } + public function getUrl(?Actor $actor = null): string { $params = ['tag' => $this->getCanonical()]; diff --git a/src/Entity/ActorTagBlock.php b/src/Entity/ActorTagBlock.php index 65031592dd..ec82957a75 100644 --- a/src/Entity/ActorTagBlock.php +++ b/src/Entity/ActorTagBlock.php @@ -26,6 +26,7 @@ use App\Core\DB\DB; use App\Core\Entity; use Component\Tag\Tag; use DateTimeInterface; +use Functional as F; /** * Entity for User's Note Tag block