From d6f31d102a552a6f57d2544bfb7d47c585d89cc1 Mon Sep 17 00:00:00 2001 From: Diogo Peralta Cordeiro Date: Thu, 16 Sep 2021 17:04:05 +0100 Subject: [PATCH] [CORE][ActivityStreamsTwo][ActivityPub] Set all routes Allow global routes to act for every actor Fix Favoured stream query --- components/Avatar/Avatar.php | 2 +- components/Left/Left.php | 1 + plugins/ActivityPub/ActivityPub.php | 18 ++++- .../ActivityStreamsTwo/ActivityStreamsTwo.php | 34 ++++----- .../Util/Model/EntityToType/GSActorToType.php | 4 +- plugins/Favourite/Controller/Favourite.php | 32 ++++++--- plugins/Favourite/Favourite.php | 16 +++-- src/Controller/GSActor.php | 2 +- src/Controller/Subscribers.php | 69 +++++++++++++++++++ src/Controller/Subscriptions.php | 69 +++++++++++++++++++ src/Core/Router/RouteLoader.php | 1 - src/Routes/GSActor.php | 9 +-- src/Routes/Main.php | 7 +- src/Routes/Note.php | 6 +- src/Routes/Subscribers.php | 47 +++++++++++++ src/Routes/Subscriptions.php | 47 +++++++++++++ templates/sidepanel/left/left.html.twig | 2 +- 17 files changed, 308 insertions(+), 58 deletions(-) create mode 100644 src/Controller/Subscribers.php create mode 100644 src/Controller/Subscriptions.php create mode 100644 src/Routes/Subscribers.php create mode 100644 src/Routes/Subscriptions.php diff --git a/components/Avatar/Avatar.php b/components/Avatar/Avatar.php index da5ddb49df..c58060cca2 100644 --- a/components/Avatar/Avatar.php +++ b/components/Avatar/Avatar.php @@ -38,7 +38,7 @@ class Avatar extends Component public function onAddRoute($r): bool { - $r->connect('avatar', '/{gsactor_id<\d+>}/avatar/{size?full}', [Controller\Avatar::class, 'avatar_view']); + $r->connect('avatar', '/actor/{gsactor_id<\d+>}/avatar/{size?full}', [Controller\Avatar::class, 'avatar_view']); $r->connect('settings_avatar', '/settings/avatar', [Controller\Avatar::class, 'settings_avatar']); return Event::next; } diff --git a/components/Left/Left.php b/components/Left/Left.php index c4e0050fe3..8105037178 100644 --- a/components/Left/Left.php +++ b/components/Left/Left.php @@ -33,6 +33,7 @@ class Left extends Component $user = Common::user(); if ($user != null) { $actor = $user->getActor(); + $vars['user_id'] = $user->getId(); $vars['user_nickname'] = $user->getNickname(); $vars['user_tags'] = $actor->getSelfTags(); $vars['user_followers'] = $actor->getFollowersCount(); diff --git a/plugins/ActivityPub/ActivityPub.php b/plugins/ActivityPub/ActivityPub.php index 1e3aed1eb2..be52aa026f 100644 --- a/plugins/ActivityPub/ActivityPub.php +++ b/plugins/ActivityPub/ActivityPub.php @@ -26,9 +26,21 @@ class ActivityPub extends Plugin */ public function onAddRoute(RouteLoader $r): bool { + $r->connect( + 'activitypub_actor_inbox', + '/actor/{gsactor_id<\d+>}/inbox.json', + [Inbox::class, 'handle'], + options: ['accept' => ActivityStreamsTwo::$accept_headers] + ); + $r->connect( + 'activitypub_actor_outbox', + '/actor/{gsactor_id<\d+>}/outbox.json', + [Inbox::class, 'handle'], + options: ['accept' => ActivityStreamsTwo::$accept_headers] + ); $r->connect( 'activitypub_inbox', - '{gsactor_id<\d+>}/inbox', + '/inbox.json', [Inbox::class, 'handle'], options: ['accept' => ActivityStreamsTwo::$accept_headers] ); @@ -48,12 +60,12 @@ class ActivityPub extends Plugin public static function validateAcceptHeader(array|string|null $accept, bool $strict): bool { if (is_string($accept) - && in_array($accept, self::$accept_headers) + && in_array($accept, ActivityStreamsTwo::$accept_headers) ) { return true; } elseif (is_array($accept) && count( - array_intersect($accept, self::$accept_headers) + array_intersect($accept, ActivityStreamsTwo::$accept_headers) ) > 0 ) { return true; diff --git a/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php b/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php index cbee726f0a..a0b804fe9e 100644 --- a/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php +++ b/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php @@ -4,7 +4,6 @@ namespace Plugin\ActivityStreamsTwo; use App\Core\Event; use App\Core\Modules\Plugin; -use App\Core\Router\RouteLoader; use Exception; use Plugin\ActivityStreamsTwo\Util\Response\ActorResponse; use Plugin\ActivityStreamsTwo\Util\Response\NoteResponse; @@ -41,33 +40,24 @@ class ActivityStreamsTwo extends Plugin return Event::next; } switch ($route) { + case 'actor_view_id': + case 'actor_view_nickname': + $response = ActorResponse::handle($vars['gsactor']); + return Event::stop; case 'note_view': $response = NoteResponse::handle($vars['note']); return Event::stop; - case 'gsactor_view_id': - case 'gsactor_view_nickname': - $response = ActorResponse::handle($vars['gsactor']); + case 'actor_favourites': + $response = LikeResponse::handle($vars['gsactor']); + return Event::stop; + case 'actor_subscriptions': + $response = FollowingResponse::handle($vars['gsactor']); + return Event::stop; + case 'actor_subscribers': + $response = FollowersResponse::handle($vars['gsactor']); return Event::stop; default: return Event::next; } } - - /** - * This code executes when GNU social creates the page routing, and we hook - * on this event to add our action handler for Embed. - * - * @param RouteLoader $r the router that was initialized. - * - * @return bool - */ - public function onAddRoute(RouteLoader $r): bool - { - /*$r->connect('note_view_as2', - '/note/{id<\d+>}', - [NoteResponse::class, 'handle'], - options: ['accept' => self::$accept_headers] - );*/ - return Event::next; - } } diff --git a/plugins/ActivityStreamsTwo/Util/Model/EntityToType/GSActorToType.php b/plugins/ActivityStreamsTwo/Util/Model/EntityToType/GSActorToType.php index 76587f7dba..ecc8a13b22 100644 --- a/plugins/ActivityStreamsTwo/Util/Model/EntityToType/GSActorToType.php +++ b/plugins/ActivityStreamsTwo/Util/Model/EntityToType/GSActorToType.php @@ -19,7 +19,7 @@ class GSActorToType */ public static function translate(GSActor $gsactor) { - $uri = Router::url('gsactor_view_id', ['id' => $gsactor->getId()], Router::ABSOLUTE_URL); + $uri = Router::url('actor_view_id', ['id' => $gsactor->getId()], Router::ABSOLUTE_URL); $attr = [ '@context' => 'https://www.w3.org/ns/activitystreams', 'id' => $uri, @@ -42,7 +42,7 @@ class GSActorToType 'summary' => $gsactor->getBio(), //'tag' => 'updated' => $gsactor->getModified()->format(DateTimeInterface::RFC3339), - 'url' => Router::url('gsactor_view_nickname', ['nickname' => $gsactor->getNickname()], Router::ABSOLUTE_URL), + 'url' => Router::url('actor_view_nickname', ['nickname' => $gsactor->getNickname()], Router::ABSOLUTE_URL), ]; return Type::create(type: 'Person', attributes: $attr); } diff --git a/plugins/Favourite/Controller/Favourite.php b/plugins/Favourite/Controller/Favourite.php index 89f83a7079..f158b7dfb3 100644 --- a/plugins/Favourite/Controller/Favourite.php +++ b/plugins/Favourite/Controller/Favourite.php @@ -23,17 +23,19 @@ namespace Plugin\Favourite\Controller; use App\Core\DB\DB; use App\Core\Event; -use App\Util\Common; use Symfony\Component\HttpFoundation\Request; class Favourite { - public function favourites(Request $request) + public function favouritesByActorId(Request $request, int $id) { - $actor_id = Common::ensureLoggedIn()->getId(); - $notes = DB::dql('select f from Plugin\Favourite\Entity\Favourite f ' . - 'where f.gsactor_id = :id ' . - 'order by f.created DESC', ['id' => $actor_id]); + $notes = DB::dql( + 'select n from App\Entity\Note n, Plugin\Favourite\Entity\Favourite f ' . + 'where n.id = f.note_id ' . + 'and f.gsactor_id = :id ' . + 'order by f.created DESC', + ['id' => $id] + ); $notes_out = null; Event::handle('FormatNoteList', [$notes, &$notes_out]); @@ -43,6 +45,11 @@ class Favourite 'notes' => $notes_out, ]; } + public function favouritesByActorNickname(Request $request, string $nickname) + { + $user = DB::findOneBy('local_user', ['nickname' => $nickname]); + return self::favouritesByActorId($request, $user->getId()); + } /** * Reverse favourites stream @@ -53,15 +60,15 @@ class Favourite * * @return array template */ - public function reverseFavourites(Request $request) + public function reverseFavouritesByActorId(Request $request, int $id) { - $actor_id = Common::ensureLoggedIn()->getId(); - $notes = DB::dql('select n from App\Entity\Note n, Plugin\Favourite\Entity\Favourite f ' . + $notes = DB::dql('select n from App\Entity\Note n, Plugin\Favourite\Entity\Favourite f ' . 'where n.id = f.note_id ' . 'and f.gsactor_id != :id ' . 'and n.gsactor_id = :id ' . 'order by f.created DESC' , - ['id' => $actor_id]); + ['id' => $id] + ); $notes_out = null; Event::handle('FormatNoteList', [$notes, &$notes_out]); @@ -71,4 +78,9 @@ class Favourite 'notes' => $notes, ]; } + public function reverseFavouritesByActorNickname(Request $request, string $nickname) + { + $user = DB::findOneBy('local_user', ['nickname' => $nickname]); + return self::reverseFavouritesByActorId($request, $user->getId()); + } } diff --git a/plugins/Favourite/Favourite.php b/plugins/Favourite/Favourite.php index 46e2b8f744..0486d9d872 100644 --- a/plugins/Favourite/Favourite.php +++ b/plugins/Favourite/Favourite.php @@ -24,6 +24,7 @@ namespace Plugin\Favourite; use App\Core\DB\DB; use App\Core\Event; use App\Core\Form; +use function App\Core\I18n\_m; use App\Core\Modules\NoteHandlerPlugin; use App\Core\Router\RouteLoader; use App\Entity\Note; @@ -36,7 +37,6 @@ use App\Util\Nickname; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; -use function App\Core\I18n\_m; class Favourite extends NoteHandlerPlugin { @@ -60,7 +60,7 @@ class Favourite extends NoteHandlerPlugin return Event::next; } - // if note is favourited, "is_set" is 1 + // if note is favoured, "is_set" is 1 $opts = ['note_id' => $note->getId(), 'gsactor_id' => $user->getId()]; $is_set = DB::find('favourite', $opts) !== null; $form_fav = Form::create([ @@ -83,7 +83,7 @@ class Favourite extends NoteHandlerPlugin /** * Called from form handler * - * @param Note $note to be favourited + * @param Note $note to be favoured * @param Form $data input * * @throws RedirectException Always thrown in order to prevent accidental form re-submit from browser @@ -117,16 +117,18 @@ class Favourite extends NoteHandlerPlugin public function onInsertLeftPanelLink(string $user_nickname, &$res): bool { $res[] = Formatting::twigRenderString(<<Favourites -Reverse Favs +Favourites +Reverse Favs END, ['user_nickname' => $user_nickname]); return Event::next; } public function onAddRoute(RouteLoader $r): bool { - $r->connect('favourites', '/favourites/{nickname<' . Nickname::DISPLAY_FMT . '>}', [Controller\Favourite::class, 'favourites']); - $r->connect('reverse_favourites', '/reverse_favourites/{nickname<' . Nickname::DISPLAY_FMT . '>}', [Controller\Favourite::class, 'reverseFavourites']); + $r->connect(id: 'actor_favourites_id', uri_path: '/actor/{id<\d+>}/favourites', target: [Controller\Favourite::class, 'favouritesByActorId']); + $r->connect(id: 'actor_reverse_favourites_id', uri_path: '/actor/{id<\d+>}/reverse_favourites', target: [Controller\Favourite::class, 'reverseFavouritesByActorId']); + $r->connect('actor_favourites_nickname', '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/favourites', [Controller\Favourite::class, 'favouritesByActorNickname']); + $r->connect('actor_reverse_favourites_nickname', '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/reverse_favourites', [Controller\Favourite::class, 'reverseFavouritesByActorNickname']); return Event::next; } } diff --git a/src/Controller/GSActor.php b/src/Controller/GSActor.php index e62235d4f0..d8713af3d8 100644 --- a/src/Controller/GSActor.php +++ b/src/Controller/GSActor.php @@ -56,7 +56,7 @@ class GSActor extends Controller } /** - * The page where the note and it's info is shown + * The page where the actor's info is shown */ public function GSActorShowId(Request $request, int $id) { diff --git a/src/Controller/Subscribers.php b/src/Controller/Subscribers.php new file mode 100644 index 0000000000..46c50ac911 --- /dev/null +++ b/src/Controller/Subscribers.php @@ -0,0 +1,69 @@ +. + +// }}} + +namespace App\Controller; + +use App\Core\Controller; +use App\Core\DB\DB; +use function App\Core\I18n\_m; +use App\Util\Exception\ClientException; +use Symfony\Component\HttpFoundation\Request; + +class Subscribers extends Controller +{ + /** + * Generic function that handles getting a representation for an actor from id + */ + private function GSActorById(int $id, callable $handle) + { + $gsactor = DB::findOneBy('gsactor', ['id' => $id]); + if (empty($gsactor)) { + throw new ClientException(_m('No such actor.'), 404); + } else { + return $handle($gsactor); + } + } + /** + * Generic function that handles getting a representation for an actor from nickname + */ + private function GSActorByNickname(string $nickname, callable $handle) + { + $user = DB::findOneBy('local_user', ['nickname' => $nickname]); + $gsactor = DB::findOneBy('gsactor', ['id' => $user->getId()]); + if (empty($gsactor)) { + throw new ClientException(_m('No such actor.'), 404); + } else { + return $handle($gsactor); + } + } + + /** + * Collection of an actor's subscribers + */ + public function GSActorShowId(Request $request, int $id) + { + return $this->GSActorById($id, fn ($gsactor) => ['_template' => 'subscribers/view.html.twig', 'gsactor' => $gsactor]); + } + public function GSActorShowNickname(Request $request, string $nickname) + { + return $this->GSActorByNickname($nickname, fn ($gsactor) => ['_template' => 'subscribers/view.html.twig', 'gsactor' => $gsactor]); + } +} diff --git a/src/Controller/Subscriptions.php b/src/Controller/Subscriptions.php new file mode 100644 index 0000000000..02c101acfe --- /dev/null +++ b/src/Controller/Subscriptions.php @@ -0,0 +1,69 @@ +. + +// }}} + +namespace App\Controller; + +use App\Core\Controller; +use App\Core\DB\DB; +use function App\Core\I18n\_m; +use App\Util\Exception\ClientException; +use Symfony\Component\HttpFoundation\Request; + +class Subscriptions extends Controller +{ + /** + * Generic function that handles getting a representation for an actor from id + */ + private function GSActorById(int $id, callable $handle) + { + $gsactor = DB::findOneBy('gsactor', ['id' => $id]); + if (empty($gsactor)) { + throw new ClientException(_m('No such actor.'), 404); + } else { + return $handle($gsactor); + } + } + /** + * Generic function that handles getting a representation for an actor from nickname + */ + private function GSActorByNickname(string $nickname, callable $handle) + { + $user = DB::findOneBy('local_user', ['nickname' => $nickname]); + $gsactor = DB::findOneBy('gsactor', ['id' => $user->getId()]); + if (empty($gsactor)) { + throw new ClientException(_m('No such actor.'), 404); + } else { + return $handle($gsactor); + } + } + + /** + * Collection of an actor's subscriptions + */ + public function GSActorShowId(Request $request, int $id) + { + return $this->GSActorById($id, fn ($gsactor) => ['_template' => 'subscriptions/view.html.twig', 'gsactor' => $gsactor]); + } + public function GSActorShowNickname(Request $request, string $nickname) + { + return $this->GSActorByNickname($nickname, fn ($gsactor) => ['_template' => 'subscriptions/view.html.twig', 'gsactor' => $gsactor]); + } +} diff --git a/src/Core/Router/RouteLoader.php b/src/Core/Router/RouteLoader.php index 0eba7a9f10..e762c25998 100644 --- a/src/Core/Router/RouteLoader.php +++ b/src/Core/Router/RouteLoader.php @@ -68,7 +68,6 @@ class RouteLoader extends Loader } ksort($to_load); - foreach ($to_load as $ns) { $ns::load($this); } diff --git a/src/Routes/GSActor.php b/src/Routes/GSActor.php index 70690479d8..58f1b7be41 100644 --- a/src/Routes/GSActor.php +++ b/src/Routes/GSActor.php @@ -20,14 +20,14 @@ // }}} /** - * Define social's attachment routes + * Define social's Actor routes * * @package GNUsocial * @category Router * * @author Diogo Cordeiro * @author Hugo Sales - * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org + * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -40,9 +40,10 @@ use App\Util\Nickname; abstract class GSActor { const LOAD_ORDER = 30; + public static function load(RouteLoader $r): void { - $r->connect(id: 'gsactor_view_id', uri_path: '/actor/{id<\d+>}', target: [C\GSActor::class, 'GSActorShowId']); - $r->connect(id: 'gsactor_view_nickname', uri_path: '/{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\GSActor::class, 'GSActorShowNickname'], options: ['is_system_path' => false]); + $r->connect(id: 'actor_view_id', uri_path: '/actor/{id<\d+>}', target: [C\GSActor::class, 'GSActorShowId']); + $r->connect(id: 'actor_view_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\GSActor::class, 'GSActorShowNickname'], options: ['is_system_path' => false]); } } diff --git a/src/Routes/Main.php b/src/Routes/Main.php index a9a387ba81..3a3bba9e09 100644 --- a/src/Routes/Main.php +++ b/src/Routes/Main.php @@ -26,7 +26,8 @@ * @category Router * * @author Hugo Sales - * @author Eliseu Amaro + * @author Eliseu Amaro + * @author Diogo Cordeiro * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -54,8 +55,8 @@ abstract class Main $r->connect('root', '/', RedirectController::class, ['defaults' => ['route' => 'main_all']]); $r->connect('main_public', '/main/public', [C\Network::class, 'public']); $r->connect('main_all', '/main/all', [C\Network::class, 'network']); - $r->connect('home_all', '/{nickname<' . Nickname::DISPLAY_FMT . '>}/all', [C\Network::class, 'home']); - $r->connect('replies', '/{nickname<' . Nickname::DISPLAY_FMT . '>}/replies', [C\Network::class, 'replies']); + $r->connect('home_all', '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/all', [C\Network::class, 'home']); + $r->connect('replies', '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/replies', [C\Network::class, 'replies']); $r->connect('panel', '/panel', [C\AdminPanel::class, 'site']); $r->connect('panel_site', '/panel/site', [C\AdminPanel::class, 'site']); diff --git a/src/Routes/Note.php b/src/Routes/Note.php index 36623c378b..27f4ae3b09 100644 --- a/src/Routes/Note.php +++ b/src/Routes/Note.php @@ -20,14 +20,14 @@ // }}} /** - * Define social's attachment routes + * Define social's Note routes * * @package GNUsocial * @category Router * * @author Diogo Cordeiro * @author Hugo Sales - * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org + * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ @@ -41,6 +41,6 @@ abstract class Note const LOAD_ORDER = 40; public static function load(RouteLoader $r): void { - $r->connect('note_view', '/note/{id<\d+>}', [C\Note::class, 'NoteShow']); + $r->connect('note_view', '/object/note/{id<\d+>}', [C\Note::class, 'NoteShow']); } } diff --git a/src/Routes/Subscribers.php b/src/Routes/Subscribers.php new file mode 100644 index 0000000000..1f6c2861d1 --- /dev/null +++ b/src/Routes/Subscribers.php @@ -0,0 +1,47 @@ +. + +// }}} + +/** + * Define social's GSActor's subscribers routes + * + * @package GNUsocial + * @category Router + * + * @author Diogo Cordeiro + * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +namespace App\Routes; + +use App\Controller as C; +use App\Core\Router\RouteLoader; +use App\Util\Nickname; + +abstract class Subscribers +{ + const LOAD_ORDER = 31; + public static function load(RouteLoader $r): void + { + $r->connect(id: 'actor_subscribers_id', uri_path: '/actor/{id<\d+>}/subscribers', target: [C\Subscribers::class, 'SubscribersByActorId']); + $r->connect(id: 'actor_subscribers_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/subscribers', target: [C\Subscribers::class, 'SubscribersByActorNickname']); + } +} diff --git a/src/Routes/Subscriptions.php b/src/Routes/Subscriptions.php new file mode 100644 index 0000000000..596c5b678a --- /dev/null +++ b/src/Routes/Subscriptions.php @@ -0,0 +1,47 @@ +. + +// }}} + +/** + * Define social's GSActor's subscriptions routes + * + * @package GNUsocial + * @category Router + * + * @author Diogo Cordeiro + * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +namespace App\Routes; + +use App\Controller as C; +use App\Core\Router\RouteLoader; +use App\Util\Nickname; + +abstract class Subscriptions +{ + const LOAD_ORDER = 32; + public static function load(RouteLoader $r): void + { + $r->connect(id: 'actor_subscriptions_id', uri_path: '/actor/{id<\d+>}/subscriptions', target: [C\Subscriptions::class, 'SubscriptionsByActorId']); + $r->connect(id: 'actor_subscriptions_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/subscriptions', target: [C\Subscriptions::class, 'SubscriptionsByActorNickname']); + } +} diff --git a/templates/sidepanel/left/left.html.twig b/templates/sidepanel/left/left.html.twig index 224366ff3b..0999d02842 100644 --- a/templates/sidepanel/left/left.html.twig +++ b/templates/sidepanel/left/left.html.twig @@ -6,7 +6,7 @@