[CORE][ActivityStreamsTwo][ActivityPub] Set all routes

Allow global routes to act for every actor
Fix Favoured stream query
This commit is contained in:
Diogo Peralta Cordeiro 2021-09-16 17:04:05 +01:00
parent 738168461c
commit d6f31d102a
Signed by: diogo
GPG Key ID: 18D2D35001FBFAB0
17 changed files with 308 additions and 58 deletions

View File

@ -38,7 +38,7 @@ class Avatar extends Component
public function onAddRoute($r): bool
{
$r->connect('avatar', '/{gsactor_id<\d+>}/avatar/{size<full|big|medium|small>?full}', [Controller\Avatar::class, 'avatar_view']);
$r->connect('avatar', '/actor/{gsactor_id<\d+>}/avatar/{size<full|big|medium|small>?full}', [Controller\Avatar::class, 'avatar_view']);
$r->connect('settings_avatar', '/settings/avatar', [Controller\Avatar::class, 'settings_avatar']);
return Event::next;
}

View File

@ -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();

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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(<<<END
<a href="{{ path("favourites", {'nickname' : user_nickname}) }}" class='hover-effect {{ active("favourites") }}'>Favourites</a>
<a href="{{ path("reverse_favourites", {'nickname' : user_nickname}) }}" class='hover-effect {{ active("reverse_favourites") }}'>Reverse Favs</a>
<a href="{{ path("actor_favourites_nickname", {'nickname' : user_nickname}) }}" class='hover-effect {{ active("favourites") }}'>Favourites</a>
<a href="{{ path("actor_reverse_favourites_nickname", {'nickname' : user_nickname}) }}" class='hover-effect {{ active("reverse_favourites") }}'>Reverse Favs</a>
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;
}
}

View File

@ -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)
{

View File

@ -0,0 +1,69 @@
<?php
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
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]);
}
}

View File

@ -0,0 +1,69 @@
<?php
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
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]);
}
}

View File

@ -68,7 +68,6 @@ class RouteLoader extends Loader
}
ksort($to_load);
foreach ($to_load as $ns) {
$ns::load($this);
}

View File

@ -20,14 +20,14 @@
// }}}
/**
* Define social's attachment routes
* Define social's Actor routes
*
* @package GNUsocial
* @category Router
*
* @author Diogo Cordeiro <mail@diogo.site>
* @author Hugo Sales <hugo@hsal.es>
* @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]);
}
}

View File

@ -26,7 +26,8 @@
* @category Router
*
* @author Hugo Sales <hugo@hsal.es>
* @author Eliseu Amaro <eliseu@fc.up.pt>
* @author Eliseu Amaro <mail@eliseuama.ro>
* @author Diogo Cordeiro <mail@diogo.site>
* @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']);

View File

@ -20,14 +20,14 @@
// }}}
/**
* Define social's attachment routes
* Define social's Note routes
*
* @package GNUsocial
* @category Router
*
* @author Diogo Cordeiro <mail@diogo.site>
* @author Hugo Sales <hugo@hsal.es>
* @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']);
}
}

View File

@ -0,0 +1,47 @@
<?php
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* Define social's GSActor's subscribers routes
*
* @package GNUsocial
* @category Router
*
* @author Diogo Cordeiro <mail@diogo.site>
* @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']);
}
}

View File

@ -0,0 +1,47 @@
<?php
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* Define social's GSActor's subscriptions routes
*
* @package GNUsocial
* @category Router
*
* @author Diogo Cordeiro <mail@diogo.site>
* @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']);
}
}

View File

@ -6,7 +6,7 @@
<aside class="panel-content accessibility-target">
{% if app.user %}
<section class='section-widget section-widget-padded' title="{{ 'Your profile information.' | trans }}">
<a id="user" href="{{ path('settings') }}">
<a id="user" href="{{ path('actor_view_nickname', {'nickname' : user_nickname}) }}">
<img src='{{ user_avatar }}' class="icon icon-avatar" alt="{{ 'Your account\'s avatar.' | trans }}">
<div class="user-info">
<strong id="user-nickname" title="{{ 'Your account\' nickname.' | trans }}">{{ user_nickname }}</strong>