From fa863d9e0366faf7401a88100ffa6767579bb598 Mon Sep 17 00:00:00 2001 From: Hugo Sales Date: Tue, 21 Dec 2021 12:12:03 +0000 Subject: [PATCH] [CONTROLLER][ENTITY][Actor] Add way of creating a group that doesn't exist --- src/Controller/Actor.php | 94 +++++++++++++++++++++++----- src/Entity/Actor.php | 55 ++++++++++++++-- src/Entity/GroupMember.php | 8 ++- templates/actor/group_view.html.twig | 41 ++++++++++++ templates/actor/view.html.twig | 4 +- 5 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 templates/actor/group_view.html.twig diff --git a/src/Controller/Actor.php b/src/Controller/Actor.php index 07ac511f2a..d9a8c0ee54 100644 --- a/src/Controller/Actor.php +++ b/src/Controller/Actor.php @@ -26,23 +26,26 @@ namespace App\Controller; use App\Core\Cache; use App\Core\Controller\ActorController; use App\Core\DB\DB; -use App\Core\Router\Router; -use Symfony\Component\HttpFoundation\RedirectResponse; +use App\Core\Form; use function App\Core\I18n\_m; -use App\Util\Exception\ClientException; +use App\Core\Log; +use App\Entity as E; +use App\Util\Common; +use App\Util\Exception\RedirectException; +use App\Util\Nickname; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; class Actor extends ActorController { - public function actorViewId(Request $request, int $id) { return $this->handleActorById( $id, fn ($actor) => [ '_template' => 'actor/view.html.twig', - 'actor' => $actor - ] + 'actor' => $actor, + ], ); } @@ -52,21 +55,80 @@ class Actor extends ActorController $nickname, fn ($actor) => [ '_template' => 'actor/view.html.twig', - 'actor' => $actor, - 'notes' => \App\Entity\Note::getAllNotesByActor($actor) - ] + 'actor' => $actor, + 'notes' => \App\Entity\Note::getAllNotesByActor($actor), + ], ); } - /** - * The page where the actor's info is shown - */ - public function ActorShowId(Request $request, int $id) + public function groupViewId(Request $request, int $id) { - return $this->ActorById($id, fn ($actor) => ['_template' => 'actor/view.html.twig', 'actor' => $actor]); + return $this->handleActorById( + $id, + fn ($actor) => [ + '_template' => 'actor/group_view.html.twig', + 'actor' => $actor, + ], + ); } - public function ActorShowNickname(Request $request, string $nickname) + + public function groupViewNickname(Request $request, string $nickname) { - return $this->ActorByNickname($nickname, fn ($actor) => ['_template' => 'actor/view.html.twig', 'actor' => $actor, 'notes' => \App\Entity\Note::getAllNotesByActor($actor)]); + Nickname::validate($nickname, which: Nickname::CHECK_LOCAL_GROUP); // throws + $group = E\Actor::getByNickname($nickname, type: E\Actor::GROUP); + if (\is_null($group)) { + $actor = Common::actor(); + if (!\is_null($actor)) { + $form = Form::create([ + ['create', SubmitType::class, ['label' => _m('Create this group')]], + ]); + + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + Log::info( + _m( + 'Actor id:{actor_id} nick:{actor_nick} created the group {nickname}', + ['{actor_id}' => $actor->getId(), 'actor_nick' => $actor->getNickname(), 'nickname' => $nickname], + ), + ); + + $group = E\Actor::create([ + 'nickname' => $nickname, + 'type' => E\Actor::GROUP, + 'is_local' => true, + ]); + DB::persist($group); + DB::persist(E\Subscription::create([ + 'subscriber' => $group->getId(), + 'subscribed' => $group->getId(), + ])); + DB::persist(E\Subscription::create([ + 'subscriber' => $actor->getId(), + 'subscribed' => $group->getId(), + ])); + DB::persist(E\GroupMember::create([ + 'group_id' => $group->getId(), + 'actor_id' => $actor->getId(), + 'is_admin' => true, + ])); + DB::flush(); + Cache::delete(self::cacheKeys($actor->getId())['subscriber']); + Cache::delete(self::cacheKeys($actor->getId())['subscribed']); + throw new RedirectException; + } + + return [ + '_template' => 'actor/group_view.html.twig', + 'nickname' => $nickname, + 'create_form' => $form->createView(), + ]; + } + } + + return [ + '_template' => 'actor/group_view.html.twig', + 'actor' => $group, + 'nickname' => $group->getNickname(), + ]; } } diff --git a/src/Entity/Actor.php b/src/Entity/Actor.php index e1d16ddd2a..9d691212bc 100644 --- a/src/Entity/Actor.php +++ b/src/Entity/Actor.php @@ -29,8 +29,10 @@ use App\Core\Entity; use App\Core\Event; use App\Core\Router\Router; use App\Core\UserRoles; +use App\Util\Exception\DuplicateFoundException; use App\Util\Exception\NicknameException; use App\Util\Exception\NotFoundException; +use App\Util\Formatting; use App\Util\Nickname; use Component\Avatar\Avatar; use Component\Tag\Tag as TagComponent; @@ -261,7 +263,7 @@ class Actor extends Entity ]; } - public function getLocalUser() + public function getLocalUser(): ?LocalUser { if ($this->getIsLocal()) { return DB::findOneBy('local_user', ['id' => $this->getId()]); @@ -270,10 +272,33 @@ class Actor extends Entity } } - public function isGroup() + /** + * @return ?self + */ + public static function getByNickname(string $nickname, int $type = self::PERSON): ?self { - // TODO: implement - return false; + try { + return DB::findOneBy(self::class, ['nickname' => $nickname, 'type' => $type]); + } catch (NotFoundException) { + return null; + } catch (DuplicateFoundException $e) { + throw new BugFoundException("Multiple actors with the same nickname '{$nickname}' found", previous: $e); + } + } + + public function __call(string $name, array $arguments): mixed + { + if (Formatting::startsWith($name, 'is')) { + $type = Formatting::removePrefix($name, 'is'); + $const = self::class . '::' . mb_strtoupper($type); + if (\defined($const)) { + return $this->type === \constant($const); + } else { + throw new BugFoundException("Actor cannot be a '{$type}', check your spelling"); + } + } else { + return parent::__call($name, $arguments); + } } public function getAvatarUrl(string $size = 'full') @@ -467,7 +492,16 @@ class Actor extends Entity { $uri = null; if (Event::handle('StartGetActorUri', [$this, $type, &$uri]) === Event::next) { - $uri = Router::url('actor_view_id', ['id' => $this->getId()], $type); + switch ($this->type) { + case self::PERSON: + case self::ORGANIZATION: + case self::BUSINESS: + case self::BOT: + $uri = Router::url('actor_view_id', ['id' => $this->getId()], $type); + break; + case self::GROUP: + $uri = Router::url('group_actor_view_id', ['id' => $this->getId()], $type); + } Event::handle('EndGetActorUri', [$this, $type, &$uri]); } return $uri; @@ -478,7 +512,16 @@ class Actor extends Entity $url = null; if (Event::handle('StartGetActorUrl', [$this, $type, &$url]) === Event::next) { if ($this->getIsLocal()) { - $url = Router::url('actor_view_nickname', ['nickname' => $this->getNickname()], $type); + switch ($this->type) { + case self::PERSON: + case self::ORGANIZATION: + case self::BUSINESS: + case self::BOT: + $url = Router::url('actor_view_nickname', ['nickname' => $this->getNickname()], $type); + break; + case self::GROUP: + $url = Router::url('group_actor_view_nickname', ['nickname' => $this->getNickname()], $type); + } } else { return $this->getUri($type); } diff --git a/src/Entity/GroupMember.php b/src/Entity/GroupMember.php index 59da000354..f96bb8090f 100644 --- a/src/Entity/GroupMember.php +++ b/src/Entity/GroupMember.php @@ -1,5 +1,7 @@ 'group_member', 'fields' => [ - 'group_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Group.id', 'multiplicity' => 'one to one', 'name' => 'group_member_group_id_fkey', 'not null' => true, 'description' => 'foreign key to group table'], + 'group_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'group_member_group_id_fkey', 'not null' => true, 'description' => 'foreign key to group table'], 'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'group_member_actor_id_fkey', 'not null' => true, 'description' => 'foreign key to actor table'], 'is_admin' => ['type' => 'bool', 'default' => false, 'description' => 'is this actor an admin?'], 'uri' => ['type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'], diff --git a/templates/actor/group_view.html.twig b/templates/actor/group_view.html.twig new file mode 100644 index 0000000000..fc41bb84df --- /dev/null +++ b/templates/actor/group_view.html.twig @@ -0,0 +1,41 @@ +{% extends 'stdgrid.html.twig' %} +{% import '/cards/note/view.html.twig' as noteView %} + +{% set nickname = nickname|escape %} + +{% block title %}{{ nickname }}{% endblock %} + +{% block stylesheets %} + {{ parent() }} + +{% endblock stylesheets %} + +{% block body %} + + {% if actor is defined and actor is not null %} + {% block profile_view %} + {% include 'cards/profile/view.html.twig' %} + {% endblock profile_view %} + +
+
+ {% if notes is defined and notes is not empty %} + {% for note in notes %} + {% block current_note %} + {{ noteView.macro_note(note, null) }} +
+ {% endblock current_note %} + {% endfor %} + {% else %} +

{% trans %}No notes here.{% endtrans %}

+ {% endif %} +
+
+ + {% else %} +
+

{% trans with { '%group%': nickname } %}The group %group% doesn't exist. Would you like to create it?{% endtrans %}

+ {{ form(create_form) }} +
+ {% endif %} +{% endblock body %} diff --git a/templates/actor/view.html.twig b/templates/actor/view.html.twig index 27f5a671d4..bd10975741 100644 --- a/templates/actor/view.html.twig +++ b/templates/actor/view.html.twig @@ -1,7 +1,9 @@ {% extends 'stdgrid.html.twig' %} {% import '/cards/note/view.html.twig' as noteView %} -{% block title %}{{ actor.getNickname() ~ '\'s profile' | trans }}{% endblock %} +{% set nickname = nickname|escape %} + +{% block title %}{% trans with {'%nick%': actor.getNickname()} %}%nick%'s profile{% endtrans %}{% endblock %}y {% block stylesheets %} {{ parent() }}