diff --git a/components/Conversation/Controller/Conversation.php b/components/Conversation/Controller/Conversation.php index 51936e15bc..15c50022fa 100644 --- a/components/Conversation/Controller/Conversation.php +++ b/components/Conversation/Controller/Conversation.php @@ -20,16 +20,23 @@ declare(strict_types = 1); /** * @author Hugo Sales - * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org + * @author Eliseu Amaro + * @copyright 2021-2022 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ namespace Component\Conversation\Controller; +use App\Core\DB\DB; +use App\Core\Form; +use function App\Core\I18n\_m; +use App\Util\Common; +use App\Util\Exception\RedirectException; +use Component\Conversation\Entity\ConversationBlock; use Component\Feed\Feed; use Component\Feed\Util\FeedController; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; -use function App\Core\I18n\_m; class Conversation extends FeedController { @@ -49,4 +56,24 @@ class Conversation extends FeedController 'page_title' => _m('Conversation'), ]; } + + public function muteConversation(Request $request, int $conversation_id) + { + $user = Common::ensureLoggedIn(); + $form = Form::create([ + ['mute_conversation', SubmitType::class, ['label' => _m('Mute conversation')]], + ]); + + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + DB::persist(ConversationBlock::create(['conversation_id' => $conversation_id, 'actor_id' => $user->getId()])); + DB::flush(); + throw new RedirectException(); + } + + return [ + '_template' => 'conversation/mute.html.twig', + 'form' => $form->createView(), + ]; + } } diff --git a/components/Conversation/Conversation.php b/components/Conversation/Conversation.php index c6beb0fb79..16089c35b0 100644 --- a/components/Conversation/Conversation.php +++ b/components/Conversation/Conversation.php @@ -26,16 +26,17 @@ namespace Component\Conversation; use App\Core\Cache; use App\Core\DB\DB; use App\Core\Event; +use function App\Core\I18n\_m; use App\Core\Modules\Component; use App\Core\Router\RouteLoader; use App\Core\Router\Router; +use App\Entity\Activity; use App\Entity\Actor; use App\Entity\Note; use App\Util\Common; use Component\Conversation\Controller\Reply as ReplyController; use Component\Conversation\Entity\Conversation as ConversationEntity; use Symfony\Component\HttpFoundation\Request; -use function App\Core\I18n\_m; class Conversation extends Component { @@ -126,8 +127,8 @@ class Conversation extends Component // Will have actors array, and action string // Actors are the subjects, action is the verb (in the final phrase) - $reply_actors = []; - $note_replies = $note->getReplies(); + $reply_actors = []; + $note_replies = $note->getReplies(); // Get actors who repeated the note foreach ($note_replies as $reply) { @@ -138,8 +139,8 @@ class Conversation extends Component } // Filter out multiple replies from the same actor - $reply_actors = array_unique($reply_actors, SORT_REGULAR); - $result[] = ['actors' => $reply_actors, 'action' => 'replied to']; + $reply_actors = array_unique($reply_actors, \SORT_REGULAR); + $result[] = ['actors' => $reply_actors, 'action' => 'replied to']; return Event::next; } @@ -148,6 +149,7 @@ class Conversation extends Component { $r->connect('reply_add', '/object/note/new?to={actor_id<\d+>}&reply_to={note_id<\d+>}', [ReplyController::class, 'addReply']); $r->connect('conversation', '/conversation/{conversation_id<\d+>}', [Controller\Conversation::class, 'showConversation']); + $r->connect('conversation_mute', '/conversation/{conversation_id<\d+>}/mute', [Controller\Conversation::class, 'muteConversation']); return Event::next; } @@ -174,4 +176,39 @@ class Conversation extends Component Cache::delete("note-replies-{$note->getId()}"); return Event::next; } + + public function onAddExtraNoteActions(Request $request, Note $note, array &$actions) + { + if (\is_null($actor = Common::actor())) { + return Event::next; + } + + $actions[] = [ + 'title' => _m('Mute conversation'), + 'classes' => '', + 'url' => Router::url('conversation_mute', ['conversation_id' => $note->getConversationId()]), + ]; + + return Event::next; + } + + public function onNewNotificationShould(Activity $activity, Actor $actor) + { + if ($activity->getObjectType() === 'note') { + $is_blocked = !empty(DB::dql( + <<<'EOQ' + select 1 + from note n + join conversation_block cb with n.conversation_id = cb.conversation_id + where n.id = :object_id + EOQ, + ['object_id' => $activity->getObjectId()], + )); + if ($is_blocked) { + return Event::stop; + } else { + return Event::next; + } + } + } } diff --git a/components/Conversation/Entity/ConversationBlock.php b/components/Conversation/Entity/ConversationBlock.php new file mode 100644 index 0000000000..d83828fb8f --- /dev/null +++ b/components/Conversation/Entity/ConversationBlock.php @@ -0,0 +1,95 @@ +. + +// }}} + +namespace Component\Conversation\Entity; + +use App\Core\Entity; +use DateTimeInterface; + +/** + * Entity class for ConversationsBlocks + * + * @category DB + * @package GNUsocial + * + * @author Hugo Sales + * @copyright 2022 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ConversationBlock extends Entity +{ + // {{{ Autocode + // @codeCoverageIgnoreStart + private int $conversation_id; + private int $actor_id; + private DateTimeInterface $created; + + public function setConversationId(int $conversation_id): self + { + $this->conversation_id = $conversation_id; + return $this; + } + + public function getConversationId(): int + { + return $this->conversation_id; + } + + public function setActorId(int $actor_id): self + { + $this->actor_id = $actor_id; + return $this; + } + + public function getActorId(): int + { + return $this->actor_id; + } + + public function setCreated(DateTimeInterface $created): self + { + $this->created = $created; + return $this; + } + + public function getCreated(): DateTimeInterface + { + return $this->created; + } + + // @codeCoverageIgnoreEnd + // }}} Autocode + + public static function schemaDef(): array + { + return [ + 'name' => 'conversation_block', + 'fields' => [ + 'conversation_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Conversation.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'The conversation being blocked'], + 'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'Who blocked the conversation'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'], + ], + 'primary key' => ['conversation_id', 'actor_id'], + ]; + } +} diff --git a/components/Conversation/templates/conversation/mute.html.twig b/components/Conversation/templates/conversation/mute.html.twig new file mode 100644 index 0000000000..d8eda52b9f --- /dev/null +++ b/components/Conversation/templates/conversation/mute.html.twig @@ -0,0 +1,5 @@ +{% extends 'base.html.twig' %} + +{% block body %} + {{ form(form) }} +{% endblock body %}