6 Commits

301 changed files with 1628 additions and 3872 deletions

View File

@@ -3,4 +3,4 @@ KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st' APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999 SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther PANTHER_APP_ENV=panther
DATABASE_URL=postgresql://postgres:password@db:5432/test DATABASE_URL=postgresql://postgres:password@db:5432/social

View File

@@ -22,10 +22,10 @@ declare(strict_types = 1);
namespace Component\Attachment; namespace Component\Attachment;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Note; use App\Entity\Note;
use App\Util\Formatting; use App\Util\Formatting;
@@ -37,7 +37,7 @@ use Doctrine\ORM\QueryBuilder;
class Attachment extends Component class Attachment extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect('note_attachment_show', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}', [C\Attachment::class, 'attachmentShowWithNote']); $r->connect('note_attachment_show', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}', [C\Attachment::class, 'attachmentShowWithNote']);
$r->connect('note_attachment_view', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}/view', [C\Attachment::class, 'attachmentViewWithNote']); $r->connect('note_attachment_view', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}/view', [C\Attachment::class, 'attachmentViewWithNote']);

View File

@@ -24,7 +24,7 @@ declare(strict_types = 1);
namespace Component\Attachment\Controller; namespace Component\Attachment\Controller;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Attachment\Entity; namespace Component\Attachment\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;

View File

@@ -24,13 +24,13 @@ declare(strict_types = 1);
namespace Component\Attachment\Entity; namespace Component\Attachment\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Note; use App\Entity\Note;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;

View File

@@ -24,12 +24,12 @@ declare(strict_types = 1);
namespace Component\Attachment\Entity; namespace Component\Attachment\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use App\Core\Log; use App\Core\Log;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Note; use App\Entity\Note;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Attachment\Entity; namespace Component\Attachment\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Attachment\Entity; namespace Component\Attachment\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;

View File

@@ -23,7 +23,7 @@ declare(strict_types = 1);
namespace Component\Attachment\tests\Controller; namespace Component\Attachment\tests\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;
use Component\Attachment\Entity\Attachment; use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentToNote; use Component\Attachment\Entity\AttachmentToNote;

View File

@@ -21,10 +21,10 @@ declare(strict_types = 1);
namespace Component\Attachment\tests\Entity; namespace Component\Attachment\tests\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Note; use App\Entity\Note;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;
use App\Util\TemporaryFile; use App\Util\TemporaryFile;

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Attachment\tests\Entity; namespace Component\Attachment\tests\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Util\Exception\NotStoredLocallyException; use App\Util\Exception\NotStoredLocallyException;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;

View File

@@ -22,11 +22,12 @@ declare(strict_types = 1);
namespace Component\Avatar; namespace Component\Avatar;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Util\Common; use App\Util\Common;
use Component\Attachment\Entity\Attachment; use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentThumbnail; use Component\Attachment\Entity\AttachmentThumbnail;
@@ -40,7 +41,7 @@ class Avatar extends Component
{ {
} }
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect('avatar_actor', '/actor/{actor_id<\d+>}/avatar/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'avatar_view']); $r->connect('avatar_actor', '/actor/{actor_id<\d+>}/avatar/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'avatar_view']);
$r->connect('avatar_default', '/avatar/default/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'default_avatar_view']); $r->connect('avatar_default', '/avatar/default/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'default_avatar_view']);

View File

@@ -24,7 +24,7 @@ declare(strict_types = 1);
namespace Component\Avatar\Controller; namespace Component\Avatar\Controller;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Form; use App\Core\Form;
use App\Core\GSFile; use App\Core\GSFile;

View File

@@ -24,10 +24,10 @@ declare(strict_types = 1);
namespace Component\Avatar\Entity; namespace Component\Avatar\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Event; use App\Core\Event;
use App\Core\Router; use App\Core\Router\Router;
use App\Util\Common; use App\Util\Common;
use Component\Attachment\Entity\Attachment; use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentThumbnail; use Component\Attachment\Entity\AttachmentThumbnail;

View File

@@ -20,31 +20,18 @@ declare(strict_types = 1);
// }}} // }}}
namespace Plugin\Blog; namespace Component\Blog;
use App\Core\Event; use App\Core\Event;
use App\Core\Modules\Plugin; use App\Core\Modules\Plugin;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Util\Common; use Component\Blog\Controller as C;
use App\Util\HTML;
use Plugin\Blog\Controller as C;
use function App\Core\I18n\_m;
class Blog extends Plugin class Blog extends Plugin
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect(id: 'blog_post', uri_path: '/blog/post', target: [C\Post::class, 'makePost']); $r->connect(id: 'blog_post', uri_path: '/blog/post', target: [C\Post::class, 'makePost']);
return Event::next; return Event::next;
} }
public function onAppendCardProfile(array $vars, array &$res): bool
{
$actor = Common::actor();
$group = $vars['actor'];
if (!\is_null($actor) && $group->isGroup()) {
$res[] = HTML::html(['a' => ['attrs' => ['href' => Router::url('blog_post', ['in' => $group->getId()]), 'title' => _m('Make a new blog post'), 'class' => 'profile-extra-actions'], _m('Post in blog')]]);
}
return Event::next;
}
} }

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
// }}} // }}}
namespace Plugin\Blog\Controller; namespace Component\Blog\Controller;
use App\Core\ActorLocalRoles; use App\Core\ActorLocalRoles;
use App\Core\Controller; use App\Core\Controller;
@@ -139,13 +139,14 @@ class Post extends Controller
$extra_args = []; $extra_args = [];
Event::handle('AddExtraArgsToNoteContent', [$request, $actor, $data, &$extra_args, $form_params, $form]); Event::handle('AddExtraArgsToNoteContent', [$request, $actor, $data, &$extra_args, $form_params, $form]);
[,$note,] = Posting::storeLocalArticle( [,$note,] = Posting::storeLocalPage(
actor: $actor, actor: $actor,
content: $data['content'], content: $data['content'],
content_type: $content_type, content_type: $content_type,
locale: $data['language'], locale: $data['language'],
scope: VisibilityScope::from($data['visibility']), scope: VisibilityScope::from($data['visibility']),
attentions: [(int) $data['in']], targets: [(int) $data['in']],
reply_to: $data['reply_to_id'],
attachments: $data['attachments'], attachments: $data['attachments'],
process_note_content_extra_args: $extra_args, process_note_content_extra_args: $extra_args,
title: $data['title'], title: $data['title'],

View File

@@ -24,14 +24,16 @@ declare(strict_types = 1);
namespace Component\Circle; namespace Component\Circle;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Feed; use App\Entity\Feed;
use App\Entity\LocalUser; use App\Entity\LocalUser;
use App\Util\Common;
use App\Util\Nickname; use App\Util\Nickname;
use Component\Circle\Controller as CircleController; use Component\Circle\Controller as CircleController;
use Component\Circle\Entity\ActorCircle; use Component\Circle\Entity\ActorCircle;
@@ -58,7 +60,7 @@ class Circle extends Component
protected const SLUG = 'circle'; protected const SLUG = 'circle';
protected const PLURAL_SLUG = 'circles'; protected const PLURAL_SLUG = 'circles';
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect('actor_circle_view_by_circle_id', '/circle/{circle_id<\d+>}', [CircleController\Circle::class, 'circleById']); $r->connect('actor_circle_view_by_circle_id', '/circle/{circle_id<\d+>}', [CircleController\Circle::class, 'circleById']);
// View circle members by (tagger id or nickname) and tag // View circle members by (tagger id or nickname) and tag
@@ -97,10 +99,10 @@ class Circle extends Component
{ {
if ($section === 'profile' && \in_array($request->get('_route'), ['person_actor_settings', 'group_actor_settings'])) { if ($section === 'profile' && \in_array($request->get('_route'), ['person_actor_settings', 'group_actor_settings'])) {
$tabs[] = [ $tabs[] = [
'title' => _m('Self tags'), 'title' => 'Self tags',
'desc' => _m('Add or remove tags to this actor'), 'desc' => 'Add or remove tags on yourself',
'id' => 'settings-self-tags', 'id' => 'settings-self-tags',
'controller' => CircleController\SelfTagsSettings::settingsSelfTags($request, Actor::getById((int) $request->get('id')), 'settings-self-tags-details'), 'controller' => CircleController\SelfTagsSettings::settingsSelfTags($request, Common::actor(), 'settings-self-tags-details'),
]; ];
} }
return Event::next; return Event::next;

View File

@@ -24,8 +24,8 @@ declare(strict_types = 1);
namespace Component\Circle\Controller; namespace Component\Circle\Controller;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\LocalUser; use App\Entity\LocalUser;
use Component\Circle\Entity\ActorCircle; use Component\Circle\Entity\ActorCircle;

View File

@@ -6,7 +6,7 @@ namespace Component\Circle\Controller;
use App\Core\Cache; use App\Core\Cache;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Entity as E; use App\Entity as E;
use App\Util\Common; use App\Util\Common;
@@ -45,7 +45,7 @@ class SelfTagsSettings extends Controller
foreach ($tags as $tag) { foreach ($tags as $tag) {
$tag = CompTag::sanitize($tag); $tag = CompTag::sanitize($tag);
[$actor_tag, $actor_tag_existed] = ActorTag::checkExistingAndCreateOrUpdate([ [$actor_tag, $actor_tag_existed] = ActorTag::createOrUpdate([
'tagger' => $target->getId(), // self tag means tagger = tagger in ActorTag 'tagger' => $target->getId(), // self tag means tagger = tagger in ActorTag
'tagged' => $target->getId(), 'tagged' => $target->getId(),
'tag' => $tag, 'tag' => $tag,

View File

@@ -22,9 +22,9 @@ declare(strict_types = 1);
namespace Component\Circle\Entity; namespace Component\Circle\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Router; use App\Core\Router\Router;
use DateTimeInterface; use DateTimeInterface;
/** /**

View File

@@ -21,9 +21,9 @@ declare(strict_types = 1);
namespace Component\Circle\Entity; namespace Component\Circle\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use Component\Tag\Tag; use Component\Tag\Tag;
use DateTimeInterface; use DateTimeInterface;

View File

@@ -34,7 +34,7 @@ abstract class SelfTagsForm
$existing_form = !empty($form_definition) ? Form::create($form_definition) : null; $existing_form = !empty($form_definition) ? Form::create($form_definition) : null;
$add_form = Form::create([ $add_form = Form::create([
['new-tags', TextType::class, ['label' => ' ', 'data' => [], 'required' => false, 'help' => _m('Tags for this actor (letters, numbers, -, ., and _), comma- or space-separated.'), 'transformer' => ArrayTransformer::class]], ['new-tags', TextType::class, ['label' => ' ', 'data' => [], 'required' => false, 'help' => _m('Tags for yourself (letters, numbers, -, ., and _), comma- or space-separated.'), 'transformer' => ArrayTransformer::class]],
[$add_form_name = 'new-tags-add', SubmitType::class, ['label' => $add_label]], [$add_form_name = 'new-tags-add', SubmitType::class, ['label' => $add_label]],
]); ]);

View File

@@ -4,7 +4,7 @@ declare(strict_types = 1);
namespace Component\Collection; namespace Component\Collection;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Entity\Actor; use App\Entity\Actor;

View File

@@ -31,7 +31,7 @@ declare(strict_types = 1);
namespace Component\Collection\Util\Controller; namespace Component\Collection\Util\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Entity\LocalUser; use App\Entity\LocalUser;

View File

@@ -31,7 +31,7 @@ declare(strict_types = 1);
namespace Component\Collection\Util; namespace Component\Collection\Util;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;

View File

@@ -28,11 +28,11 @@ declare(strict_types = 1);
namespace Component\Conversation\Controller; namespace Component\Conversation\Controller;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Note; use App\Entity\Note;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;

View File

@@ -28,11 +28,12 @@ declare(strict_types = 1);
namespace Component\Conversation; namespace Component\Conversation;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Note; use App\Entity\Note;
@@ -45,7 +46,7 @@ use Symfony\Component\HttpFoundation\Request;
class Conversation extends Component class Conversation extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect('conversation', '/conversation/{conversation_id<\d+>}', [Controller\Conversation::class, 'showConversation']); $r->connect('conversation', '/conversation/{conversation_id<\d+>}', [Controller\Conversation::class, 'showConversation']);
$r->connect('conversation_mute', '/conversation/{conversation_id<\d+>}/mute', [Controller\Conversation::class, 'muteConversation']); $r->connect('conversation_mute', '/conversation/{conversation_id<\d+>}/mute', [Controller\Conversation::class, 'muteConversation']);

View File

@@ -23,9 +23,9 @@ declare(strict_types = 1);
namespace Component\Conversation\Entity; namespace Component\Conversation\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Router; use App\Core\Router\Router;
/** /**
* Entity class for Conversations * Entity class for Conversations

View File

@@ -24,7 +24,7 @@ declare(strict_types = 1);
namespace Component\Conversation\Entity; namespace Component\Conversation\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;

View File

@@ -25,12 +25,12 @@ namespace Component\Feed;
use App\Core\Event; use App\Core\Event;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use Component\Feed\Controller as C; use Component\Feed\Controller as C;
class Feed extends Component class Feed extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect('feed_public', '/feed/public', [C\Feeds::class, 'public']); $r->connect('feed_public', '/feed/public', [C\Feeds::class, 'public']);
$r->connect('feed_home', '/feed/home', [C\Feeds::class, 'home']); $r->connect('feed_home', '/feed/home', [C\Feeds::class, 'home']);

View File

@@ -23,7 +23,7 @@ declare(strict_types = 1);
namespace Component\Feed\tests\Controller; namespace Component\Feed\tests\Controller;
use App\Core\Router; use App\Core\Router\Router;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;
use Component\Feed\Controller\Feeds; use Component\Feed\Controller\Feeds;
use Jchook\AssertThrows\AssertThrows; use Jchook\AssertThrows\AssertThrows;

View File

@@ -34,7 +34,7 @@ declare(strict_types = 1);
namespace Component\FreeNetwork\Controller; namespace Component\FreeNetwork\Controller;
use App\Core\DB; use App\Core\DB\DB;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Util\Common; use App\Util\Common;
use Component\Collection\Util\Controller\FeedController; use Component\Collection\Util\Controller\FeedController;

View File

@@ -32,7 +32,7 @@ declare(strict_types = 1);
namespace Component\FreeNetwork\Entity; namespace Component\FreeNetwork\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Entity\Actor; use App\Entity\Actor;
use Component\FreeNetwork\Util\Discovery; use Component\FreeNetwork\Util\Discovery;

View File

@@ -21,14 +21,15 @@ declare(strict_types = 1);
namespace Component\FreeNetwork; namespace Component\FreeNetwork;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use App\Core\HTTPClient; use App\Core\HTTPClient;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\LocalUser; use App\Entity\LocalUser;
@@ -85,7 +86,7 @@ class FreeNetwork extends Component
return Event::next; return Event::next;
} }
public function onAddRoute(Router $m): bool public function onAddRoute(RouteLoader $m): bool
{ {
// Feeds // Feeds
$m->connect('feed_network', '/feed/network', [Feeds::class, 'network']); $m->connect('feed_network', '/feed/network', [Feeds::class, 'network']);

View File

@@ -6,7 +6,7 @@ namespace Component\FreeNetwork\Util\WebfingerResource;
use App\Core\Event; use App\Core\Event;
use App\Core\Log; use App\Core\Log;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Util\Common; use App\Util\Common;
use Component\FreeNetwork\Exception\WebfingerReconstructionException; use Component\FreeNetwork\Exception\WebfingerReconstructionException;

View File

@@ -26,7 +26,7 @@ namespace Component\Group\Controller;
use App\Core\ActorLocalRoles; use App\Core\ActorLocalRoles;
use App\Core\Cache; use App\Core\Cache;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;

View File

@@ -24,10 +24,10 @@ declare(strict_types = 1);
namespace Component\Group\Controller; namespace Component\Group\Controller;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity as E; use App\Entity as E;
use App\Util\Common; use App\Util\Common;
@@ -80,7 +80,6 @@ class GroupFeed extends FeedController
WHERE act.object_type = 'note' AND act.id IN WHERE act.object_type = 'note' AND act.id IN
(SELECT att.activity_id FROM \Component\Notification\Entity\Notification AS att WHERE att.target_id = :id) (SELECT att.activity_id FROM \Component\Notification\Entity\Notification AS att WHERE att.target_id = :id)
) )
ORDER BY n.created DESC
EOF, ['id' => $group->getId()]); EOF, ['id' => $group->getId()]);
return [ return [

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Group\Entity; namespace Component\Group\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Util\Exception\NicknameEmptyException; use App\Util\Exception\NicknameEmptyException;

View File

@@ -24,7 +24,8 @@ namespace Component\Group;
use App\Core\Event; use App\Core\Event;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Util\Common; use App\Util\Common;
@@ -37,7 +38,7 @@ use Symfony\Component\HttpFoundation\Request;
class Group extends Component class Group extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect(id: 'group_actor_view_id', uri_path: '/group/{id<\d+>}', target: [C\GroupFeed::class, 'groupViewId']); $r->connect(id: 'group_actor_view_id', uri_path: '/group/{id<\d+>}', target: [C\GroupFeed::class, 'groupViewId']);
$r->connect(id: 'group_actor_view_nickname', uri_path: '/!{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\GroupFeed::class, 'groupViewNickname']); $r->connect(id: 'group_actor_view_nickname', uri_path: '/!{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\GroupFeed::class, 'groupViewNickname']);
@@ -50,17 +51,12 @@ class Group extends Component
* Enqueues a notification for an Actor (such as person or group) which means * Enqueues a notification for an Actor (such as person or group) which means
* it shows up in their home feed and such. * it shows up in their home feed and such.
*/ */
public function onNewNotificationStart(Actor $sender, Activity $activity, array $targets = [], ?string $reason = null): bool public function onNewNotificationWithTargets(Actor $sender, Activity $activity, array $targets = [], ?string $reason = null): bool
{ {
foreach ($targets as $target) { foreach ($targets as $target) {
if ($target->isGroup()) { if ($target->isGroup()) {
// The Group announces to its subscribers // The Group announces to its subscribers
Notification::notify( Notification::notify($target, $activity, $target->getSubscribers(), $reason);
sender: $target,
activity: $activity,
targets: $target->getSubscribers(),
reason: $reason,
);
} }
} }
@@ -79,6 +75,7 @@ class Group extends Component
$url = Router::url('group_actor_settings', ['id' => $group->getId()]); $url = Router::url('group_actor_settings', ['id' => $group->getId()]);
$res[] = HTML::html(['a' => ['attrs' => ['href' => $url, 'title' => _m('Edit group settings'), 'class' => 'profile-extra-actions'], _m('Group settings')]]); $res[] = HTML::html(['a' => ['attrs' => ['href' => $url, 'title' => _m('Edit group settings'), 'class' => 'profile-extra-actions'], _m('Group settings')]]);
} }
$res[] = HTML::html(['a' => ['attrs' => ['href' => Router::url('blog_post', ['in' => $group->getId()]), 'title' => _m('Make a new blog post'), 'class' => 'profile-extra-actions'], _m('Post in blog')]]);
} }
return Event::next; return Event::next;
} }
@@ -102,7 +99,7 @@ class Group extends Component
case 'group_actor_view_nickname': case 'group_actor_view_nickname':
return LocalGroup::getActorByNickname($identifier); return LocalGroup::getActorByNickname($identifier);
case 'group_actor_view_id': case 'group_actor_view_id':
return Actor::getById((int) $identifier); return Actor::getById($identifier);
} }
} }
return null; return null;

View File

@@ -7,7 +7,7 @@
<h1>Settings</h1> <h1>Settings</h1>
<ul> <ul>
<li> <li>
{% set profile_tabs = [{'title': 'Personal Info', 'desc': 'Nickname, Homepage, Bio and more.', 'id': 'settings-personal-info', 'form': personal_info_form}] %} {% set profile_tabs = [{'title': 'Personal Info', 'desc': 'Nickname, Homepage, Bio, Self Tags and more.', 'id': 'settings-personal-info', 'form': personal_info_form}] %}
{% set profile_tabs = profile_tabs|merge(handle_event('PopulateSettingsTabs', app.request, 'profile')) %} {% set profile_tabs = profile_tabs|merge(handle_event('PopulateSettingsTabs', app.request, 'profile')) %}
{{ macros.settings_details_container('Profile', 'Personal Information, Avatar and Profile', 'settings-profile-details', profile_tabs, _context) }} {{ macros.settings_details_container('Profile', 'Personal Information, Avatar and Profile', 'settings-profile-details', profile_tabs, _context) }}
</li> </li>

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Group\tests\Entity; namespace Component\Group\tests\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Entity\Actor; use App\Entity\Actor;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;
use Component\Group\Entity\LocalGroup; use Component\Group\Entity\LocalGroup;

View File

@@ -25,7 +25,7 @@ namespace Component\Language\Controller;
use App\Core\Cache; use App\Core\Cache;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Util\Common; use App\Util\Common;

View File

@@ -24,7 +24,7 @@ declare(strict_types = 1);
namespace Component\Language\Entity; namespace Component\Language\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\LocalUser; use App\Entity\LocalUser;

View File

@@ -24,7 +24,7 @@ declare(strict_types = 1);
namespace Component\Language\Entity; namespace Component\Language\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Entity\Actor; use App\Entity\Actor;
@@ -116,7 +116,7 @@ class Language extends Entity
return Cache::getHashMapKey( return Cache::getHashMapKey(
map_key: 'languages-id', map_key: 'languages-id',
key: (string) $id, key: (string) $id,
calculate_map: fn () => F\reindex(DB::dql('SELECT l FROM \Component\Language\Entity\Language AS l'), fn (self $l) => (string) $l->getId()), calculate_map: fn () => F\reindex(DB::dql('select l from language l'), fn (self $l) => (string) $l->getId()),
); );
} }
@@ -125,7 +125,7 @@ class Language extends Entity
return Cache::getHashMapKey( return Cache::getHashMapKey(
'languages', 'languages',
$locale, $locale,
calculate_map: fn () => F\reindex(DB::dql('SELECT l FROM \Component\Language\Entity\Language AS l'), fn (self $l) => $l->getLocale()), calculate_map: fn () => F\reindex(DB::dql('select l from language l'), fn (self $l) => $l->getLocale()),
); );
} }
@@ -138,7 +138,7 @@ class Language extends Entity
{ {
$langs = Cache::getHashMap( $langs = Cache::getHashMap(
'languages', 'languages',
fn () => F\reindex(DB::dql('SELECT l FROM \Component\Language\Entity\Language AS l'), fn (self $l) => $l->getLocale()), fn () => F\reindex(DB::dql('select l from language l'), fn (self $l) => $l->getLocale()),
); );
return array_merge(...F\map(array_values($langs), fn ($l) => $l->toChoiceFormat())); return array_merge(...F\map(array_values($langs), fn ($l) => $l->toChoiceFormat()));

View File

@@ -23,7 +23,7 @@ namespace Component\Language;
use App\Core\Event; use App\Core\Event;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Note; use App\Entity\Note;
use App\Util\Formatting; use App\Util\Formatting;
@@ -38,7 +38,7 @@ use Symfony\Component\HttpFoundation\Request;
class Language extends Component class Language extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect('settings_sort_languages', '/settings/sort_languages', [C\Language::class, 'sortLanguages']); $r->connect('settings_sort_languages', '/settings/sort_languages', [C\Language::class, 'sortLanguages']);
return Event::next; return Event::next;

View File

@@ -25,10 +25,10 @@ namespace Component\LeftPanel\Controller;
use App\Core\Cache; use App\Core\Cache;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Feed; use App\Entity\Feed;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;

View File

@@ -22,11 +22,12 @@ declare(strict_types = 1);
namespace Component\LeftPanel; namespace Component\LeftPanel;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Feed; use App\Entity\Feed;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;
@@ -35,7 +36,7 @@ use Component\LeftPanel\Controller as C;
class LeftPanel extends Component class LeftPanel extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect('edit_feeds', '/edit-feeds', C\EditFeeds::class); $r->connect('edit_feeds', '/edit-feeds', C\EditFeeds::class);
return Event::next; return Event::next;

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Link\Entity; namespace Component\Link\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;

View File

@@ -21,7 +21,7 @@ declare(strict_types = 1);
namespace Component\Link\Entity; namespace Component\Link\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Event; use App\Core\Event;
use DateTimeInterface; use DateTimeInterface;
@@ -84,14 +84,16 @@ class NoteToLink extends Entity
* Create an instance of NoteToLink or fill in the * Create an instance of NoteToLink or fill in the
* properties of $obj with the associative array $args. Doesn't * properties of $obj with the associative array $args. Doesn't
* persist the result * persist the result
*
* @param null|mixed $obj
*/ */
public static function create(array $args, bool $_delegated_call = false): static public static function create(array $args, $obj = null)
{ {
$link = DB::find('link', ['id' => $args['link_id']]); $link = DB::find('link', ['id' => $args['link_id']]);
$note = DB::find('note', ['id' => $args['note_id']]); $note = DB::find('note', ['id' => $args['note_id']]);
Event::handle('NewLinkFromNote', [$link, $note]); Event::handle('NewLinkFromNote', [$link, $note]);
$obj = new self(); $obj = new self();
return parent::createOrUpdate(obj: $obj, args: $args); return parent::create($args, $obj);
} }
public static function removeWhereNoteId(int $note_id): mixed public static function removeWhereNoteId(int $note_id): mixed

View File

@@ -23,7 +23,7 @@ declare(strict_types = 1);
namespace Component\Link; namespace Component\Link;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Entity\Actor; use App\Entity\Actor;
@@ -35,22 +35,6 @@ use InvalidArgumentException;
class Link extends Component class Link extends Component
{ {
/**
* Note that this persists both a Link and a NoteToLink
*
* @return [Entity\Link, NoteToLink]
*/
public static function maybeCreateLink(string $url, int $note_id): array
{
try {
$link = Entity\Link::getOrCreate($url);
DB::persist($note_link = NoteToLink::create(['link_id' => $link->getId(), 'note_id' => $note_id]));
return ['link' => $link, 'note_to_link' => $note_link];
} catch (InvalidArgumentException) {
return ['link' => null, 'note_to_link' => null];
}
}
/** /**
* Extract URLs from $content and create the appropriate Link and NoteToLink entities * Extract URLs from $content and create the appropriate Link and NoteToLink entities
*/ */
@@ -65,7 +49,12 @@ class Link extends Component
if (\in_array($match, $ignore)) { if (\in_array($match, $ignore)) {
continue; continue;
} }
self::maybeCreateLink($match, $note_id); try {
$link_id = Entity\Link::getOrCreate($match)->getId();
DB::persist(NoteToLink::create(['link_id' => $link_id, 'note_id' => $note->getId()]));
} catch (InvalidArgumentException) {
continue;
}
} }
} }
return Event::next; return Event::next;

View File

@@ -35,7 +35,7 @@ declare(strict_types = 1);
namespace Component\Notification\Controller; namespace Component\Notification\Controller;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Util\Common; use App\Util\Common;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -53,9 +53,9 @@ class Feed extends Controller
WHERE n.id IN ( WHERE n.id IN (
SELECT act.object_id FROM \App\Entity\Activity AS act SELECT act.object_id FROM \App\Entity\Activity AS act
WHERE act.object_type = 'note' AND act.id IN WHERE act.object_type = 'note' AND act.id IN
(SELECT att.activity_id FROM \Component\Notification\Entity\Notification AS att WHERE att.target_id = :target_id) (SELECT att.activity_id FROM \Component\Notification\Entity\Notification AS att WHERE att.target_id = :id)
) )
EOF, [':target_id' => $user->getId()]); EOF, ['id' => $user->getId()]);
return [ return [
'_template' => 'collection/notes.html.twig', '_template' => 'collection/notes.html.twig',
'page_title' => _m('Notifications'), 'page_title' => _m('Notifications'),

View File

@@ -24,51 +24,31 @@ namespace Component\Notification\Entity;
use App\Core\Entity; use App\Core\Entity;
/** /**
* Entity for object attentions * Entity for note attentions
*
* An attention is a form of persistent notification.
* It exists together and for as long as the object it belongs to.
* Creating an attention requires creating a Notification.
* *
* @category DB * @category DB
* @package GNUsocial * @package GNUsocial
* *
* @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet Inc.
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
* @author Diogo Peralta Cordeiro <@diogo.site> * @author Diogo Peralta Cordeiro <@diogo.site>
* @copyright 2021-2022 Free Software Foundation, Inc http://www.fsf.org * @copyright 2022 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class Attention extends Entity class Attention extends Entity
{ {
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private string $object_type; private int $note_id;
private int $object_id;
private int $target_id; private int $target_id;
public function setObjectType(string $object_type): self public function setNoteId(int $note_id): self
{ {
$this->object_type = mb_substr($object_type, 0, 32); $this->note_id = $note_id;
return $this; return $this;
} }
public function getObjectType(): string public function getNoteId(): int
{ {
return $this->object_type; return $this->note_id;
}
public function setObjectId(int $object_id): self
{
$this->object_id = $object_id;
return $this;
}
public function getObjectId(): int
{
return $this->object_id;
} }
public function setTargetId(int $target_id): self public function setTargetId(int $target_id): self
@@ -88,16 +68,15 @@ class Attention extends Entity
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [
'name' => 'attention', 'name' => 'note_attention',
'description' => 'Attentions to actors (these are not mentions)', 'description' => 'Note attentions to actors (that are not a mention)',
'fields' => [ 'fields' => [
'object_type' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'the name of the table this object refers to'], 'note_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'note_id to give attention'],
'object_id' => ['type' => 'int', 'not null' => true, 'description' => 'id in the referenced table'],
'target_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'actor_id for feed receiver'], 'target_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'actor_id for feed receiver'],
], ],
'primary key' => ['object_type', 'object_id', 'target_id'], 'primary key' => ['note_id', 'target_id'],
'indexes' => [ 'indexes' => [
'attention_object_id_idx' => ['object_id'], 'attention_note_id_idx' => ['note_id'],
'attention_target_id_idx' => ['target_id'], 'attention_target_id_idx' => ['target_id'],
], ],
]; ];

View File

@@ -21,24 +21,24 @@ declare(strict_types = 1);
namespace Component\Notification\Entity; namespace Component\Notification\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use DateTimeInterface; use DateTimeInterface;
/** /**
* Entity for Notifications * Entity for attentions
*
* A Notification when isolated is a form of transient notification.
* When together with a persistent form of notification such as attentions or mentions,
* it records that the target was notified - which avoids re-notifying upon objects reconstructions.
* *
* @category DB * @category DB
* @package GNUsocial * @package GNUsocial
* *
* @author Diogo Peralta Cordeiro <@diogo.site> * @author Zach Copley <zach@status.net>
* @copyright 2021-2022 Free Software Foundation, Inc http://www.fsf.org * @copyright 2010 StatusNet Inc.
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
* @author Hugo Sales <hugo@hsal.es>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class Notification extends Entity class Notification extends Entity

View File

@@ -21,13 +21,14 @@ declare(strict_types = 1);
namespace Component\Notification; namespace Component\Notification;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Queue; use App\Core\Queue\Queue;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\LocalUser; use App\Entity\LocalUser;
@@ -39,7 +40,7 @@ use Throwable;
class Notification extends Component class Notification extends Component
{ {
public function onAddRoute(Router $m): bool public function onAddRoute(RouteLoader $m): bool
{ {
$m->connect('feed_notifications', '/feed/notifications', [Feed::class, 'notifications']); $m->connect('feed_notifications', '/feed/notifications', [Feed::class, 'notifications']);
return Event::next; return Event::next;
@@ -63,42 +64,14 @@ class Notification extends Component
/** /**
* Enqueues a notification for an Actor (such as person or group) which means * Enqueues a notification for an Actor (such as person or group) which means
* it shows up in their home feed and such. * it shows up in their home feed and such.
* WARNING: It's highly advisable to have flushed any relevant objects before triggering this event.
*
* $targets should be of the shape:
* (int|Actor)[] // Prefer Actor whenever possible
* Example of $targets:
* [42, $actor_alice, $actor_bob] // Avoid repeating actors or ids
*
* @param Actor $sender The one responsible for this activity, take care not to include it in targets
* @param Activity $activity The activity responsible for the object being given to known to targets
* @param array $targets Attentions, Mentions, any other source. Should never be empty, you usually want to register an attention to every $sender->getSubscribers()
* @param null|string $reason An optional reason explaining why this notification exists
*/ */
public function onNewNotification(Actor $sender, Activity $activity, array $targets, ?string $reason = null): bool public function onNewNotification(Actor $sender, Activity $activity, array $ids_already_known = [], ?string $reason = null): bool
{ {
// Ensure targets are all actor objects and unique $targets = $activity->getNotificationTargets(ids_already_known: $ids_already_known, sender_id: $sender->getId());
$effective_targets = []; if (Event::handle('NewNotificationWithTargets', [$sender, $activity, $targets, $reason]) === Event::next) {
foreach ($targets as $target) { self::notify($sender, $activity, $targets, $reason);
if (\is_int($target)) {
$target_id = $target;
$target_object = null;
} else {
$target_id = $target->getId();
$target_object = $target;
}
if (!\array_key_exists(key: $target_id, array: $effective_targets)) {
$target_object ??= Actor::getById($target_id);
$effective_targets[$target_id] = $target_object;
}
}
unset($targets);
if (Event::handle('NewNotificationStart', [$sender, $activity, $effective_targets, $reason]) === Event::next) {
self::notify($sender, $activity, $effective_targets, $reason);
} }
Event::handle('NewNotificationEnd', [$sender, $activity, $effective_targets, $reason]);
return Event::next; return Event::next;
} }
@@ -118,8 +91,7 @@ class Notification extends Component
} }
/** /**
* Bring given Activity to Targets' knowledge. * Bring given Activity to Targets's attention
* This will flush a Notification to DB.
* *
* @return true if successful, false otherwise * @return true if successful, false otherwise
*/ */
@@ -128,8 +100,8 @@ class Notification extends Component
$remote_targets = []; $remote_targets = [];
foreach ($targets as $target) { foreach ($targets as $target) {
if ($target->getIsLocal()) { if ($target->getIsLocal()) {
if ($target->hasBlocked($author = $activity->getActor())) { if ($target->hasBlocked($activity->getActor())) {
Log::info("Not saving notification to actor {$target->getId()} from sender {$sender->getId()} because receiver blocked author {$author->getId()}."); Log::info("Not saving reply to actor {$target->getId()} from sender {$sender->getId()} because of a block.");
continue; continue;
} }
if (Event::handle('NewNotificationShould', [$activity, $target]) === Event::next) { if (Event::handle('NewNotificationShould', [$activity, $target]) === Event::next) {
@@ -141,7 +113,7 @@ class Notification extends Component
} }
Queue::enqueue( Queue::enqueue(
payload: [$sender, $activity, $target, $reason], payload: [$sender, $activity, $target, $reason],
queue: 'NotificationLocal', queue: 'notification_local',
priority: true, priority: true,
); );
} else { } else {
@@ -152,7 +124,7 @@ class Notification extends Component
} }
// XXX: Unideal as in failures the rollback will leave behind a false notification, // XXX: Unideal as in failures the rollback will leave behind a false notification,
// but most notifications (all) require flushing the objects first // but most notifications (all) require flushing the objects first
// Should be okay as long as implementations bear this in mind // Should be okay as long as implementors bear this in mind
try { try {
DB::wrapInTransaction(fn () => DB::persist(Entity\Notification::create([ DB::wrapInTransaction(fn () => DB::persist(Entity\Notification::create([
'activity_id' => $activity->getId(), 'activity_id' => $activity->getId(),
@@ -160,7 +132,7 @@ class Notification extends Component
'reason' => $reason, 'reason' => $reason,
]))); ])));
} catch (Exception|Throwable $e) { } catch (Exception|Throwable $e) {
// We do our best not to record duplicate notifications, but it's not insane that can happen // We do our best not to record duplicated notifications, but it's not insane that can happen
Log::error('It was attempted to record an invalid notification!', [$e]); Log::error('It was attempted to record an invalid notification!', [$e]);
} }
} }
@@ -168,7 +140,7 @@ class Notification extends Component
if ($remote_targets !== []) { if ($remote_targets !== []) {
Queue::enqueue( Queue::enqueue(
payload: [$sender, $activity, $remote_targets, $reason], payload: [$sender, $activity, $remote_targets, $reason],
queue: 'NotificationRemote', queue: 'notification_remote',
priority: false, priority: false,
); );
} }

View File

@@ -24,7 +24,7 @@ declare(strict_types = 1);
namespace Component\Person\Controller; namespace Component\Person\Controller;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity as E; use App\Entity as E;
use App\Entity\LocalUser; use App\Entity\LocalUser;

View File

@@ -38,12 +38,11 @@ namespace Component\Person\Controller;
// {{{ Imports // {{{ Imports
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Entity\Actor;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\AuthenticationException; use App\Util\Exception\AuthenticationException;
use App\Util\Exception\NicknameEmptyException; use App\Util\Exception\NicknameEmptyException;
@@ -93,8 +92,7 @@ class PersonSettings extends Controller
{ {
// Ensure the user is logged in and retrieve Actor object for given user // Ensure the user is logged in and retrieve Actor object for given user
$user = Common::ensureLoggedIn(); $user = Common::ensureLoggedIn();
// Must be persisted $actor = $user->getActor();
$actor = DB::findOneBy(Actor::class, ['id' => $user->getId()]);
$personal_form = ActorForms::personalInfo(request: $request, scope: $actor, target: $actor); $personal_form = ActorForms::personalInfo(request: $request, scope: $actor, target: $actor);
$email_form = self::email($request); $email_form = self::email($request);
@@ -103,7 +101,7 @@ class PersonSettings extends Controller
$language_form = $language->settings($request); $language_form = $language->settings($request);
return [ return [
'_template' => 'person/settings.html.twig', '_template' => 'settings/base.html.twig',
'personal_info_form' => $personal_form->createView(), 'personal_info_form' => $personal_form->createView(),
'email_form' => $email_form->createView(), 'email_form' => $email_form->createView(),
'password_form' => $password_form->createView(), 'password_form' => $password_form->createView(),
@@ -286,7 +284,7 @@ class PersonSettings extends Controller
$data = $form->getData(); $data = $form->getData();
unset($data['translation_domain']); unset($data['translation_domain']);
try { try {
[$entity, $is_update] = UserNotificationPrefs::checkExistingAndCreateOrUpdate( [$entity, $is_update] = UserNotificationPrefs::createOrUpdate(
array_merge(['user_id' => $user->getId(), 'transport' => $transport_name], $data), array_merge(['user_id' => $user->getId(), 'transport' => $transport_name], $data),
find_by_keys: ['user_id', 'transport'], find_by_keys: ['user_id', 'transport'],
); );

View File

@@ -23,13 +23,13 @@ namespace Component\Person;
use App\Core\Event; use App\Core\Event;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Util\Nickname; use App\Util\Nickname;
use Component\Person\Controller as C; use Component\Person\Controller as C;
class Person extends Component class Person extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect(id: 'person_actor_view_id', uri_path: '/person/{id<\d+>}', target: [C\PersonFeed::class, 'personViewId']); $r->connect(id: 'person_actor_view_id', uri_path: '/person/{id<\d+>}', target: [C\PersonFeed::class, 'personViewId']);
$r->connect(id: 'person_actor_view_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\PersonFeed::class, 'personViewNickname'], options: ['is_system_path' => false]); $r->connect(id: 'person_actor_view_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\PersonFeed::class, 'personViewNickname'], options: ['is_system_path' => false]);

View File

@@ -23,8 +23,8 @@ declare(strict_types = 1);
namespace Component\Person\tests\Controller; namespace Component\Person\tests\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\LocalUser; use App\Entity\LocalUser;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;
use Jchook\AssertThrows\AssertThrows; use Jchook\AssertThrows\AssertThrows;
@@ -55,13 +55,13 @@ class PersonSettingsTest extends GNUsocialTestCase
]); ]);
$changed_user = DB::findOneBy(LocalUser::class, ['id' => $user->getId()]); $changed_user = DB::findOneBy(LocalUser::class, ['id' => $user->getId()]);
$actor = $changed_user->getActor(); $actor = $changed_user->getActor();
static::assertSame('form_test_user_new_nickname', $changed_user->getNickname()); static::assertSame($changed_user->getNickname(), 'form_test_user_new_nickname');
static::assertSame('form_test_user_new_nickname', $actor->getNickname()); static::assertSame($actor->getNickname(), 'form_test_user_new_nickname');
static::assertSame('Form User', $actor->getFullName()); static::assertSame($actor->getFullName(), 'Form User');
static::assertSame('https://gnu.org', $actor->getHomepage()); static::assertSame($actor->getHomepage(), 'https://gnu.org');
static::assertSame('I was born at a very young age', $actor->getBio()); static::assertSame($actor->getBio(), 'I was born at a very young age');
static::assertSame('right here', $actor->getLocation()); static::assertSame($actor->getLocation(), 'right here');
// static::assertSame('908555842', $changed_user->getPhoneNumber()->getNationalNumber()); // static::assertSame($changed_user->getPhoneNumber()->getNationalNumber(), '908555842');
} }
/** /**

View File

@@ -52,7 +52,7 @@ class Posting extends Controller
content_type: $data['content_type'], content_type: $data['content_type'],
locale: $data['language'], locale: $data['language'],
scope: VisibilityScope::from($data['visibility']), scope: VisibilityScope::from($data['visibility']),
attentions: isset($target) ? [$target] : [], targets: isset($target) ? [$target] : [],
reply_to: \array_key_exists('reply_to_id', $data) ? $data['reply_to_id'] : null, reply_to: \array_key_exists('reply_to_id', $data) ? $data['reply_to_id'] : null,
attachments: $data['attachments'], attachments: $data['attachments'],
process_note_content_extra_args: $extra_args, process_note_content_extra_args: $extra_args,
@@ -61,9 +61,9 @@ class Posting extends Controller
return Core\Form::forceRedirect($form, $request); return Core\Form::forceRedirect($form, $request);
} }
} catch (FormSizeFileException $e) { } catch (FormSizeFileException $e) {
throw new ClientException(_m('Invalid file size given.'), previous: $e); throw new ClientException(_m('Invalid file size given'), previous: $e);
} }
} }
throw new ClientException(_m('Invalid form submission.')); throw new ClientException(_m('Invalid form submission'));
} }
} }

View File

@@ -8,7 +8,7 @@ use App\Core\ActorLocalRoles;
use App\Core\Event; use App\Core\Event;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Router; use App\Core\Router\Router;
use App\Core\VisibilityScope; use App\Core\VisibilityScope;
use App\Entity\Actor; use App\Entity\Actor;
use App\Util\Common; use App\Util\Common;

View File

@@ -24,12 +24,13 @@ declare(strict_types = 1);
namespace Component\Posting; namespace Component\Posting;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Core\VisibilityScope; use App\Core\VisibilityScope;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
@@ -47,6 +48,7 @@ use Component\Attachment\Entity\AttachmentToNote;
use Component\Conversation\Conversation; use Component\Conversation\Conversation;
use Component\Language\Entity\Language; use Component\Language\Entity\Language;
use Component\Notification\Entity\Attention; use Component\Notification\Entity\Attention;
use Functional as F;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -54,7 +56,7 @@ class Posting extends Component
{ {
public const route = 'posting_form_action'; public const route = 'posting_form_action';
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect(self::route, '/form/posting', Controller\Posting::class); $r->connect(self::route, '/form/posting', Controller\Posting::class);
return Event::next; return Event::next;
@@ -86,13 +88,13 @@ class Posting extends Component
* @throws DuplicateFoundException * @throws DuplicateFoundException
* @throws ServerException * @throws ServerException
*/ */
public static function storeLocalArticle( public static function storeLocalPage(
Actor $actor, Actor $actor,
?string $content, ?string $content,
string $content_type, string $content_type,
?string $locale = null, ?string $locale = null,
?VisibilityScope $scope = null, ?VisibilityScope $scope = null,
array $attentions = [], array $targets = [],
null|int|Note $reply_to = null, null|int|Note $reply_to = null,
array $attachments = [], array $attachments = [],
array $processed_attachments = [], array $processed_attachments = [],
@@ -102,13 +104,13 @@ class Posting extends Component
string $source = 'web', string $source = 'web',
?string $title = null, ?string $title = null,
): array { ): array {
[$activity, $note, $effective_attentions] = self::storeLocalNote( [$activity, $note, $attention_ids] = self::storeLocalNote(
actor: $actor, actor: $actor,
content: $content, content: $content,
content_type: $content_type, content_type: $content_type,
locale: $locale, locale: $locale,
scope: $scope, scope: $scope,
attentions: $attentions, targets: $targets,
reply_to: $reply_to, reply_to: $reply_to,
attachments: $attachments, attachments: $attachments,
processed_attachments: $processed_attachments, processed_attachments: $processed_attachments,
@@ -117,24 +119,16 @@ class Posting extends Component
rendered: $rendered, rendered: $rendered,
source: $source, source: $source,
); );
$note->setType('article'); $note->setType('page');
$note->setTitle($title); $note->setTitle($title);
if ($flush_and_notify) { if ($flush_and_notify) {
// Flush before notification // Flush before notification
DB::flush(); DB::flush();
Event::handle('NewNotification', [ Event::handle('NewNotification', [$actor, $activity, ['object' => $attention_ids], _m('{nickname} created a page {note_id}.', ['{nickname}' => $actor->getNickname(), '{note_id}' => $activity->getObjectId()])]);
$actor,
$activity,
$effective_attentions,
_m('Actor {actor_id} created article {note_id}.', [
'{actor_id}' => $actor->getId(),
'{note_id}' => $activity->getObjectId(),
]),
]);
} }
return [$activity, $note, $effective_attentions]; return [$activity, $note, $attention_ids];
} }
/** /**
@@ -147,7 +141,7 @@ class Posting extends Component
* @param string $content_type Indicating one of the various supported content format (Plain Text, Markdown, LaTeX...) * @param string $content_type Indicating one of the various supported content format (Plain Text, Markdown, LaTeX...)
* @param null|string $locale Note's written text language, set by the default Actor language or upon filling * @param null|string $locale Note's written text language, set by the default Actor language or upon filling
* @param null|VisibilityScope $scope The visibility of this Note * @param null|VisibilityScope $scope The visibility of this Note
* @param array $attentions Actor|int[]: In Group/To Person or Bot, registers an attention between note and target * @param array $targets Actor|int[]: In Group/To Person or Bot, registers an attention between note and target
* @param null|int|Note $reply_to The soon-to-be Note parent's id, if it's a Reply itself * @param null|int|Note $reply_to The soon-to-be Note parent's id, if it's a Reply itself
* @param array $attachments UploadedFile[] to be stored as GSFiles associated to this note * @param array $attachments UploadedFile[] to be stored as GSFiles associated to this note
* @param array $processed_attachments Array of [Attachment, Attachment's name][] to be associated to this $actor and Note * @param array $processed_attachments Array of [Attachment, Attachment's name][] to be associated to this $actor and Note
@@ -160,7 +154,7 @@ class Posting extends Component
* @throws DuplicateFoundException * @throws DuplicateFoundException
* @throws ServerException * @throws ServerException
* *
* @return array [Activity, Note, Effective Attentions] * @return array [Activity, Note, int[]] Activity, Note, Attention Ids
*/ */
public static function storeLocalNote( public static function storeLocalNote(
Actor $actor, Actor $actor,
@@ -168,7 +162,7 @@ class Posting extends Component
string $content_type, string $content_type,
?string $locale = null, ?string $locale = null,
?VisibilityScope $scope = null, ?VisibilityScope $scope = null,
array $attentions = [], array $targets = [],
null|int|Note $reply_to = null, null|int|Note $reply_to = null,
array $attachments = [], array $attachments = [],
array $processed_attachments = [], array $processed_attachments = [],
@@ -215,7 +209,7 @@ class Posting extends Component
if (!\is_null($reply_to_id)) { if (!\is_null($reply_to_id)) {
Cache::incr(Note::cacheKeys($reply_to_id)['replies-count']); Cache::incr(Note::cacheKeys($reply_to_id)['replies-count']);
// Not having them cached doesn't mean replies don't exist, but don't push it to the // Not having them cached doesn't mean replies don't exist, but don't push it to the
// list, as that means they need to be re-fetched, or some would be missed // list, as that means they need to be refetched, or some would be missed
if (Cache::exists(Note::cacheKeys($reply_to_id)['replies'])) { if (Cache::exists(Note::cacheKeys($reply_to_id)['replies'])) {
Cache::listPushRight(Note::cacheKeys($reply_to_id)['replies'], $note); Cache::listPushRight(Note::cacheKeys($reply_to_id)['replies'], $note);
} }
@@ -227,12 +221,12 @@ class Posting extends Component
Event::handle('ProcessNoteContent', [$note, $content, $content_type, $process_note_content_extra_args]); Event::handle('ProcessNoteContent', [$note, $content, $content_type, $process_note_content_extra_args]);
} }
// These are note attachments now, and not just attachments, ensure these relations are respected // These are note attachments now, and not just attachments, ensure these relations are ensured
if ($processed_attachments !== []) { if ($processed_attachments !== []) {
foreach ($processed_attachments as [$a, $fname]) { foreach ($processed_attachments as [$a, $fname]) {
// Most attachments should already be associated with its author, but maybe it didn't make sense // Most attachments should already be associated with its author, but maybe it didn't make sense
//for this attachment, or it's simply a repost of an attachment by a different actor //for this attachment, or it's simply a repost of an attachment by a different actor
if (DB::count(ActorToAttachment::class, $args = ['attachment_id' => $a->getId(), 'actor_id' => $actor->getId()]) === 0) { if (DB::count('actor_to_attachment', $args = ['attachment_id' => $a->getId(), 'actor_id' => $actor->getId()]) === 0) {
DB::persist(ActorToAttachment::create($args)); DB::persist(ActorToAttachment::create($args));
} }
DB::persist(AttachmentToNote::create(['attachment_id' => $a->getId(), 'note_id' => $note->getId(), 'title' => $fname])); DB::persist(AttachmentToNote::create(['attachment_id' => $a->getId(), 'note_id' => $note->getId(), 'title' => $fname]));
@@ -248,38 +242,13 @@ class Posting extends Component
]); ]);
DB::persist($activity); DB::persist($activity);
$effective_attentions = []; $attention_ids = [];
foreach ($attentions as $target) { foreach ($targets as $target) {
if (\is_int($target)) { $target_id = \is_int($target) ? $target : $target->getId();
$target_id = $target; DB::persist(Attention::create(['note_id' => $note->getId(), 'target_id' => $target_id]));
$add = !\array_key_exists($target_id, $effective_attentions); $attention_ids[$target_id] = true;
$effective_attentions[$target_id] = $target;
} else {
$target_id = $target->getId();
if ($add = !\array_key_exists($target_id, $effective_attentions)) {
$effective_attentions[$target_id] = $target_id;
}
}
if ($add) {
DB::persist(Attention::create(['object_type' => Note::schemaName(), 'object_id' => $note->getId(), 'target_id' => $target_id]));
}
}
foreach ($mentions as $m) {
foreach ($m['mentioned'] ?? [] as $mentioned) {
$target_id = $mentioned->getId();
if (!\array_key_exists($target_id, $effective_attentions)) {
DB::persist(Attention::create(['object_type' => Note::schemaName(), 'object_id' => $note->getId(), 'target_id' => $target_id]));
}
$effective_attentions[$target_id] = $mentioned;
}
}
foreach ($actor->getSubscribers() as $subscriber) {
$target_id = $subscriber->getId();
DB::persist(Attention::create(['object_type' => Activity::schemaName(), 'object_id' => $activity->getId(), 'target_id' => $target_id]));
$effective_attentions[$target_id] = $subscriber;
} }
$attention_ids = array_keys($attention_ids);
if ($flush_and_notify) { if ($flush_and_notify) {
// Flush before notification // Flush before notification
@@ -287,15 +256,18 @@ class Posting extends Component
Event::handle('NewNotification', [ Event::handle('NewNotification', [
$actor, $actor,
$activity, $activity,
$effective_attentions, [
_m('Actor {actor_id} created note {note_id}.', [ 'note-attention' => $attention_ids,
'{actor_id}' => $actor->getId(), 'object' => F\unique(F\flat_map($mentions, fn (array $m) => F\map($m['mentioned'] ?? [], fn (Actor $a) => $a->getId()))),
],
_m('{nickname} created a note {note_id}.', [
'{nickname}' => $actor->getNickname(),
'{note_id}' => $activity->getObjectId(), '{note_id}' => $activity->getObjectId(),
]), ]),
]); ]);
} }
return [$activity, $note, $effective_attentions]; return [$activity, $note, $attention_ids];
} }
public function onRenderNoteContent(string $content, string $content_type, ?string &$rendered, Actor $author, ?string $language = null, array &$mentions = []) public function onRenderNoteContent(string $content, string $content_type, ?string &$rendered, Actor $author, ?string $language = null, array &$mentions = [])

View File

@@ -23,11 +23,11 @@ declare(strict_types = 1);
namespace Component\Subscription\Controller; namespace Component\Subscription\Controller;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Form; use App\Core\Form;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;

View File

@@ -114,6 +114,27 @@ class ActorSubscription extends Entity
]; ];
} }
/**
* @see Entity->getNotificationTargetIds
*/
public function getNotificationTargetIds(array $ids_already_known = [], ?int $sender_id = null, bool $include_additional = true): array
{
if (!\array_key_exists('object', $ids_already_known)) {
$target_ids = [$this->getSubscribedId()]; // The object of any subscription is the one subscribed (or unsubscribed)
} else {
$target_ids = $ids_already_known['object'];
}
// Additional actors that should know about this
if ($include_additional && \array_key_exists('additional', $ids_already_known)) {
array_push($target_ids, ...$ids_already_known['additional']);
} else {
return $target_ids;
}
return array_unique($target_ids);
}
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [

View File

@@ -24,11 +24,12 @@ declare(strict_types = 1);
namespace Component\Subscription; namespace Component\Subscription;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\LocalUser; use App\Entity\LocalUser;
@@ -36,14 +37,14 @@ use App\Util\Common;
use App\Util\Exception\DuplicateFoundException; use App\Util\Exception\DuplicateFoundException;
use App\Util\Exception\NotFoundException; use App\Util\Exception\NotFoundException;
use App\Util\Exception\ServerException; use App\Util\Exception\ServerException;
use Component\Notification\Entity\Attention;
use Component\Subscription\Controller\Subscribers as SubscribersController; use Component\Subscription\Controller\Subscribers as SubscribersController;
use Component\Subscription\Controller\Subscriptions as SubscriptionsController; use Component\Subscription\Controller\Subscriptions as SubscriptionsController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class Subscription extends Component class Subscription extends Component
{ {
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect(id: 'actor_subscribe_add', uri_path: '/actor/subscribe/{object_id<\d+>}', target: [SubscribersController::class, 'subscribersAdd']); $r->connect(id: 'actor_subscribe_add', uri_path: '/actor/subscribe/{object_id<\d+>}', target: [SubscribersController::class, 'subscribersAdd']);
$r->connect(id: 'actor_subscribe_remove', uri_path: '/actor/unsubscribe/{object_id<\d+>}', target: [SubscribersController::class, 'subscribersRemove']); $r->connect(id: 'actor_subscribe_remove', uri_path: '/actor/unsubscribe/{object_id<\d+>}', target: [SubscribersController::class, 'subscribersRemove']);
@@ -96,24 +97,22 @@ class Subscription extends Component
$subscription = DB::findOneBy(table: Entity\ActorSubscription::class, criteria: $opts, return_null: true); $subscription = DB::findOneBy(table: Entity\ActorSubscription::class, criteria: $opts, return_null: true);
$activity = null; $activity = null;
if (\is_null($subscription)) { if (\is_null($subscription)) {
DB::persist($subscription = Entity\ActorSubscription::create($opts)); DB::persist(Entity\ActorSubscription::create($opts));
$activity = Activity::create([ $activity = Activity::create([
'actor_id' => $subscriber_id, 'actor_id' => $subscriber_id,
'verb' => 'subscribe', 'verb' => 'subscribe',
'object_type' => Actor::schemaName(), 'object_type' => 'actor',
'object_id' => $subscribed_id, 'object_id' => $subscribed_id,
'source' => $source, 'source' => $source,
]); ]);
DB::persist($activity); DB::persist($activity);
DB::persist(Attention::create(['object_type' => Activity::schemaName(), 'object_id' => $activity->getId(), 'target_id' => $subscribed_id]));
Event::handle('NewNotification', [ Event::handle('NewNotification', [
\is_int($subject) ? $subject : Actor::getById($subscriber_id), \is_int($subject) ? $subject : Actor::getById($subscriber_id),
$activity, $activity,
[$subscribed_id], ['object' => [$activity->getObjectId()]],
$reason = _m('{subject} subscribed to {object}.', ['{subject}' => $activity->getActorId(), '{object}' => $activity->getObjectId()]), _m('{subject} subscribed to {object}.', ['{subject}' => $activity->getActorId(), '{object}' => $activity->getObjectId()]),
]); ]);
Event::handle('NewSubscriptionEnd', [$subject, $activity, $object, $reason]);
} }
return $activity; return $activity;
} }
@@ -147,22 +146,21 @@ class Subscription extends Component
if (!\is_null($subscription)) { if (!\is_null($subscription)) {
// Remove Subscription // Remove Subscription
DB::remove($subscription); DB::remove($subscription);
$previous_follow_activity = DB::findBy(Activity::class, ['verb' => 'subscribe', 'object_type' => Actor::schemaName(), 'object_id' => $subscribed_id], order_by: ['created' => 'DESC'])[0]; $previous_follow_activity = DB::findBy('activity', ['verb' => 'subscribe', 'object_type' => 'actor', 'object_id' => $subscribed_id], order_by: ['created' => 'DESC'])[0];
// Store Activity // Store Activity
$activity = Activity::create([ $activity = Activity::create([
'actor_id' => $subscriber_id, 'actor_id' => $subscriber_id,
'verb' => 'undo', 'verb' => 'undo',
'object_type' => Activity::schemaName(), 'object_type' => 'activity',
'object_id' => $previous_follow_activity->getId(), 'object_id' => $previous_follow_activity->getId(),
'source' => $source, 'source' => $source,
]); ]);
DB::persist($activity); DB::persist($activity);
DB::persist(Attention::create(['object_type' => Activity::schemaName(), 'object_id' => $activity->getId(), 'target_id' => $subscribed_id]));
Event::handle('NewNotification', [ Event::handle('NewNotification', [
\is_int($subject) ? $subject : Actor::getById($subscriber_id), \is_int($subject) ? $subject : Actor::getById($subscriber_id),
$activity, $activity,
[$subscribed_id], ['object' => [$previous_follow_activity->getObjectId()]],
_m('{subject} unsubscribed from {object}.', ['{subject}' => $activity->getActorId(), '{object}' => $previous_follow_activity->getObjectId()]), _m('{subject} unsubscribed from {object}.', ['{subject}' => $activity->getActorId(), '{object}' => $previous_follow_activity->getObjectId()]),
]); ]);
} }

View File

@@ -22,9 +22,9 @@ declare(strict_types = 1);
namespace Component\Tag\Entity; namespace Component\Tag\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Note; use App\Entity\Note;
use Component\Language\Entity\Language; use Component\Language\Entity\Language;

View File

@@ -22,7 +22,7 @@ declare(strict_types = 1);
namespace Component\Tag\Entity; namespace Component\Tag\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use Component\Tag\Tag; use Component\Tag\Tag;
use DateTimeInterface; use DateTimeInterface;

View File

@@ -24,11 +24,11 @@ declare(strict_types = 1);
namespace Component\Tag; namespace Component\Tag;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Note; use App\Entity\Note;
use App\Util\Common; use App\Util\Common;
@@ -67,54 +67,6 @@ class Tag extends Component
return Event::next; return Event::next;
} }
public static function maybeCreateTag(string $tag, int $note_id, ?int $lang_id): ?NoteTag
{
if (!self::validate($tag)) {
return null; // Ignore invalid tag candidates
}
$canonical_tag = self::canonicalTag($tag, \is_null($lang_id) ? null : Language::getById($lang_id)->getLocale());
DB::persist($note_tag = NoteTag::create([
'tag' => $tag,
'canonical' => $canonical_tag,
'note_id' => $note_id,
'use_canonical' => $extra_args['tag_use_canonical'] ?? false,
'language_id' => $lang_id,
]));
foreach (self::cacheKeys($canonical_tag) as $key) {
Cache::delete($key);
}
return $note_tag;
}
/**
* @return NoteTag[]
*/
public static function getNoteTags(int $actor_id, ?string $note_type): array
{
$query = <<<'EOF'
select nt from \App\Entity\Note n
join \Component\Tag\Entity\NoteTag nt with n.id = nt.note_id
where n.actor_id = :id
EOF;
if (\is_null($note_type)) {
return Cache::getList(
Actor::cacheKeys($actor_id, 'any')['note-tags'],
fn () => DB::dql(
$query,
['id' => $actor_id],
),
);
} else {
return Cache::getList(
Actor::cacheKeys($actor_id, $note_type)['note-tags'],
fn () => DB::dql(
$query . ' and n.type = :type',
['id' => $actor_id, 'type' => $note_type],
),
);
}
}
/** /**
* Process note by extracting any tags present * Process note by extracting any tags present
*/ */
@@ -130,7 +82,21 @@ class Tag extends Component
$matched_tags = array_unique(F\map($matched_tags, fn ($m) => $m[2])); $matched_tags = array_unique(F\map($matched_tags, fn ($m) => $m[2]));
foreach ($matched_tags as $match) { foreach ($matched_tags as $match) {
$tag = self::extract($match); $tag = self::extract($match);
self::maybeCreateTag(tag: $tag, note_id: $note->getId(), lang_id: $note->getLanguageId()); if (!self::validate($tag)) {
continue; // Ignore invalid tag candidates
}
$canonical_tag = self::canonicalTag($tag, \is_null($lang_id = $note->getLanguageId()) ? null : Language::getById($lang_id)->getLocale());
DB::persist(NoteTag::create([
'tag' => $tag,
'canonical' => $canonical_tag,
'note_id' => $note->getId(),
'use_canonical' => $extra_args['tag_use_canonical'] ?? false,
'language_id' => $lang_id,
]));
Cache::listPushLeft("tag-{$canonical_tag}", $note);
foreach (self::cacheKeys($canonical_tag) as $key) {
Cache::delete($key);
}
} }
return Event::next; return Event::next;
} }

View File

@@ -112,8 +112,7 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"App\\Test\\Fixtures\\": "tests/fixtures/", "App\\Tests\\": "tests/"
"App\\Test\\": "tests/"
} }
}, },
"replace": { "replace": {

231
composer.lock generated
View File

@@ -553,16 +553,16 @@
}, },
{ {
"name": "doctrine/dbal", "name": "doctrine/dbal",
"version": "3.3.4", "version": "3.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/dbal.git", "url": "https://github.com/doctrine/dbal.git",
"reference": "83f779beaea1893c0bece093ab2104c6d15a7f26" "reference": "35eae239ef515d55ebb24e9d4715cad09a4f58ed"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/83f779beaea1893c0bece093ab2104c6d15a7f26", "url": "https://api.github.com/repos/doctrine/dbal/zipball/35eae239ef515d55ebb24e9d4715cad09a4f58ed",
"reference": "83f779beaea1893c0bece093ab2104c6d15a7f26", "reference": "35eae239ef515d55ebb24e9d4715cad09a4f58ed",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -577,14 +577,14 @@
"require-dev": { "require-dev": {
"doctrine/coding-standard": "9.0.0", "doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2021.1", "jetbrains/phpstorm-stubs": "2021.1",
"phpstan/phpstan": "1.4.6", "phpstan/phpstan": "1.4.0",
"phpstan/phpstan-strict-rules": "^1.1", "phpstan/phpstan-strict-rules": "^1.1",
"phpunit/phpunit": "9.5.16", "phpunit/phpunit": "9.5.11",
"psalm/plugin-phpunit": "0.16.1", "psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.6.2", "squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^5.2|^6.0", "symfony/cache": "^5.2|^6.0",
"symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
"vimeo/psalm": "4.22.0" "vimeo/psalm": "4.16.1"
}, },
"suggest": { "suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files." "symfony/console": "For helpful console commands such as SQL execution and import of files."
@@ -644,7 +644,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/dbal/issues", "issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.3.4" "source": "https://github.com/doctrine/dbal/tree/3.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -660,7 +660,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-20T18:37:29+00:00" "time": "2022-02-05T16:33:45+00:00"
}, },
{ {
"name": "doctrine/deprecations", "name": "doctrine/deprecations",
@@ -1342,16 +1342,16 @@
}, },
{ {
"name": "doctrine/orm", "name": "doctrine/orm",
"version": "2.11.2", "version": "2.11.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/orm.git", "url": "https://github.com/doctrine/orm.git",
"reference": "9c351e044478135aec1755e2c0c0493a4b6309db" "reference": "4b88ce787d3916c8366abf52f6c658a7a27ed3a6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/orm/zipball/9c351e044478135aec1755e2c0c0493a4b6309db", "url": "https://api.github.com/repos/doctrine/orm/zipball/4b88ce787d3916c8366abf52f6c658a7a27ed3a6",
"reference": "9c351e044478135aec1755e2c0c0493a4b6309db", "reference": "4b88ce787d3916c8366abf52f6c658a7a27ed3a6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1367,6 +1367,7 @@
"doctrine/lexer": "^1.0", "doctrine/lexer": "^1.0",
"doctrine/persistence": "^2.2", "doctrine/persistence": "^2.2",
"ext-ctype": "*", "ext-ctype": "*",
"ext-pdo": "*",
"php": "^7.1 || ^8.0", "php": "^7.1 || ^8.0",
"psr/cache": "^1 || ^2 || ^3", "psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0",
@@ -1380,12 +1381,12 @@
"doctrine/annotations": "^1.13", "doctrine/annotations": "^1.13",
"doctrine/coding-standard": "^9.0", "doctrine/coding-standard": "^9.0",
"phpbench/phpbench": "^0.16.10 || ^1.0", "phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "1.4.6", "phpstan/phpstan": "1.4.3",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"squizlabs/php_codesniffer": "3.6.2", "squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.22.0" "vimeo/psalm": "4.19.0"
}, },
"suggest": { "suggest": {
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0",
@@ -1434,22 +1435,22 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/orm/issues", "issues": "https://github.com/doctrine/orm/issues",
"source": "https://github.com/doctrine/orm/tree/2.11.2" "source": "https://github.com/doctrine/orm/tree/2.11.1"
}, },
"time": "2022-03-09T15:23:58+00:00" "time": "2022-01-30T21:47:06+00:00"
}, },
{ {
"name": "doctrine/persistence", "name": "doctrine/persistence",
"version": "2.4.1", "version": "2.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/persistence.git", "url": "https://github.com/doctrine/persistence.git",
"reference": "092a52b71410ac1795287bb5135704ef07d18dd0" "reference": "f8af155c1e7963f3d2b4415097d55757bbaa53d8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/persistence/zipball/092a52b71410ac1795287bb5135704ef07d18dd0", "url": "https://api.github.com/repos/doctrine/persistence/zipball/f8af155c1e7963f3d2b4415097d55757bbaa53d8",
"reference": "092a52b71410ac1795287bb5135704ef07d18dd0", "reference": "f8af155c1e7963f3d2b4415097d55757bbaa53d8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1462,23 +1463,23 @@
}, },
"conflict": { "conflict": {
"doctrine/annotations": "<1.0 || >=2.0", "doctrine/annotations": "<1.0 || >=2.0",
"doctrine/common": "<2.10" "doctrine/common": "<2.10@dev"
}, },
"require-dev": { "require-dev": {
"composer/package-versions-deprecated": "^1.11", "composer/package-versions-deprecated": "^1.11",
"doctrine/annotations": "^1.0", "doctrine/annotations": "^1.0",
"doctrine/coding-standard": "^9.0", "doctrine/coding-standard": "^6.0 || ^9.0",
"doctrine/common": "^3.0", "doctrine/common": "^3.0",
"phpstan/phpstan": "1.4.6", "phpstan/phpstan": "1.2.0",
"phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", "phpunit/phpunit": "^7.5.20 || ^8.0 || ^9.0",
"symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/cache": "^4.4 || ^5.0 || ^6.0",
"vimeo/psalm": "4.21.0" "vimeo/psalm": "4.13.1"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Doctrine\\Common\\": "src/Common", "Doctrine\\Common\\": "lib/Doctrine/Common",
"Doctrine\\Persistence\\": "src/Persistence" "Doctrine\\Persistence\\": "lib/Doctrine/Persistence"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@@ -1522,9 +1523,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/persistence/issues", "issues": "https://github.com/doctrine/persistence/issues",
"source": "https://github.com/doctrine/persistence/tree/2.4.1" "source": "https://github.com/doctrine/persistence/tree/2.3.0"
}, },
"time": "2022-03-22T06:44:40+00:00" "time": "2022-01-09T19:58:46+00:00"
}, },
{ {
"name": "doctrine/sql-formatter", "name": "doctrine/sql-formatter",
@@ -1648,16 +1649,16 @@
}, },
{ {
"name": "embed/embed", "name": "embed/embed",
"version": "v4.4.3", "version": "v4.4.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/oscarotero/Embed.git", "url": "https://github.com/oscarotero/Embed.git",
"reference": "2ac32581a8617c3bbe593e3d7799ca9db6974471" "reference": "84631fa16f2a669de66218774a20b6c1c5f9ba03"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/oscarotero/Embed/zipball/2ac32581a8617c3bbe593e3d7799ca9db6974471", "url": "https://api.github.com/repos/oscarotero/Embed/zipball/84631fa16f2a669de66218774a20b6c1c5f9ba03",
"reference": "2ac32581a8617c3bbe593e3d7799ca9db6974471", "reference": "84631fa16f2a669de66218774a20b6c1c5f9ba03",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1717,7 +1718,7 @@
"support": { "support": {
"email": "oom@oscarotero.com", "email": "oom@oscarotero.com",
"issues": "https://github.com/oscarotero/Embed/issues", "issues": "https://github.com/oscarotero/Embed/issues",
"source": "https://github.com/oscarotero/Embed/tree/v4.4.3" "source": "https://github.com/oscarotero/Embed/tree/v4.4.2"
}, },
"funding": [ "funding": [
{ {
@@ -1733,7 +1734,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"time": "2022-03-13T01:27:51+00:00" "time": "2022-02-13T16:03:59+00:00"
}, },
{ {
"name": "erusev/parsedown", "name": "erusev/parsedown",
@@ -1916,16 +1917,16 @@
}, },
{ {
"name": "giggsey/libphonenumber-for-php", "name": "giggsey/libphonenumber-for-php",
"version": "8.12.45", "version": "8.12.44",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/giggsey/libphonenumber-for-php.git", "url": "https://github.com/giggsey/libphonenumber-for-php.git",
"reference": "7f494fd3b87c7d83472e8b6126eedc6b72dd3f3e" "reference": "a726990faf05bfffdd826f75d9b41a1580dad4a7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/7f494fd3b87c7d83472e8b6126eedc6b72dd3f3e", "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/a726990faf05bfffdd826f75d9b41a1580dad4a7",
"reference": "7f494fd3b87c7d83472e8b6126eedc6b72dd3f3e", "reference": "a726990faf05bfffdd826f75d9b41a1580dad4a7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1985,7 +1986,7 @@
"issues": "https://github.com/giggsey/libphonenumber-for-php/issues", "issues": "https://github.com/giggsey/libphonenumber-for-php/issues",
"source": "https://github.com/giggsey/libphonenumber-for-php" "source": "https://github.com/giggsey/libphonenumber-for-php"
}, },
"time": "2022-03-10T10:28:34+00:00" "time": "2022-02-24T09:38:54+00:00"
}, },
{ {
"name": "giggsey/locale", "name": "giggsey/locale",
@@ -2043,16 +2044,16 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.4.2", "version": "7.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4" "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
"reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4", "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2147,7 +2148,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.4.2" "source": "https://github.com/guzzle/guzzle/tree/7.4.1"
}, },
"funding": [ "funding": [
{ {
@@ -2163,7 +2164,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-20T14:16:28+00:00" "time": "2021-12-06T18:43:05+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
@@ -2251,16 +2252,16 @@
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "2.2.1", "version": "2.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72",
"reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2284,7 +2285,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.2-dev" "dev-master": "2.1-dev"
} }
}, },
"autoload": { "autoload": {
@@ -2346,7 +2347,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.2.1" "source": "https://github.com/guzzle/psr7/tree/2.1.0"
}, },
"funding": [ "funding": [
{ {
@@ -2362,7 +2363,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-20T21:55:58+00:00" "time": "2021-10-06T17:43:30+00:00"
}, },
{ {
"name": "jcupitt/vips", "name": "jcupitt/vips",
@@ -3321,16 +3322,16 @@
}, },
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "2.4.0", "version": "2.3.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Seldaek/monolog.git", "url": "https://github.com/Seldaek/monolog.git",
"reference": "d7fd7450628561ba697b7097d86db72662f54aef" "reference": "fd4380d6fc37626e2f799f29d91195040137eba9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/d7fd7450628561ba697b7097d86db72662f54aef", "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9",
"reference": "d7fd7450628561ba697b7097d86db72662f54aef", "reference": "fd4380d6fc37626e2f799f29d91195040137eba9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3352,7 +3353,7 @@
"phpstan/phpstan": "^0.12.91", "phpstan/phpstan": "^0.12.91",
"phpunit/phpunit": "^8.5", "phpunit/phpunit": "^8.5",
"predis/predis": "^1.1", "predis/predis": "^1.1",
"rollbar/rollbar": "^1.3 || ^2 || ^3", "rollbar/rollbar": "^1.3",
"ruflin/elastica": ">=0.90@dev", "ruflin/elastica": ">=0.90@dev",
"swiftmailer/swiftmailer": "^5.3|^6.0" "swiftmailer/swiftmailer": "^5.3|^6.0"
}, },
@@ -3404,7 +3405,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/Seldaek/monolog/issues", "issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/2.4.0" "source": "https://github.com/Seldaek/monolog/tree/2.3.5"
}, },
"funding": [ "funding": [
{ {
@@ -3416,7 +3417,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-14T12:44:37+00:00" "time": "2021-10-01T21:08:31+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
@@ -3849,21 +3850,21 @@
}, },
{ {
"name": "php-ds/php-ds", "name": "php-ds/php-ds",
"version": "v1.4.1", "version": "v1.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-ds/polyfill.git", "url": "https://github.com/php-ds/polyfill.git",
"reference": "43d2df301a9e2017f67b8c11d94a5222f9c00fd1" "reference": "298cafa4e0e20aeba4d63644e3de694e7cf83ffd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-ds/polyfill/zipball/43d2df301a9e2017f67b8c11d94a5222f9c00fd1", "url": "https://api.github.com/repos/php-ds/polyfill/zipball/298cafa4e0e20aeba4d63644e3de694e7cf83ffd",
"reference": "43d2df301a9e2017f67b8c11d94a5222f9c00fd1", "reference": "298cafa4e0e20aeba4d63644e3de694e7cf83ffd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"php": ">=7.0" "php": "^7.3 || ~8.0.0 || ~8.1.0"
}, },
"provide": { "provide": {
"ext-ds": "1.3.0" "ext-ds": "1.3.0"
@@ -3898,9 +3899,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/php-ds/polyfill/issues", "issues": "https://github.com/php-ds/polyfill/issues",
"source": "https://github.com/php-ds/polyfill/tree/v1.4.1" "source": "https://github.com/php-ds/polyfill/tree/v1.4.0"
}, },
"time": "2022-03-09T20:39:30+00:00" "time": "2021-11-17T19:52:15+00:00"
}, },
{ {
"name": "php-ffmpeg/php-ffmpeg", "name": "php-ffmpeg/php-ffmpeg",
@@ -5020,16 +5021,16 @@
}, },
{ {
"name": "spatie/temporary-directory", "name": "spatie/temporary-directory",
"version": "2.1.0", "version": "2.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/temporary-directory.git", "url": "https://github.com/spatie/temporary-directory.git",
"reference": "79f138f2b81adae583d04d3727a4538dd394023f" "reference": "06fe0f10d068fdf145c9b2235030e568c913bb61"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/temporary-directory/zipball/79f138f2b81adae583d04d3727a4538dd394023f", "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/06fe0f10d068fdf145c9b2235030e568c913bb61",
"reference": "79f138f2b81adae583d04d3727a4538dd394023f", "reference": "06fe0f10d068fdf145c9b2235030e568c913bb61",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -5065,7 +5066,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/spatie/temporary-directory/issues", "issues": "https://github.com/spatie/temporary-directory/issues",
"source": "https://github.com/spatie/temporary-directory/tree/2.1.0" "source": "https://github.com/spatie/temporary-directory/tree/2.0.0"
}, },
"funding": [ "funding": [
{ {
@@ -5077,7 +5078,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-11T08:16:01+00:00" "time": "2021-03-30T19:46:13+00:00"
}, },
{ {
"name": "symfony/asset", "name": "symfony/asset",
@@ -10715,16 +10716,16 @@
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.3.9", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "6ff9b0e440fa66f97f207e181c41340ddfa5683d" "reference": "972d8604a92b7054828b539f2febb0211dd5945c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/6ff9b0e440fa66f97f207e181c41340ddfa5683d", "url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c",
"reference": "6ff9b0e440fa66f97f207e181c41340ddfa5683d", "reference": "972d8604a92b7054828b539f2febb0211dd5945c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -10775,7 +10776,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.3.9" "source": "https://github.com/twigphp/Twig/tree/v3.3.8"
}, },
"funding": [ "funding": [
{ {
@@ -10787,7 +10788,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-25T09:37:52+00:00" "time": "2022-02-04T06:59:48+00:00"
}, },
{ {
"name": "voku/portable-ascii", "name": "voku/portable-ascii",
@@ -11242,16 +11243,16 @@
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
"version": "3.3.1", "version": "3.2.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/semver.git", "url": "https://github.com/composer/semver.git",
"reference": "5d8e574bb0e69188786b8ef77d43341222a41a71" "reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/5d8e574bb0e69188786b8ef77d43341222a41a71", "url": "https://api.github.com/repos/composer/semver/zipball/a951f614bd64dcd26137bc9b7b2637ddcfc57649",
"reference": "5d8e574bb0e69188786b8ef77d43341222a41a71", "reference": "a951f614bd64dcd26137bc9b7b2637ddcfc57649",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -11303,7 +11304,7 @@
"support": { "support": {
"irc": "irc://irc.freenode.org/composer", "irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/semver/issues", "issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.3.1" "source": "https://github.com/composer/semver/tree/3.2.9"
}, },
"funding": [ "funding": [
{ {
@@ -11319,7 +11320,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-16T11:22:07+00:00" "time": "2022-02-04T13:58:43+00:00"
}, },
{ {
"name": "composer/xdebug-handler", "name": "composer/xdebug-handler",
@@ -11554,16 +11555,16 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.8.0", "version": "v3.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3" "reference": "7705d5a985132a40282d18a176eb9a4a0497747c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7705d5a985132a40282d18a176eb9a4a0497747c",
"reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", "reference": "7705d5a985132a40282d18a176eb9a4a0497747c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -11631,7 +11632,7 @@
"description": "A tool to automatically fix PHP code style", "description": "A tool to automatically fix PHP code style",
"support": { "support": {
"issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues",
"source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.8.0" "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.7.0"
}, },
"funding": [ "funding": [
{ {
@@ -11639,7 +11640,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-18T17:20:59+00:00" "time": "2022-03-07T16:59:59+00:00"
}, },
{ {
"name": "jchook/phpunit-assert-throws", "name": "jchook/phpunit-assert-throws",
@@ -11998,12 +11999,12 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "a9503c3474dfca53db977ebd14987264e8e8d6bf" "reference": "996a6c62b7e832903b4cf60e5c5744a4521ff2cc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a9503c3474dfca53db977ebd14987264e8e8d6bf", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/996a6c62b7e832903b4cf60e5c5744a4521ff2cc",
"reference": "a9503c3474dfca53db977ebd14987264e8e8d6bf", "reference": "996a6c62b7e832903b4cf60e5c5744a4521ff2cc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -12012,11 +12013,17 @@
"conflict": { "conflict": {
"phpstan/phpstan-shim": "*" "phpstan/phpstan-shim": "*"
}, },
"default-branch": true,
"bin": [ "bin": [
"phpstan", "phpstan",
"phpstan.phar" "phpstan.phar"
], ],
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": { "autoload": {
"files": [ "files": [
"bootstrap.php" "bootstrap.php"
@@ -12049,7 +12056,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-12T12:05:50+00:00" "time": "2022-03-08T14:11:13+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@@ -12371,16 +12378,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.5.19", "version": "9.5.18",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807" "reference": "1b5856028273bfd855e60a887278857d872ec67a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35ea4b7f3acabb26f4bb640f8c30866c401da807", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1b5856028273bfd855e60a887278857d872ec67a",
"reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807", "reference": "1b5856028273bfd855e60a887278857d872ec67a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -12410,7 +12417,7 @@
"sebastian/global-state": "^5.0.1", "sebastian/global-state": "^5.0.1",
"sebastian/object-enumerator": "^4.0.3", "sebastian/object-enumerator": "^4.0.3",
"sebastian/resource-operations": "^3.0.3", "sebastian/resource-operations": "^3.0.3",
"sebastian/type": "^3.0", "sebastian/type": "^2.3.4",
"sebastian/version": "^3.0.2" "sebastian/version": "^3.0.2"
}, },
"require-dev": { "require-dev": {
@@ -12458,7 +12465,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.19" "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.18"
}, },
"funding": [ "funding": [
{ {
@@ -12470,7 +12477,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-15T09:57:31+00:00" "time": "2022-03-08T06:52:28+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@@ -13329,28 +13336,28 @@
}, },
{ {
"name": "sebastian/type", "name": "sebastian/type",
"version": "3.0.0", "version": "2.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/type.git", "url": "https://github.com/sebastianbergmann/type.git",
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914",
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.5" "phpunit/phpunit": "^9.3"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.0-dev" "dev-master": "2.3-dev"
} }
}, },
"autoload": { "autoload": {
@@ -13373,7 +13380,7 @@
"homepage": "https://github.com/sebastianbergmann/type", "homepage": "https://github.com/sebastianbergmann/type",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/type/issues", "issues": "https://github.com/sebastianbergmann/type/issues",
"source": "https://github.com/sebastianbergmann/type/tree/3.0.0" "source": "https://github.com/sebastianbergmann/type/tree/2.3.4"
}, },
"funding": [ "funding": [
{ {
@@ -13381,7 +13388,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-15T09:54:48+00:00" "time": "2021-06-15T12:49:02+00:00"
}, },
{ {
"name": "sebastian/version", "name": "sebastian/version",

View File

@@ -20,13 +20,15 @@ security:
dev: dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
oauth:
pattern: ^/oauth
security: false
main: main:
lazy: true lazy: true
provider: local_user provider: local_user
form_login: form_login:
login_path: security_login login_path: security_login
check_path: security_login check_path: security_login
default_target_path: root
logout: logout:
path: security_logout path: security_logout
# where to redirect after logout # where to redirect after logout

View File

@@ -15,16 +15,13 @@ services:
resource: '../src/*' resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php,Routes}' exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php,Routes}'
App\Test\Fixtures\:
resource: '../tests/fixtures/*'
# controllers are imported separately to make sure services can be injected # controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class # as action arguments even if you don't extend any base controller class
App\Controller\: App\Controller\:
resource: '../src/Controller' resource: '../src/Controller'
tags: ['controller.service_arguments'] tags: ['controller.service_arguments']
App\Core\Router: App\Core\Router\RouteLoader:
tags: ['routing.loader'] tags: ['routing.loader']
# Wrapper around Doctrine's StaticPHP metadata driver # Wrapper around Doctrine's StaticPHP metadata driver

View File

@@ -1,18 +1,3 @@
server {
# Listen only on port 81 for localhost, and nothing else.
server_name 127.0.0.1;
listen 127.0.0.1:81 default_server;
charset utf-8;
# Certbot's folder used for the ACME challenge response.
location ^~ /.well-known/acme-challenge {
default_type text/plain;
root /var/www/certbot;
try_files $uri =404;
}
}
server { server {
listen [::]:80; listen [::]:80;
@@ -20,10 +5,6 @@ server {
server_name %hostname%; server_name %hostname%;
location '/.well-known/acme-challenge' {
proxy_pass http://localhost:81;
}
# redirect all traffic to HTTPS # redirect all traffic to HTTPS
rewrite ^ https://$host$request_uri? permanent; rewrite ^ https://$host$request_uri? permanent;
} }
@@ -54,13 +35,6 @@ server {
root /var/www/social; root /var/www/social;
} }
location /.well-known/acme-challenge/ {
allow all;
root /var/www/certbot;
try_files $uri =404;
break;
}
# PHP # PHP
location ~ ^/(index|install)\.php(/.*)?$ { location ~ ^/(index|install)\.php(/.*)?$ {
include fastcgi_params; include fastcgi_params;

View File

@@ -2,7 +2,8 @@
case "${DBMS}" in case "${DBMS}" in
'postgres') 'postgres')
test "$(PGPASSWORD="${POSTGRES_PASSWORD}" psql -Upostgres -hdb -tAc "select 1 from pg_database where datname='${SOCIAL_DB}'")" = "1" PGPASSWORD="${POSTGRES_PASSWORD}" psql -ltq -Upostgres -hdb | \
cut -d '|' -f1 | grep -Fwq "${SOCIAL_DB}"
DB_EXISTS=$? DB_EXISTS=$?
;; ;;
'mariadb') 'mariadb')
@@ -27,8 +28,7 @@ if [ ${DB_EXISTS} -ne 0 ]; then
chmod g+w -R . chmod g+w -R .
chown -R :www-data . chown -R :www-data .
php bin/console doctrine:database:drop -f php bin/console doctrine:database:create || exit 1
php bin/console doctrine:database:create
php bin/console doctrine:schema:create || exit 1 php bin/console doctrine:schema:create || exit 1
php bin/console app:populate_initial_values || exit 1 php bin/console app:populate_initial_values || exit 1

View File

@@ -3,10 +3,8 @@
cd /var/www/social || exit 1 cd /var/www/social || exit 1
printf "Cleaning Redis cache: " && echo "FLUSHALL" | nc redis 6379 printf "Cleaning Redis cache: " && echo "FLUSHALL" | nc redis 6379
bin/console doctrine:database:drop --force || exit 1 yes yes | php bin/console doctrine:fixtures:load || exit 1
bin/console doctrine:database:create || exit 1 php bin/console app:populate_initial_values # since loading fixtures purges the DB
bin/console doctrine:schema:update --force || exit 1
yes yes | bin/console doctrine:fixtures:load || exit 1
if [ "$#" -eq 0 ] || [ -z "$*" ]; then if [ "$#" -eq 0 ] || [ -z "$*" ]; then
vendor/bin/simple-phpunit -vvv --coverage-html .test_coverage_report vendor/bin/simple-phpunit -vvv --coverage-html .test_coverage_report

View File

@@ -34,14 +34,15 @@ namespace Plugin\ActivityPub;
use ActivityPhp\Type; use ActivityPhp\Type;
use ActivityPhp\Type\AbstractObject; use ActivityPhp\Type\AbstractObject;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\HTTPClient; use App\Core\HTTPClient;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Modules\Plugin; use App\Core\Modules\Plugin;
use App\Core\Queue; use App\Core\Queue\Queue;
use App\Core\Router; use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\Note; use App\Entity\Note;
@@ -103,7 +104,7 @@ class ActivityPub extends Plugin
'User-Agent' => 'GNUsocialBot ' . GNUSOCIAL_VERSION . ' - ' . GNUSOCIAL_PROJECT_URL, 'User-Agent' => 'GNUsocialBot ' . GNUSOCIAL_VERSION . ' - ' . GNUSOCIAL_PROJECT_URL,
]; ];
public static function version(): string public function version(): string
{ {
return '3.0.0'; return '3.0.0';
} }
@@ -140,12 +141,14 @@ class ActivityPub extends Plugin
$ap_actor->getActorId(), $ap_actor->getActorId(),
Discovery::normalize($actor->getNickname() . '@' . parse_url($ap_actor->getInboxUri(), \PHP_URL_HOST)), Discovery::normalize($actor->getNickname() . '@' . parse_url($ap_actor->getInboxUri(), \PHP_URL_HOST)),
); );
$already_known_ids = [];
if (!empty($ap_act->_object_mention_ids)) {
$already_known_ids = $ap_act->_object_mention_ids;
}
DB::flush(); DB::flush();
if (($att_targets = $ap_act->getAttentionTargets()) !== []) { if (Event::handle('ActivityPubNewNotification', [$actor, $ap_act->getActivity(), $already_known_ids, _m('{nickname} attentioned you.', ['{nickname}' => $actor->getNickname()])]) === Event::next) {
if (Event::handle('ActivityPubNewNotification', [$actor, ($act = $ap_act->getActivity()), $att_targets, _m('{actor_id} triggered a notification via ActivityPub.', ['{actor_id}' => $actor->getId()])]) === Event::next) { Event::handle('NewNotification', [$actor, $ap_act->getActivity(), $already_known_ids, _m('{nickname} attentioned you.', ['{nickname}' => $actor->getNickname()])]);
Event::handle('NewNotification', [$actor, $act, $att_targets, _m('{actor_id} triggered a notification via ActivityPub.', ['{nickname}' => $actor->getId()])]);
}
} }
return Event::stop; return Event::stop;
@@ -155,9 +158,9 @@ class ActivityPub extends Plugin
* This code executes when GNU social creates the page routing, and we hook * This code executes when GNU social creates the page routing, and we hook
* on this event to add our Inbox and Outbox handler for ActivityPub. * on this event to add our Inbox and Outbox handler for ActivityPub.
* *
* @param Router $r the router that was initialized * @param RouteLoader $r the router that was initialized
*/ */
public function onAddRoute(Router $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
$r->connect( $r->connect(
'activitypub_inbox', 'activitypub_inbox',
@@ -321,24 +324,15 @@ class ActivityPub extends Plugin
string $inbox, string $inbox,
array $to_actors, array $to_actors,
array &$retry_args, array &$retry_args,
): bool { ): bool
{
try { try {
$data = Model::toType($activity); $data = Model::toJson($activity);
if ($sender->isGroup()) { // When the sender is a group, if ($sender->isGroup()) {
if ($activity->getVerb() === 'subscribe') { // When the sender is a group, we have to wrap it in an Announce activity
// Regular postman happens $data = Type::create('Announce', ['object' => $data])->toJson();
} elseif ($activity->getVerb() === 'undo' && $data->get('object')->get('type') === 'Follow') {
// Regular postman happens
} else {
// For every other activity sent by a Group, we have to wrap it in a transient Announce activity
$data = Type::create('Announce', [
'@context' => 'https:\/\/www.w3.org\/ns\/activitystreams',
'actor' => $sender->getUri(type: Router::ABSOLUTE_URL),
'object' => $data,
]);
} }
} $res = self::postman($sender, $data, $inbox);
$res = self::postman($sender, $data->toJson(), $inbox);
// accumulate errors for later use, if needed // accumulate errors for later use, if needed
$status_code = $res->getStatusCode(); $status_code = $res->getStatusCode();
@@ -386,7 +380,6 @@ class ActivityPub extends Plugin
// the actor, that could for example mean that OStatus handled this actor while we were deactivated // the actor, that could for example mean that OStatus handled this actor while we were deactivated
// On next interaction this should be resolved, for now continue // On next interaction this should be resolved, for now continue
if (\is_null($ap_target = DB::findOneBy(ActivitypubActor::class, ['actor_id' => $actor->getId()], return_null: true))) { if (\is_null($ap_target = DB::findOneBy(ActivitypubActor::class, ['actor_id' => $actor->getId()], return_null: true))) {
Log::info('FreeNetwork wrongly told ActivityPub that it can handle actor id: ' . $actor->getId() . ' you might want to keep an eye on it.');
continue; continue;
} }
$to_addr[$ap_target->getInboxSharedUri() ?? $ap_target->getInboxUri()][] = $actor; $to_addr[$ap_target->getInboxSharedUri() ?? $ap_target->getInboxUri()][] = $actor;
@@ -398,7 +391,7 @@ class ActivityPub extends Plugin
foreach ($to_addr as $inbox => $to_actors) { foreach ($to_addr as $inbox => $to_actors) {
Queue::enqueue( Queue::enqueue(
payload: [$sender, $activity, $inbox, $to_actors], payload: [$sender, $activity, $inbox, $to_actors],
queue: 'ActivitypubPostman', queue: 'activitypub_postman',
priority: false, priority: false,
); );
} }
@@ -537,7 +530,7 @@ class ActivityPub extends Plugin
* *
* @return null|Actor|mixed|Note got from URI * @return null|Actor|mixed|Note got from URI
*/ */
public static function getObjectByUri(string $resource, bool $try_online = true): mixed public static function getObjectByUri(string $resource, bool $try_online = true)
{ {
// Try known object // Try known object
$known_object = DB::findOneBy(ActivitypubObject::class, ['object_uri' => $resource], return_null: true); $known_object = DB::findOneBy(ActivitypubObject::class, ['object_uri' => $resource], return_null: true);
@@ -551,6 +544,18 @@ class ActivityPub extends Plugin
return $known_activity->getActivity(); return $known_activity->getActivity();
} }
// Try local Note
if (Common::isValidHttpUrl($resource)) {
$resource_parts = parse_url($resource);
// TODO: Use URLMatcher
if ($resource_parts['host'] === Common::config('site', 'server')) {
$local_note = DB::findOneBy('note', ['url' => $resource], return_null: true);
if (!\is_null($local_note)) {
return $local_note;
}
}
}
// Try Actor // Try Actor
try { try {
return Explorer::getOneFromUri($resource, try_online: false); return Explorer::getOneFromUri($resource, try_online: false);
@@ -558,42 +563,16 @@ class ActivityPub extends Plugin
// Ignore, this is brute forcing, it's okay not to find // Ignore, this is brute forcing, it's okay not to find
} }
// Is it a HTTP URL? // Try remote
if (Common::isValidHttpUrl($resource)) {
$resource_parts = parse_url($resource);
// If it is local
if ($resource_parts['host'] === Common::config('site', 'server')) {
// Try Local Note
$local_note = DB::findOneBy(Note::class, ['url' => $resource], return_null: true);
if (!\is_null($local_note)) {
return $local_note;
}
// Try local Activity
try {
$match = Router::match($resource_parts['path']);
$local_activity = DB::findOneBy(Activity::class, ['id' => $match['id']], return_null: true);
if (!\is_null($local_activity)) {
return $local_activity;
} else {
throw new InvalidArgumentException('Tried to retrieve a non-existent local activity.');
}
} catch (\Exception) {
// Ignore, this is brute forcing, it's okay not to find
}
throw new BugFoundException('ActivityPub failed to retrieve local resource: "' . $resource . '". This is a big issue.');
} else {
// Then it's remote
if (!$try_online) { if (!$try_online) {
throw new Exception("Remote resource {$resource} not found without online resources."); return;
} }
$response = HTTPClient::get($resource, ['headers' => self::HTTP_CLIENT_HEADERS]); $response = HTTPClient::get($resource, ['headers' => self::HTTP_CLIENT_HEADERS]);
// If it was deleted // If it was deleted
if ($response->getStatusCode() == 410) { if ($response->getStatusCode() == 410) {
//$obj = Type::create('Tombstone', ['id' => $resource]); //$obj = Type::create('Tombstone', ['id' => $resource]);
return null; return;
} elseif (!HTTPClient::statusCodeIsOkay($response)) { // If it is unavailable } elseif (!HTTPClient::statusCodeIsOkay($response)) { // If it is unavailable
throw new Exception('Non Ok Status Code for given Object id.'); throw new Exception('Non Ok Status Code for given Object id.');
} else { } else {
@@ -601,7 +580,3 @@ class ActivityPub extends Plugin
} }
} }
} }
return null;
}
}

View File

@@ -33,11 +33,12 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Controller; namespace Plugin\ActivityPub\Controller;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB; use App\Core\DB\DB;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Queue; use App\Core\Queue\Queue;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Actor;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;
use Exception; use Exception;
@@ -163,7 +164,7 @@ class Inbox extends Controller
Queue::enqueue( Queue::enqueue(
payload: [$ap_actor, $actor, $type], payload: [$ap_actor, $actor, $type],
queue: 'ActivitypubInbox', queue: 'activitypub_inbox',
priority: false, priority: false,
); );

View File

@@ -34,7 +34,7 @@ namespace Plugin\ActivityPub\Controller;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Router; use App\Core\Router\Router;
use App\Entity\Activity; use App\Entity\Activity;
use App\Entity\Actor; use App\Entity\Actor;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;

View File

@@ -32,7 +32,7 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Entity; namespace Plugin\ActivityPub\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Entity\Activity; use App\Entity\Activity;
use DateTimeInterface; use DateTimeInterface;
@@ -101,17 +101,27 @@ class ActivitypubActivity extends Entity
public function getActivity(): Activity public function getActivity(): Activity
{ {
return DB::findOneBy(Activity::class, ['id' => $this->getActivityId()]); return DB::findOneBy('activity', ['id' => $this->getActivityId()]);
} }
public function getAttentionTargetIds(): array public array $_object_mention_ids = [];
public function setObjectMentionIds(array $mentions): self
{ {
return $this->getActivity()->getAttentionTargetIds(); $this->_object_mention_ids = $mentions;
return $this;
} }
public function getAttentionTargets(): array /**
* @see Entity->getNotificationTargetIds
*/
public function getNotificationTargetIds(array $ids_already_known = [], ?int $sender_id = null, bool $include_additional = true): array
{ {
return $this->getActivity()->getAttentionTargets(); // Additional actors that should know about this
if (\array_key_exists('additional', $ids_already_known)) {
return $ids_already_known['additional'];
} else {
return $this->_object_mention_ids;
}
} }
public static function schemaDef(): array public static function schemaDef(): array

View File

@@ -33,7 +33,7 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Entity; namespace Plugin\ActivityPub\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;

View File

@@ -32,7 +32,7 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Entity; namespace Plugin\ActivityPub\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;
@@ -115,16 +115,6 @@ class ActivitypubObject extends Entity
return DB::findOneBy($this->getObjectType(), ['id' => $this->getObjectId()]); return DB::findOneBy($this->getObjectType(), ['id' => $this->getObjectId()]);
} }
public function getAttentionTargetIds(): array
{
return $this->getObject()->getAttentionTargetIds();
}
public function getAttentionTargets(): array
{
return $this->getObject()->getAttentionTargets();
}
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [

View File

@@ -32,7 +32,7 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Entity; namespace Plugin\ActivityPub\Entity;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Log; use App\Core\Log;
use App\Entity\Actor; use App\Entity\Actor;

View File

@@ -1,70 +0,0 @@
<?php
declare(strict_types = 1);
namespace Plugin\ActivityPub\Test\Fixtures;
use App\Core\DB;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Plugin\ActivityPub\Util\Model\Activity;
use Plugin\ActivityPub\Util\Model\Actor;
use Plugin\ActivityPub\Util\Model\Note;
class ActivityPubFixtures extends Fixture
{
private static string $fixtures_path = __DIR__ . \DIRECTORY_SEPARATOR;
public static function fixturesPath(string $path, string $ontology = 'gnusocial'): string
{
return self::$fixtures_path . $ontology . \DIRECTORY_SEPARATOR . $path;
}
public function load(ObjectManager $manager)
{
/*
* Beware that it's important to Load Actors, Objects, Activities in this sequence
* because we're running offline tests here.
*/
$ontology = 'gnusocial';
// Load Actors
$person_path = self::fixturesPath('objects/person.jsonld', $ontology);
$person = Actor::fromJson(fread(fopen($person_path, 'r'), filesize($person_path)));
DB::flush();
$another_person_path = self::fixturesPath('objects/another_person.jsonld', $ontology);
$another_person = Actor::fromJson(fread(fopen($another_person_path, 'r'), filesize($another_person_path)));
DB::flush();
$group_path = self::fixturesPath('objects/group.jsonld', $ontology);
$group = Actor::fromJson(fread(fopen($group_path, 'r'), filesize($group_path)));
DB::flush();
// Load Objects
$note_path = self::fixturesPath('objects/note.jsonld', $ontology);
$note = Note::fromJson(fread(fopen($note_path, 'r'), filesize($note_path)));
DB::flush();
$article_path = self::fixturesPath('objects/article.jsonld', $ontology);
$article = Note::fromJson(fread(fopen($article_path, 'r'), filesize($article_path)));
DB::flush();
$reply_path = self::fixturesPath('objects/reply.jsonld', $ontology);
$reply = Note::fromJson(fread(fopen($reply_path, 'r'), filesize($reply_path)));
DB::flush();
$note_with_mention_path = self::fixturesPath('objects/note_with_mention.jsonld', $ontology);
$note_with_mention = Note::fromJson(fread(fopen($note_with_mention_path, 'r'), filesize($note_with_mention_path)));
DB::flush();
// Load Activities
$create_note_path = self::fixturesPath('activities/create_note.jsonld', $ontology);
$create_note = Activity::fromJson(fread(fopen($create_note_path, 'r'), filesize($create_note_path)));
DB::flush();
$create_article_path = self::fixturesPath('activities/create_article.jsonld', $ontology);
$create_article = Activity::fromJson(fread(fopen($create_article_path, 'r'), filesize($create_article_path)));
DB::flush();
$create_reply_path = self::fixturesPath('activities/create_reply.jsonld', $ontology);
$create_reply = Activity::fromJson(fread(fopen($create_reply_path, 'r'), filesize($create_reply_path)));
DB::flush();
$like_note_path = self::fixturesPath('activities/like_note.jsonld', $ontology);
$like_note = Activity::fromJson(fread(fopen($like_note_path, 'r'), filesize($like_note_path)));
DB::flush();
}
}

View File

@@ -1,53 +0,0 @@
{
"type": "Create",
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"gs": "https://www.gnu.org/software/social/ns#"
},
{
"litepub": "http://litepub.social/ns#"
},
{
"chatMessage": "litepub:chatMessage"
},
{
"inConversation": {
"@id": "gs:inConversation",
"@type": "@id"
}
}
],
"id": "https://instance.gnusocial.test/activity/1338",
"published": "2022-03-17T23:30:26+00:00",
"actor": "https://instance.gnusocial.test/actor/42",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://instance.gnusocial.test/actor/21"
],
"object": {
"type": "Article",
"id": "https://instance.gnusocial.test/object/note/1338",
"published": "2022-03-17T23:30:26+00:00",
"attributedTo": "https://instance.gnusocial.test/actor/42",
"name": "hello, world.",
"content": "<p>This is an interesting article.</p>",
"mediaType": "text/html",
"source": {
"content": "This is an interesting article.",
"mediaType": "text/markdown"
},
"attachment": [],
"tag": [],
"inConversation": "https://instance.gnusocial.test/conversation/1338",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://instance.gnusocial.test/actor/21"
]
}
}

View File

@@ -1,53 +0,0 @@
{
"type": "Create",
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"gs": "https://www.gnu.org/software/social/ns#"
},
{
"litepub": "http://litepub.social/ns#"
},
{
"chatMessage": "litepub:chatMessage"
},
{
"inConversation": {
"@id": "gs:inConversation",
"@type": "@id"
}
}
],
"id": "https://instance.gnusocial.test/activity/1339",
"published": "2022-03-01T20:58:48+00:00",
"actor": "https://instance.gnusocial.test/actor/42",
"object": {
"type": "Note",
"id": "https://instance.gnusocial.test/object/note/1339",
"published": "2022-03-01T21:00:16+00:00",
"attributedTo": "https://instance.gnusocial.test/actor/42",
"content": "<p>yay ^^</p>",
"mediaType": "text/html",
"source": {
"content": "yay ^^",
"mediaType": "text/plain"
},
"attachment": [],
"tag": [],
"inReplyTo": "https://instance.gnusocial.test/object/note/1338",
"inConversation": "https://instance.gnusocial.test/conversation/1338",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://instance.gnusocial.test/actor/42/subscribers"
]
},
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://instance.gnusocial.test/actor/42/subscribers"
]
}

View File

@@ -1,16 +0,0 @@
{
"type": "Like",
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"id": "https://another_instance.gnusocial.test/activity/41362",
"published": "2022-03-20T17:54:15+00:00",
"actor": "https://another_instance.gnusocial.test/actor/43",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://instance.gnusocial.test/actor/42"
],
"object": "https://instance.gnusocial.test/object/note/1337"
}

View File

@@ -1,42 +0,0 @@
{
"type": "Person",
"streams": [],
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"gs": "https://www.gnu.org/software/social/ns#"
},
{
"litepub": "http://litepub.social/ns#"
},
{
"chatMessage": "litepub:chatMessage"
},
{
"inConversation": {
"@id": "gs:inConversation",
"@type": "@id"
}
}
],
"id": "https://another_instance.gnusocial.test/actor/43",
"inbox": "https://another_instance.gnusocial.test/actor/43/inbox.json",
"outbox": "https://another_instance.gnusocial.test/actor/43/outbox.json",
"following": "https://another_instance.gnusocial.test/actor/43/subscriptions",
"followers": "https://another_instance.gnusocial.test/actor/43/subscribers",
"liked": "https://another_instance.gnusocial.test/actor/43/favourites",
"preferredUsername": "alice",
"publicKey": {
"id": "https://another_instance.gnusocial.test/actor/43#public-key",
"owner": "https://another_instance.gnusocial.test/actor/43",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArRHP8lxqj1HqFL4q7CKh\noDyBvuhoaoWo/AGdjiWu5AEODL6utaQX+bCJApH9+XNICCkWvayKupgOvLPqBxkh\nl4TPUjlb1iCt+iZeMB8ftude4epaUNUDdqK1zG3g8z8AdF3nx9/cHI+8UY7+JAh6\naZ5EBi+wNYtl4UoDfizmLeRmmGIam5UQ6x2RseYCevIm1BBCZZHLdIaoPJphyjLW\n8sRJtHL4D3m28NkGG8GizctXHbMl7+RlVJ8HyQSr5taRMF+CmZ9ZDFqF2ewc9Pmw\nOMG4o/6m50Q2ELz23O8idjGxKgG7iGdEa3c5cQZTCQ0+2N77L+iS029AV9AKyZMi\nCwIDAQAB\n-----END PUBLIC KEY-----\n"
},
"name": "Alice P. Hacker",
"published": "2022-02-23T17:20:30+00:00",
"updated": "2022-02-25T02:12:48+00:00",
"url": "https://another_instance.gnusocial.test/@alice",
"endpoints": {
"sharedInbox": "https://another_instance.gnusocial.test/inbox.json"
}
}

View File

@@ -1,41 +0,0 @@
{
"type": "Article",
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"gs": "https://www.gnu.org/software/social/ns#"
},
{
"litepub": "http://litepub.social/ns#"
},
{
"chatMessage": "litepub:chatMessage"
},
{
"inConversation": {
"@id": "gs:inConversation",
"@type": "@id"
}
}
],
"id": "https://instance.gnusocial.test/object/note/1338",
"published": "2022-03-17T23:30:26+00:00",
"attributedTo": "https://instance.gnusocial.test/actor/42",
"name": "hello, world.",
"content": "<p>This is an interesting article.</p>",
"mediaType": "text/html",
"source": {
"content": "This is an interesting article.",
"mediaType": "text/markdown"
},
"attachment": [],
"tag": [],
"inConversation": "https://instance.gnusocial.test/conversation/1338",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://instance.gnusocial.test/actor/21"
]
}

View File

@@ -1,50 +0,0 @@
{
"type": "Note",
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"gs": "https://www.gnu.org/software/social/ns#"
},
{
"litepub": "http://litepub.social/ns#"
},
{
"chatMessage": "litepub:chatMessage"
},
{
"inConversation": {
"@id": "gs:inConversation",
"@type": "@id"
}
},
{
"@language": "en"
}
],
"id": "https://instance.gnusocial.test/object/note/1340",
"published": "2022-03-16T21:54:43+00:00",
"attributedTo": "https://instance.gnusocial.test/actor/42",
"content": "<p>This is a public root note with a mention to @<span class=\"h-card\"><a href=\"https://another_instance.gnusocial.test/actor/43\" class=\"h-card u-url p-nickname mention\">alice@another_instance.gnusocial.test</a></span>.</p>",
"mediaType": "text/html",
"source": {
"content": "This is a public root note with a mention to @alice@another_instance.gnusocial.test.",
"mediaType": "text/plain"
},
"attachment": [],
"tag": [
{
"type": "Mention",
"href": "https://another_instance.gnusocial.test/actor/43",
"name": "@alice@another_instance.gnusocial.test"
}
],
"inConversation": "https://instance.gnusocial.test/conversation/1340",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://testv3.gnusocial.rocks/actor/43"
],
"cc": [
"https://testv3.gnusocial.rocks/actor/43"
]
}

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types = 1);
// {{{ 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 Plugin\ActivityPub\Test\Objects;
use App\Util\GNUsocialTestCase;
use Plugin\ActivityPub\Entity\ActivitypubActivity;
use Plugin\ActivityPub\Util\Explorer;
class GSActivityCreatePageTest extends GNUsocialTestCase
{
public function testNoteFromJson()
{
self::bootKernel();
$activity_uri = 'https://instance.gnusocial.test/activity/1338';
$group_uri = 'https://instance.gnusocial.test/actor/21';
$ap_activity = ActivitypubActivity::getByPK(['activity_uri' => $activity_uri]);
$activity = $ap_activity->getActivity();
static::assertSame('create', $activity->getVerb());
static::assertSame('note', $activity->getObjectType());
static::assertSame('ActivityPub', $activity->getSource());
static::assertCount(1, $attT = $ap_activity->getAttentionTargets());
static::assertObjectEquals(Explorer::getOneFromUri($group_uri, try_online: false), $attT[0]);
}
}

View File

@@ -1,50 +0,0 @@
<?php
declare(strict_types = 1);
// {{{ 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 Plugin\ActivityPub\Test\Objects;
use App\Entity\Actor;
use App\Util\GNUsocialTestCase;
use Plugin\ActivityPub\Entity\ActivitypubActor;
use Plugin\ActivityPub\Entity\ActivitypubRsa;
use Plugin\ActivityPub\Util\Explorer;
class GSActorPersonTest extends GNUsocialTestCase
{
public function testPersonFromJson()
{
self::bootKernel();
$person = Explorer::getOneFromUri('https://instance.gnusocial.test/actor/42', try_online: false);
$ap_person = ActivitypubActor::getByPK(['actor_id' => $person->getId()]);
static::assertSame('https://instance.gnusocial.test/actor/42/inbox.json', $ap_person->getInboxUri());
static::assertSame('https://instance.gnusocial.test/inbox.json', $ap_person->getInboxSharedUri());
$person = $ap_person->getActor();
static::assertSame('https://instance.gnusocial.test/actor/42', $person->getUri());
static::assertSame(Actor::PERSON, $person->getType());
static::assertSame('diogo', $person->getNickname());
static::assertSame('Diogo Peralta Cordeiro', $person->getFullname());
$public_key = ActivityPubRsa::getByActor($person)->getPublicKey();
static::assertSame("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArBB+3ldwA2qC1hQTtIho\n9KYhvvMlPdydn8dA6OlyIQ3Jy57ADt2e144jDSY5RQ3esmzWm2QqsI8rAsZsAraO\nl2+855y7Fw35WH4GBc7PJ6MLAEvMk1YWeS/rttXaDzh2i4n/AXkMuxDjS1IBqw2w\nn0qTz2sdGcBJ+mop6AB9Qt2lseBc5IW040jSnfLEDDIaYgoc5m2yRsjGKItOh3BG\njGHDb6JB9FySToSMGIt0/tE5k06wfvAxtkxX5dfGeKtciBpC2MGT169iyMIOM8DN\nFhSl8mowtV1NJQ7nN692USrmNvSJjqe9ugPCDPPvwQ5A6A61Qrgpz5pav/o5Sz69\nzQIDAQAB\n-----END PUBLIC KEY-----\n", $public_key);
}
}

View File

@@ -1,67 +0,0 @@
<?php
declare(strict_types = 1);
// {{{ 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 Plugin\ActivityPub\Test\Objects;
use App\Core\VisibilityScope;
use App\Entity\Note;
use App\Util\GNUsocialTestCase;
use Plugin\ActivityPub\ActivityPub;
use Plugin\ActivityPub\Entity\ActivitypubObject;
use Plugin\ActivityPub\Util\Explorer;
class GSObjectArticleTest extends GNUsocialTestCase
{
public function testNoteFromJson()
{
self::bootKernel();
$actor_uri = 'https://instance.gnusocial.test/actor/42';
$object_uri = 'https://instance.gnusocial.test/object/note/1338';
$group_uri = 'https://instance.gnusocial.test/actor/21';
$article = ActivityPub::getObjectByUri($object_uri, try_online: false);
static::assertInstanceOf(Note::class, $article);
static::assertSame(Explorer::getOneFromUri($actor_uri)->getId(), $article->getActorId());
static::assertSame('text/markdown', $article->getContentType());
static::assertSame('This is an interesting article.', $article->getContent());
static::assertSame('<p>This is an interesting article.</p>', $article->getRendered());
static::assertSame('ActivityPub', $article->getSource());
static::assertNull($article->getReplyTo());
static::assertFalse($article->getIsLocal());
static::assertSame(VisibilityScope::EVERYWHERE, $article->getScope());
static::assertSame($object_uri, $article->getUrl());
static::assertNull($article->getLanguageLocale());
static::assertSame('article', $article->getType());
static::assertSame('hello, world.', $article->getTitle());
$ap_object = ActivitypubObject::getByPK(['object_uri' => $object_uri]);
static::assertSame(Note::schemaName(), $ap_object->getObjectType());
static::assertSame($object_uri, $ap_object->getObjectUri());
static::assertSame($article->getId(), $ap_object->getObjectId());
static::assertCount(1, $attT = $article->getAttentionTargets());
static::assertObjectEquals(Explorer::getOneFromUri($group_uri, try_online: false), $attT[0]);
static::assertSame([], $article->getMentionTargets());
}
}

View File

@@ -1,99 +0,0 @@
<?php
declare(strict_types = 1);
// {{{ 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 Plugin\ActivityPub\Test\Objects;
use App\Core\VisibilityScope;
use App\Entity\Note;
use App\Util\GNUsocialTestCase;
use Plugin\ActivityPub\ActivityPub;
use Plugin\ActivityPub\Entity\ActivitypubObject;
use Plugin\ActivityPub\Util\Explorer;
class GSObjectNoteTest extends GNUsocialTestCase
{
public function testNoteFromJson()
{
self::bootKernel();
$actor_uri = 'https://instance.gnusocial.test/actor/42';
$object_uri = 'https://instance.gnusocial.test/object/note/1337';
$note = ActivityPub::getObjectByUri($object_uri, try_online: false);
static::assertInstanceOf(Note::class, $note);
static::assertSame(Explorer::getOneFromUri($actor_uri)->getId(), $note->getActorId());
static::assertSame('text/plain', $note->getContentType());
static::assertSame('hello, world.', $note->getContent());
static::assertSame('<p>hello, world.</p>', $note->getRendered());
static::assertSame('ActivityPub', $note->getSource());
static::assertNull($note->getReplyTo());
static::assertFalse($note->getIsLocal());
static::assertSame(VisibilityScope::EVERYWHERE, $note->getScope());
static::assertSame($object_uri, $note->getUrl());
static::assertSame('en', $note->getLanguageLocale());
static::assertSame('note', $note->getType());
static::assertNull($note->getTitle());
$ap_object = ActivitypubObject::getByPK(['object_uri' => $object_uri]);
static::assertSame(Note::schemaName(), $ap_object->getObjectType());
static::assertSame($object_uri, $ap_object->getObjectUri());
static::assertSame($note->getId(), $ap_object->getObjectId());
static::assertSame([], $note->getAttentionTargets());
static::assertSame([], $note->getMentionTargets());
}
public function testNoteWithMentionFromJson()
{
self::bootKernel();
$actor_uri = 'https://instance.gnusocial.test/actor/42';
$another_actor_uri = 'https://another_instance.gnusocial.test/actor/43';
$object_uri = 'https://instance.gnusocial.test/object/note/1340';
$note = ActivityPub::getObjectByUri($object_uri, try_online: false);
static::assertInstanceOf(Note::class, $note);
static::assertSame(Explorer::getOneFromUri($actor_uri, try_online: false)->getId(), $note->getActorId());
static::assertSame('text/plain', $note->getContentType());
static::assertSame('This is a public root note with a mention to @alice@another_instance.gnusocial.test.', $note->getContent());
// TODO: implement proper sanitization rules
//static::assertSame('<p>This is a public root note with a mention to @<span class=\"h-card\"><a href=\"https://another_instance.gnusocial.test/actor/43\" class=\"h-card u-url p-nickname mention\">alice@another_instance.gnusocial.test</a></span>.</p>', $note->getRendered());
static::assertSame('ActivityPub', $note->getSource());
static::assertNull($note->getReplyTo());
static::assertFalse($note->getIsLocal());
static::assertSame(VisibilityScope::EVERYWHERE, $note->getScope());
static::assertSame($object_uri, $note->getUrl());
static::assertSame('en', $note->getLanguageLocale());
static::assertSame('note', $note->getType());
static::assertNull($note->getTitle());
$ap_object = ActivitypubObject::getByPK(['object_uri' => $object_uri]);
static::assertSame(Note::schemaName(), $ap_object->getObjectType());
static::assertSame($object_uri, $ap_object->getObjectUri());
static::assertSame($note->getId(), $ap_object->getObjectId());
static::assertCount(1, $attT = $note->getAttentionTargets());
static::assertObjectEquals(Explorer::getOneFromUri($another_actor_uri, try_online: false), $attT[0]);
static::assertSame([], $note->getMentionTargets());
}
}

View File

@@ -32,7 +32,7 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Util; namespace Plugin\ActivityPub\Util;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\HTTPClient; use App\Core\HTTPClient;
use App\Core\Log; use App\Core\Log;
use App\Entity\Actor; use App\Entity\Actor;
@@ -185,6 +185,7 @@ class Explorer
// Try standard ActivityPub route // Try standard ActivityPub route
// Is this a known filthy little mudblood? // Is this a known filthy little mudblood?
$aprofile = DB::findOneBy(ActivitypubActor::class, ['uri' => $uri], return_null: true); $aprofile = DB::findOneBy(ActivitypubActor::class, ['uri' => $uri], return_null: true);
//dd($aprofile, DB::sql('select * from activitypub_actor;'));
if (!\is_null($aprofile)) { if (!\is_null($aprofile)) {
Log::debug('ActivityPub Explorer: Found a known ActivityPub Actor for ' . $uri); Log::debug('ActivityPub Explorer: Found a known ActivityPub Actor for ' . $uri);
$this->discovered_actors[] = $aprofile->getActor(); $this->discovered_actors[] = $aprofile->getActor();

View File

@@ -30,7 +30,6 @@ namespace Plugin\ActivityPub\Util;
use App\Entity\Actor; use App\Entity\Actor;
use DateTime; use DateTime;
use Exception; use Exception;
use Plugin\ActivityPub\ActivityPub;
use Plugin\ActivityPub\Entity\ActivitypubRsa; use Plugin\ActivityPub\Entity\ActivitypubRsa;
class HTTPSignature class HTTPSignature
@@ -92,7 +91,7 @@ class HTTPSignature
'Date' => $date->format('D, d M Y H:i:s \G\M\T'), 'Date' => $date->format('D, d M Y H:i:s \G\M\T'),
'Host' => parse_url($url, \PHP_URL_HOST), 'Host' => parse_url($url, \PHP_URL_HOST),
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, application/json', 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, application/json',
'User-Agent' => 'GNU social ActivityPub Plugin - v' . ActivityPub::version() . ' - ' . GNUSOCIAL_ENGINE_URL, 'User-Agent' => 'GNU social ActivityPub Plugin - ' . GNUSOCIAL_ENGINE_URL,
'Content-Type' => 'application/activity+json', 'Content-Type' => 'application/activity+json',
]; ];

View File

@@ -114,36 +114,24 @@ abstract class Model
*/ */
abstract public static function fromJson(string|Type\AbstractObject $json, array $options = []): Entity; abstract public static function fromJson(string|Type\AbstractObject $json, array $options = []): Entity;
/**
* Get a Type
*
* @throws \App\Util\Exception\ServerException
* @throws ClientException
*/
public static function toType(mixed $object): Type\AbstractObject
{
switch ($object::class) {
case \App\Entity\Activity::class:
return Activity::toType($object);
case \App\Entity\Note::class:
return Note::toType($object);
default:
$type = self::jsonToType($object);
Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]);
return $type;
}
}
/** /**
* Get a JSON * Get a JSON
* *
* @param int $options PHP JSON options * @param ?int $options PHP JSON options
* *
* @throws \App\Util\Exception\ServerException
* @throws ClientException * @throws ClientException
*/ */
public static function toJson(mixed $object, int $options = \JSON_UNESCAPED_SLASHES): string public static function toJson(mixed $object, int $options = \JSON_UNESCAPED_SLASHES): string
{ {
return self::toType($object)->toJson($options); switch ($object::class) {
case \App\Entity\Activity::class:
return Activity::toJson($object, $options);
case \App\Entity\Note::class:
return Note::toJson($object, $options);
default:
$type = self::jsonToType($object);
Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]);
return $type->toJson($options);
}
} }
} }

View File

@@ -34,18 +34,15 @@ namespace Plugin\ActivityPub\Util\Model;
use ActivityPhp\Type; use ActivityPhp\Type;
use ActivityPhp\Type\AbstractObject; use ActivityPhp\Type\AbstractObject;
use App\Core\DB; use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Log; use App\Core\Router\Router;
use App\Core\Router;
use App\Entity\Activity as GSActivity; use App\Entity\Activity as GSActivity;
use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;
use App\Util\Exception\NoSuchActorException; use App\Util\Exception\NoSuchActorException;
use App\Util\Exception\NotFoundException; use App\Util\Exception\NotFoundException;
use App\Util\Exception\NotImplementedException; use App\Util\Exception\NotImplementedException;
use DateTimeInterface; use DateTimeInterface;
use Exception;
use InvalidArgumentException; use InvalidArgumentException;
use Plugin\ActivityPub\ActivityPub; use Plugin\ActivityPub\ActivityPub;
use Plugin\ActivityPub\Entity\ActivitypubActivity; use Plugin\ActivityPub\Entity\ActivitypubActivity;
@@ -90,21 +87,11 @@ class Activity extends Model
// Find Actor and Object // Find Actor and Object
$actor = Explorer::getOneFromUri($type_activity->get('actor')); $actor = Explorer::getOneFromUri($type_activity->get('actor'));
$type_object = $type_activity->get('object'); $type_object = $type_activity->get('object');
if (\is_string($type_object)) { if (\is_string($type_object)) { // Retrieve it
if (Common::isValidHttpUrl($type_object)) { // Retrieve it
$type_object = ActivityPub::getObjectByUri($type_object, try_online: true); $type_object = ActivityPub::getObjectByUri($type_object, try_online: true);
} else { } else { // Encapsulated, if we have it locally, prefer it
$type_object = Type::fromJson($type_object);
}
}
if ($type_object instanceof AbstractObject) { // Encapsulated, if we have it locally, prefer it
// TODO: Test authority of activity over object // TODO: Test authority of activity over object
try { $type_object = ActivityPub::getObjectByUri($type_object->get('id'), try_online: false) ?? $type_object;
$type_object = ActivityPub::getObjectByUri($type_object->get('id'), try_online: false);
} catch (Exception $e) {
// Use the encapsulated then
Log::debug('Failed to find a local activity, will continue with encapsulated.', [$e]);
}
} }
if (($type_object instanceof Type\AbstractObject)) { // It's a new object apparently if (($type_object instanceof Type\AbstractObject)) { // It's a new object apparently
@@ -158,7 +145,7 @@ class Activity extends Model
* *
* @throws ClientException * @throws ClientException
*/ */
public static function toType(mixed $object): AbstractObject public static function toJson(mixed $object, int $options = \JSON_UNESCAPED_SLASHES): string
{ {
if ($object::class !== GSActivity::class) { if ($object::class !== GSActivity::class) {
throw new InvalidArgumentException('First argument type must be an Activity.'); throw new InvalidArgumentException('First argument type must be an Activity.');
@@ -177,26 +164,15 @@ class Activity extends Model
$attr = [ $attr = [
'type' => $gs_verb_to_activity_streams_two_verb, 'type' => $gs_verb_to_activity_streams_two_verb,
'@context' => ActivityPub::$activity_streams_two_context, '@context' => ActivityPub::$activity_streams_two_context,
'instrument' => Type::create('Service', [
'name' => 'GNU social ActivityPub Plugin - v' . ActivityPub::version(),
'url' => GNUSOCIAL_ENGINE_URL,
]),
'id' => Router::url('activity_view', ['id' => $object->getId()], Router::ABSOLUTE_URL), 'id' => Router::url('activity_view', ['id' => $object->getId()], Router::ABSOLUTE_URL),
'published' => $object->getCreated()->format(DateTimeInterface::RFC3339), 'published' => $object->getCreated()->format(DateTimeInterface::RFC3339),
'actor' => $object->getActor()->getUri(Router::ABSOLUTE_URL), 'actor' => $object->getActor()->getUri(Router::ABSOLUTE_URL),
]; ];
$attr['to'] = ['https://www.w3.org/ns/activitystreams#Public'];
$attr['cc'] = [];
foreach ($object->getAttentionTargets() as $target) {
$attr['cc'][] = $target->getUri();
}
// Get object or Tombstone // Get object or Tombstone
try { try {
$child = $object->getObject(); // Throws NotFoundException $object = $object->getObject(); // Throws NotFoundException
$prefer_embed = ['Create', 'Undo']; $attr['object'] = ($attr['type'] === 'Create') ? self::jsonToType(Model::toJson($object)) : ActivityPub::getUriByObject($object);
$attr['object'] = \in_array($attr['type'], $prefer_embed) ? self::jsonToType(Model::toJson($child)) : ActivityPub::getUriByObject($child);
} catch (NotFoundException) { } catch (NotFoundException) {
// It seems this object was deleted, refer to it as a Tombstone // It seems this object was deleted, refer to it as a Tombstone
$uri = match ($object->getObjectType()) { $uri = match ($object->getObjectType()) {
@@ -204,15 +180,25 @@ class Activity extends Model
'actor' => Router::url('actor_view_id', ['id' => $object->getObjectId()], type: Router::ABSOLUTE_URL), 'actor' => Router::url('actor_view_id', ['id' => $object->getObjectId()], type: Router::ABSOLUTE_URL),
default => throw new NotImplementedException(), default => throw new NotImplementedException(),
}; };
$attr['object'] = Type::create('Tombstone', ['id' => $uri]); $attr['object'] = Type::create('Tombstone', [
'id' => $uri,
]);
}
// If embedded non tombstone Object
if (!\is_string($attr['object']) && $attr['object']->get('type') !== 'Tombstone') {
// Little special case
if ($attr['type'] === 'Create' && ($attr['object']->get('type') === 'Note' || $attr['object']->get('type') === 'Page')) {
$attr['to'] = $attr['object']->get('to') ?? [];
$attr['cc'] = $attr['object']->get('cc') ?? [];
}
} }
if (!\is_string($attr['object'])) { if (!\is_string($attr['object'])) {
$attr['@context'] = $attr['object']->get('@context');
$attr['object']->set('@context', null); $attr['object']->set('@context', null);
} }
$type = self::jsonToType($attr); $type = self::jsonToType($attr);
Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]); Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]);
return $type; return $type->toJson($options);
} }
} }

View File

@@ -33,8 +33,6 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Util\Model; namespace Plugin\ActivityPub\Util\Model;
use ActivityPhp\Type\AbstractObject; use ActivityPhp\Type\AbstractObject;
use Exception;
use InvalidArgumentException;
use Plugin\ActivityPub\Entity\ActivitypubActivity; use Plugin\ActivityPub\Entity\ActivitypubActivity;
/** /**
@@ -47,15 +45,27 @@ class ActivityAnnounce extends Activity
{ {
protected static function handle_core_activity(\App\Entity\Actor $actor, AbstractObject $type_activity, mixed $type_object, ?ActivitypubActivity &$ap_act): ActivitypubActivity protected static function handle_core_activity(\App\Entity\Actor $actor, AbstractObject $type_activity, mixed $type_object, ?ActivitypubActivity &$ap_act): ActivitypubActivity
{ {
// The only core Announce we recognise is for (transient) activities coming from Group actors // The only core Announce we recognise is for (transitive) activities coming from Group actors
if ($actor->isGroup()) { if ($actor->isGroup()) {
if ($type_object instanceof AbstractObject) { if ($type_object instanceof AbstractObject) {
return $ap_act = Activity::fromJson($type_object); $actual_to = array_flip(\is_string($type_object->get('to')) ? [$type_object->get('to')] : $type_object->get('to'));
} else { $actual_cc = array_flip(\is_string($type_object->get('cc')) ? [$type_object->get('cc')] : $type_object->get('cc'));
throw new Exception('Already handled.'); $actual_cc[$type_activity->get('actor')] = true; // Add group to targets
} foreach (\is_string($type_activity->get('to')) ? [$type_activity->get('to')] : $type_activity->get('to') as $to) {
} else { if ($to !== 'https://www.w3.org/ns/activitystreams#Public') {
throw new InvalidArgumentException('Unsupported Announce Activity.'); $actual_to[$to] = true;
} }
} }
foreach (\is_string($type_activity->get('cc')) ? [$type_activity->get('cc')] : $type_activity->get('cc') as $cc) {
if ($cc !== 'https://www.w3.org/ns/activitystreams#Public') {
$actual_cc[$cc] = true;
}
}
$type_object->set('to', array_keys($actual_to));
$type_object->set('cc', array_keys($actual_cc));
$ap_act = self::fromJson($type_object);
}
}
return $ap_act ?? ($ap_act = $type_object);
}
} }

View File

@@ -33,7 +33,7 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Util\Model; namespace Plugin\ActivityPub\Util\Model;
use ActivityPhp\Type\AbstractObject; use ActivityPhp\Type\AbstractObject;
use App\Core\DB; use App\Core\DB\DB;
use App\Entity\Activity as GSActivity; use App\Entity\Activity as GSActivity;
use App\Util\Exception\NotImplementedException; use App\Util\Exception\NotImplementedException;
use DateTime; use DateTime;
@@ -94,6 +94,7 @@ class ActivityCreate extends Activity
'modified' => new DateTime(), 'modified' => new DateTime(),
]); ]);
DB::persist($ap_act); DB::persist($ap_act);
$ap_act->setObjectMentionIds($note->_object_mentions_ids);
return $ap_act; return $ap_act;
} }
} }

View File

@@ -33,11 +33,10 @@ declare(strict_types = 1);
namespace Plugin\ActivityPub\Util\Model; namespace Plugin\ActivityPub\Util\Model;
use ActivityPhp\Type\AbstractObject; use ActivityPhp\Type\AbstractObject;
use App\Core\DB; use App\Core\DB\DB;
use App\Entity\Activity as GSActivity; use App\Entity\Activity as GSActivity;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;
use Component\Subscription\Subscription; use Component\Subscription\Subscription;
use Component\Subscription\Subscription as SubscriptionComponent;
use DateTime; use DateTime;
use InvalidArgumentException; use InvalidArgumentException;
use Plugin\ActivityPub\Entity\ActivitypubActivity; use Plugin\ActivityPub\Entity\ActivitypubActivity;
@@ -64,8 +63,6 @@ class ActivityFollow extends Activity
if (\is_null($act)) { if (\is_null($act)) {
throw new ClientException('You are already subscribed to this actor.'); throw new ClientException('You are already subscribed to this actor.');
} }
SubscriptionComponent::refreshSubscriptionCount($actor, $subscribed);
// Store ActivityPub Activity // Store ActivityPub Activity
$ap_act = ActivitypubActivity::create([ $ap_act = ActivitypubActivity::create([
'activity_id' => $act->getId(), 'activity_id' => $act->getId(),

Some files were not shown because too many files have changed in this diff Show More