From 365edbaff076ceb7c343e3f48654cd05c2a25868 Mon Sep 17 00:00:00 2001 From: Diogo Peralta Cordeiro Date: Tue, 14 Sep 2021 17:15:37 +0100 Subject: [PATCH] [ActivityStreamsTwo] Initial Actor support Various bug fixes --- plugins/ActivityPub/ActivityPub.php | 12 +--- .../ActivityStreamsTwo/ActivityStreamsTwo.php | 25 ++++--- .../Util/Model/EntityToType/GSActorToType.php | 49 +++++++++++++ .../Util/Response/ActorResponse.php | 24 +++++++ src/Controller/GSActor.php | 69 +++++++++++++++++++ src/Controller/Note.php | 4 +- src/Core/Controller.php | 2 +- src/Entity/GSActor.php | 17 ++--- src/Routes/GSActor.php | 47 +++++++++++++ src/Routes/Note.php | 2 +- 10 files changed, 217 insertions(+), 34 deletions(-) create mode 100644 plugins/ActivityStreamsTwo/Util/Model/EntityToType/GSActorToType.php create mode 100644 plugins/ActivityStreamsTwo/Util/Response/ActorResponse.php create mode 100644 src/Controller/GSActor.php create mode 100644 src/Routes/GSActor.php diff --git a/plugins/ActivityPub/ActivityPub.php b/plugins/ActivityPub/ActivityPub.php index e9e8c41be2..1e3aed1eb2 100644 --- a/plugins/ActivityPub/ActivityPub.php +++ b/plugins/ActivityPub/ActivityPub.php @@ -7,6 +7,7 @@ use App\Core\Modules\Plugin; use App\Core\Router\RouteLoader; use Exception; use Plugin\ActivityPub\Controller\Inbox; +use Plugin\ActivityStreamsTwo\ActivityStreamsTwo; class ActivityPub extends Plugin { @@ -15,13 +16,6 @@ class ActivityPub extends Plugin return '3.0.0'; } - public static array $accept_headers = [ - 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', - 'application/activity+json', - 'application/json', - 'application/ld+json', - ]; - /** * This code executes when GNU social creates the page routing, and we hook * on this event to add our action handler for Embed. @@ -36,7 +30,7 @@ class ActivityPub extends Plugin 'activitypub_inbox', '{gsactor_id<\d+>}/inbox', [Inbox::class, 'handle'], - options: ['accept' => self::$accept_headers] + options: ['accept' => ActivityStreamsTwo::$accept_headers] ); return Event::next; } @@ -60,7 +54,7 @@ class ActivityPub extends Plugin } elseif (is_array($accept) && count( array_intersect($accept, self::$accept_headers) - ) + ) > 0 ) { return true; } diff --git a/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php b/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php index 803a9ea8d0..cbee726f0a 100644 --- a/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php +++ b/plugins/ActivityStreamsTwo/ActivityStreamsTwo.php @@ -5,6 +5,8 @@ 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; use Plugin\ActivityStreamsTwo\Util\Response\TypeResponse; @@ -15,7 +17,7 @@ class ActivityStreamsTwo extends Plugin return '0.1.0'; } - public array $accept = [ + public static array $accept_headers = [ 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'application/activity+json', 'application/json', @@ -24,23 +26,28 @@ class ActivityStreamsTwo extends Plugin /** * @param string $route - * @param array $accept + * @param array $accept_header * @param array $vars * @param null|TypeResponse $response * - * @throws \Exception + *@throws Exception * * @return bool + * */ - public function onRouteInFormat(string $route, array $accept, array $vars, ?TypeResponse &$response = null): bool + public function onControllerResponseInFormat(string $route, array $accept_header, array $vars, ?TypeResponse &$response = null): bool { - if (empty(array_intersect($this->accept, $accept))) { + if (count(array_intersect(self::$accept_headers, $accept_header)) === 0) { return Event::next; } switch ($route) { - case 'note_show': + case 'note_view': $response = NoteResponse::handle($vars['note']); return Event::stop; + case 'gsactor_view_id': + case 'gsactor_view_nickname': + $response = ActorResponse::handle($vars['gsactor']); + return Event::stop; default: return Event::next; } @@ -56,11 +63,11 @@ class ActivityStreamsTwo extends Plugin */ public function onAddRoute(RouteLoader $r): bool { - $r->connect('note_view_as2', + /*$r->connect('note_view_as2', '/note/{id<\d+>}', [NoteResponse::class, 'handle'], - options: ['accept' => $this->accept] - ); + 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 new file mode 100644 index 0000000000..76587f7dba --- /dev/null +++ b/plugins/ActivityStreamsTwo/Util/Model/EntityToType/GSActorToType.php @@ -0,0 +1,49 @@ + $gsactor->getId()], Router::ABSOLUTE_URL); + $attr = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $uri, + //'inbox' => + //'outbox' => + //'following' => + //'followers' => + //'liked' => + //'streams' => + 'preferredUsername' => $gsactor->getNickname(), + //'publicKey' => [ + // 'id' => $uri . "#public-key", + // 'owner' => $uri, + // 'publicKeyPem' => $public_key + // ], + 'name' => $gsactor->getFullname(), + //'icon' => + //'location' => + 'published' => $gsactor->getCreated()->format(DateTimeInterface::RFC3339), + 'summary' => $gsactor->getBio(), + //'tag' => + 'updated' => $gsactor->getModified()->format(DateTimeInterface::RFC3339), + 'url' => Router::url('gsactor_view_nickname', ['nickname' => $gsactor->getNickname()], Router::ABSOLUTE_URL), + ]; + return Type::create(type: 'Person', attributes: $attr); + } +} diff --git a/plugins/ActivityStreamsTwo/Util/Response/ActorResponse.php b/plugins/ActivityStreamsTwo/Util/Response/ActorResponse.php new file mode 100644 index 0000000000..72ebc1545c --- /dev/null +++ b/plugins/ActivityStreamsTwo/Util/Response/ActorResponse.php @@ -0,0 +1,24 @@ +getLocalUser(); // This throws exception if not a local user, which is intended + return new TypeResponse(data: GSActorToType::translate($gsactor), status: $status); + } +} \ No newline at end of file diff --git a/src/Controller/GSActor.php b/src/Controller/GSActor.php new file mode 100644 index 0000000000..e62235d4f0 --- /dev/null +++ b/src/Controller/GSActor.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 GSActor 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); + } + } + + /** + * The page where the note and it's info is shown + */ + public function GSActorShowId(Request $request, int $id) + { + return $this->GSActorById($id, fn ($gsactor) => ['_template' => 'actor/view.html.twig', 'gsactor' => $gsactor]); + } + public function GSActorShowNickname(Request $request, string $nickname) + { + return $this->GSActorByNickname($nickname, fn ($gsactor) => ['_template' => 'actor/view.html.twig', 'gsactor' => $gsactor]); + } +} diff --git a/src/Controller/Note.php b/src/Controller/Note.php index b075b8afc3..a6be37bf21 100644 --- a/src/Controller/Note.php +++ b/src/Controller/Note.php @@ -36,7 +36,7 @@ class Note extends Controller { $note = DB::findOneBy('note', ['id' => $id]); if (empty($note)) { - throw new ClientException(_m('No such note'), 404); + throw new ClientException(_m('No such note.'), 404); } else { return $handle($note); } @@ -45,7 +45,7 @@ class Note extends Controller /** * The page where the note and it's info is shown */ - public function note_show(Request $request, int $id) + public function NoteShow(Request $request, int $id) { return $this->note($id, fn ($note) => ['_template' => 'note/view.html.twig', 'note' => $note]); } diff --git a/src/Core/Controller.php b/src/Core/Controller.php index 8dd9d643ec..45a294d122 100644 --- a/src/Core/Controller.php +++ b/src/Core/Controller.php @@ -126,7 +126,7 @@ class Controller extends AbstractController implements EventSubscriberInterface 'accept_header' => $accept, 'vars' => $this->vars, 'response' => &$potential_response, - ]) === Event::next) { + ]) !== Event::stop) { switch ($format) { case 'html': $event->setResponse($this->render($template, $this->vars)); diff --git a/src/Entity/GSActor.php b/src/Entity/GSActor.php index a4bea0e04e..397278e6f9 100644 --- a/src/Entity/GSActor.php +++ b/src/Entity/GSActor.php @@ -49,7 +49,6 @@ class GSActor extends Entity // @codeCoverageIgnoreStart private int $id; private string $nickname; - private string $normalized_nickname; private ?string $fullname; private int $roles = 4; private ?string $homepage; @@ -84,17 +83,6 @@ class GSActor extends Entity return $this->nickname; } - public function setNormalizedNickname(string $normalized_nickname): self - { - $this->normalized_nickname = $normalized_nickname; - return $this; - } - - public function getNormalizedNickname(): string - { - return $this->normalized_nickname; - } - public function setFullname(?string $fullname): self { $this->fullname = $fullname; @@ -219,6 +207,11 @@ class GSActor extends Entity // @codeCoverageIgnoreEnd // }}} Autocode + public function getLocalUser() + { + return DB::findOneBy('local_user', ['id' => $this->getId()]); + } + public function getAvatarUrl() { $url = null; diff --git a/src/Routes/GSActor.php b/src/Routes/GSActor.php new file mode 100644 index 0000000000..d956ce4ccb --- /dev/null +++ b/src/Routes/GSActor.php @@ -0,0 +1,47 @@ +. + +// }}} + +/** + * Define social's attachment routes + * + * @package GNUsocial + * @category Router + * + * @author Diogo Cordeiro + * @author Hugo Sales + * @copyright 2020-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 GSActor +{ + 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']); + } +} diff --git a/src/Routes/Note.php b/src/Routes/Note.php index d84ef58c91..ebf86f78cd 100644 --- a/src/Routes/Note.php +++ b/src/Routes/Note.php @@ -40,6 +40,6 @@ abstract class Note { public static function load(RouteLoader $r): void { - $r->connect('note_view', '/note/{id<\d+>}', [C\Note::class, 'note_show']); + $r->connect('note_view', '/note/{id<\d+>}', [C\Note::class, 'NoteShow']); } }