From 74329a44a057d3a820a3a95c1eacf5c9065c5337 Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Wed, 15 May 2024 12:06:12 +0200 Subject: [PATCH 1/9] Add peertube support To be able to handle the activities peertube sends these things needed to be added: - extract the target magazine / group from the `attributedTo` property. It is an array with both the user and the channel (group) in it - handle array as `url` property - add `Video` as a supported entry type --- .../ActivityPub/Inbox/ActivityHandler.php | 5 ++- .../ActivityPub/Inbox/CreateHandler.php | 14 ++---- src/Service/ActivityPub/Page.php | 5 ++- src/Service/ActivityPubManager.php | 43 ++++++++++++++++++- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php b/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php index 94e90e4cd..c42bd8f19 100644 --- a/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/ActivityHandler.php @@ -63,10 +63,10 @@ public function __invoke(ActivityMessage $message): void try { if (isset($payload['actor']) || isset($payload['attributedTo'])) { - if (!$this->verifyInstanceDomain($payload['actor'] ?? $payload['attributedTo'])) { + if (!$this->verifyInstanceDomain($payload['actor'] ?? $this->manager->getActorFromAttributedTo($payload['attributedTo']))) { return; } - $user = $this->manager->findActorOrCreate($payload['actor'] ?? $payload['attributedTo']); + $user = $this->manager->findActorOrCreate($payload['actor'] ?? $this->manager->getActorFromAttributedTo($payload['attributedTo'])); } else { if (!$this->verifyInstanceDomain($payload['id'])) { return; @@ -125,6 +125,7 @@ private function handle(?array $payload) case 'Page': case 'Article': case 'Question': + case 'Video': $this->bus->dispatch(new CreateMessage($payload)); // no break case 'Announce': diff --git a/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php b/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php index 5f3038329..61a6b1844 100644 --- a/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php @@ -41,23 +41,17 @@ public function __invoke(CreateMessage $message): void { $this->object = $message->payload; $this->logger->debug('Got a CreateMessage of type {t}', [$message->payload['type'], $message->payload]); + $entryTypes = ['Page', 'Article', 'Video']; + $postTypes = ['Question', 'Note']; try { - if ('Note' === $this->object['type']) { + if (in_array($this->object['type'], $postTypes)) { $this->handleChain(); } - if ('Page' === $this->object['type']) { + if (in_array($this->object['type'], $entryTypes)) { $this->handlePage(); } - - if ('Article' === $this->object['type']) { - $this->handlePage(); - } - - if ('Question' === $this->object['type']) { - $this->handleChain(); - } } catch (UserBannedException) { $this->logger->info('Did not create the post, because the user is banned'); } catch (TagBannedException) { diff --git a/src/Service/ActivityPub/Page.php b/src/Service/ActivityPub/Page.php index e0c837ab1..b7cc66328 100644 --- a/src/Service/ActivityPub/Page.php +++ b/src/Service/ActivityPub/Page.php @@ -44,7 +44,8 @@ public function __construct( */ public function create(array $object): Entry { - $actor = $this->activityPubManager->findActorOrCreate($object['attributedTo']); + $actorUrl = $this->activityPubManager->getActorFromAttributedTo($object['attributedTo']); + $actor = $this->activityPubManager->findActorOrCreate($actorUrl); if (!empty($actor)) { if ($actor->isBanned) { throw new UserBannedException(); @@ -153,7 +154,7 @@ private function handleUrl(EntryDto $dto, ?array $object): void } if (!$dto->url && isset($object['url'])) { - $dto->url = $object['url']; + $dto->url = $this->activityPubManager->extractUrl($object['url']); } } diff --git a/src/Service/ActivityPubManager.php b/src/Service/ActivityPubManager.php index 91bc49f56..d787b586f 100644 --- a/src/Service/ActivityPubManager.php +++ b/src/Service/ActivityPubManager.php @@ -462,7 +462,7 @@ public function updateMagazine(string $actorUrl): ?Magazine $magazine->apInboxUrl = $actor['endpoints']['sharedInbox'] ?? $actor['inbox']; $magazine->apDomain = parse_url($actor['id'], PHP_URL_HOST); $magazine->apFollowersUrl = $actor['followers'] ?? null; - $magazine->apAttributedToUrl = $actor['attributedTo'] ?? null; + $magazine->apAttributedToUrl = $this->getActorFromAttributedTo($actor['attributedTo'] ?? null, filterForPerson: false); $magazine->apPreferredUsername = $actor['preferredUsername'] ?? null; $magazine->apDiscoverable = $actor['discoverable'] ?? true; $magazine->apPublicUrl = $actor['url'] ?? $actorUrl; @@ -711,6 +711,11 @@ public static function getReceivers(array $object): array } elseif (isset($object['object']['cc']) and \is_string($object['object']['cc'])) { $res[] = $object['object']['cc']; } + } else if (isset($object['attributedTo']) && \is_array($object['attributedTo'])) { + // if there is no "object" inside of this it will probably be a create activity which has an attributedTo field + // this was implemented for peertube support, because they list the channel (Group) and the user in an array in that field + $groups = array_filter($object['attributedTo'], fn ($item) => \is_array($item) && !empty($item['type']) && 'Group' === $item['type']); + $res = array_merge($res, array_map(fn ($item) => $item['id'], $groups)); } $res = array_filter($res, fn ($i) => null !== $i and ActivityPubActivityInterface::PUBLIC_URL !== $i); @@ -800,4 +805,40 @@ public function extractMarkdownSummary(array $apObject): ?string return stripslashes($converter->convert($apObject['summary'])); } } + + public function getActorFromAttributedTo(string|array|null $attributedTo, bool $filterForPerson = true): ?string + { + if (\is_string($attributedTo)) { + return $attributedTo; + } else if (\is_array($attributedTo)) { + $actors = \array_filter($attributedTo, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && (!$filterForPerson || 'Person' === $item['type']))); + if (\sizeof($actors) >= 1) { + if (\is_string($actors[0])) { + return $actors[0]; + } else if (!empty($actors[0]['id'])) { + return $actors[0]['id']; + } + } + } + + return null; + } + + public function extractUrl(string|array|null $url): ?string + { + if (\is_string($url)) { + return $url; + } else if (\is_array($url)) { + $urls = \array_filter($url, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && 'Link' === $item['type'] && (empty($item['mediaType']) || "text/html" === $item['mediaType']))); + if (\sizeof($urls) >= 1) { + if (\is_string($urls[0])) { + return $urls[0]; + } else if (!empty($urls[0]['href'])) { + return $urls[0]['href']; + } + } + } + + return null; + } } From b89293d880b8f4cac45bd2f2900f0e80e7160bdc Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Wed, 15 May 2024 12:17:27 +0200 Subject: [PATCH 2/9] Fix linter --- .../ActivityPub/Inbox/CreateHandler.php | 4 ++-- src/Service/ActivityPubManager.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php b/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php index 61a6b1844..bca000ea8 100644 --- a/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/CreateHandler.php @@ -45,11 +45,11 @@ public function __invoke(CreateMessage $message): void $postTypes = ['Question', 'Note']; try { - if (in_array($this->object['type'], $postTypes)) { + if (\in_array($this->object['type'], $postTypes)) { $this->handleChain(); } - if (in_array($this->object['type'], $entryTypes)) { + if (\in_array($this->object['type'], $entryTypes)) { $this->handlePage(); } } catch (UserBannedException) { diff --git a/src/Service/ActivityPubManager.php b/src/Service/ActivityPubManager.php index d787b586f..247f1f677 100644 --- a/src/Service/ActivityPubManager.php +++ b/src/Service/ActivityPubManager.php @@ -694,33 +694,33 @@ public static function getReceivers(array $object): array } if (isset($object['cc']) and \is_array($object['cc'])) { - $res = array_merge($res, $object['cc']); + $res = \array_merge($res, $object['cc']); } elseif (isset($object['cc']) and \is_string($object['cc'])) { $res[] = $object['cc']; } if (isset($object['object']) and \is_array($object['object'])) { if (isset($object['object']['to']) and \is_array($object['object']['to'])) { - $res = array_merge($res, $object['object']['to']); + $res = \array_merge($res, $object['object']['to']); } elseif (isset($object['object']['to']) and \is_string($object['object']['to'])) { $res[] = $object['object']['to']; } if (isset($object['object']['cc']) and \is_array($object['object']['cc'])) { - $res = array_merge($res, $object['object']['cc']); + $res = \array_merge($res, $object['object']['cc']); } elseif (isset($object['object']['cc']) and \is_string($object['object']['cc'])) { $res[] = $object['object']['cc']; } } else if (isset($object['attributedTo']) && \is_array($object['attributedTo'])) { // if there is no "object" inside of this it will probably be a create activity which has an attributedTo field // this was implemented for peertube support, because they list the channel (Group) and the user in an array in that field - $groups = array_filter($object['attributedTo'], fn ($item) => \is_array($item) && !empty($item['type']) && 'Group' === $item['type']); - $res = array_merge($res, array_map(fn ($item) => $item['id'], $groups)); + $groups = \array_filter($object['attributedTo'], fn ($item) => \is_array($item) && !empty($item['type']) && 'Group' === $item['type']); + $res = \array_merge($res, array_map(fn ($item) => $item['id'], $groups)); } - $res = array_filter($res, fn ($i) => null !== $i and ActivityPubActivityInterface::PUBLIC_URL !== $i); + $res = \array_filter($res, fn ($i) => null !== $i and ActivityPubActivityInterface::PUBLIC_URL !== $i); - return array_unique($res); + return \array_unique($res); } private function isImageAttachment(array $object): bool @@ -828,8 +828,8 @@ public function extractUrl(string|array|null $url): ?string { if (\is_string($url)) { return $url; - } else if (\is_array($url)) { - $urls = \array_filter($url, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && 'Link' === $item['type'] && (empty($item['mediaType']) || "text/html" === $item['mediaType']))); + } elseif (\is_array($url)) { + $urls = \array_filter($url, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && 'Link' === $item['type'] && (empty($item['mediaType']) || 'text/html' === $item['mediaType']))); if (\sizeof($urls) >= 1) { if (\is_string($urls[0])) { return $urls[0]; From 3b7c0c538586ddcbaddef0504144d4cf99fa532e Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Wed, 15 May 2024 12:20:23 +0200 Subject: [PATCH 3/9] Fix linter II --- src/Service/ActivityPubManager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/ActivityPubManager.php b/src/Service/ActivityPubManager.php index 247f1f677..608a63153 100644 --- a/src/Service/ActivityPubManager.php +++ b/src/Service/ActivityPubManager.php @@ -711,11 +711,11 @@ public static function getReceivers(array $object): array } elseif (isset($object['object']['cc']) and \is_string($object['object']['cc'])) { $res[] = $object['object']['cc']; } - } else if (isset($object['attributedTo']) && \is_array($object['attributedTo'])) { + } elseif (isset($object['attributedTo']) && \is_array($object['attributedTo'])) { // if there is no "object" inside of this it will probably be a create activity which has an attributedTo field // this was implemented for peertube support, because they list the channel (Group) and the user in an array in that field $groups = \array_filter($object['attributedTo'], fn ($item) => \is_array($item) && !empty($item['type']) && 'Group' === $item['type']); - $res = \array_merge($res, array_map(fn ($item) => $item['id'], $groups)); + $res = \array_merge($res, \array_map(fn ($item) => $item['id'], $groups)); } $res = \array_filter($res, fn ($i) => null !== $i and ActivityPubActivityInterface::PUBLIC_URL !== $i); From ee762c03cefde79d7ab38a52d3939b38946c018c Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Wed, 15 May 2024 12:26:12 +0200 Subject: [PATCH 4/9] Fix linter III --- src/Service/ActivityPubManager.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Service/ActivityPubManager.php b/src/Service/ActivityPubManager.php index 608a63153..c45998396 100644 --- a/src/Service/ActivityPubManager.php +++ b/src/Service/ActivityPubManager.php @@ -694,33 +694,33 @@ public static function getReceivers(array $object): array } if (isset($object['cc']) and \is_array($object['cc'])) { - $res = \array_merge($res, $object['cc']); + $res = array_merge($res, $object['cc']); } elseif (isset($object['cc']) and \is_string($object['cc'])) { $res[] = $object['cc']; } if (isset($object['object']) and \is_array($object['object'])) { if (isset($object['object']['to']) and \is_array($object['object']['to'])) { - $res = \array_merge($res, $object['object']['to']); + $res = array_merge($res, $object['object']['to']); } elseif (isset($object['object']['to']) and \is_string($object['object']['to'])) { $res[] = $object['object']['to']; } if (isset($object['object']['cc']) and \is_array($object['object']['cc'])) { - $res = \array_merge($res, $object['object']['cc']); + $res = array_merge($res, $object['object']['cc']); } elseif (isset($object['object']['cc']) and \is_string($object['object']['cc'])) { $res[] = $object['object']['cc']; } } elseif (isset($object['attributedTo']) && \is_array($object['attributedTo'])) { // if there is no "object" inside of this it will probably be a create activity which has an attributedTo field // this was implemented for peertube support, because they list the channel (Group) and the user in an array in that field - $groups = \array_filter($object['attributedTo'], fn ($item) => \is_array($item) && !empty($item['type']) && 'Group' === $item['type']); - $res = \array_merge($res, \array_map(fn ($item) => $item['id'], $groups)); + $groups = array_filter($object['attributedTo'], fn ($item) => \is_array($item) && !empty($item['type']) && 'Group' === $item['type']); + $res = array_merge($res, array_map(fn ($item) => $item['id'], $groups)); } - $res = \array_filter($res, fn ($i) => null !== $i and ActivityPubActivityInterface::PUBLIC_URL !== $i); + $res = array_filter($res, fn ($i) => null !== $i and ActivityPubActivityInterface::PUBLIC_URL !== $i); - return \array_unique($res); + return array_unique($res); } private function isImageAttachment(array $object): bool @@ -810,12 +810,12 @@ public function getActorFromAttributedTo(string|array|null $attributedTo, bool $ { if (\is_string($attributedTo)) { return $attributedTo; - } else if (\is_array($attributedTo)) { - $actors = \array_filter($attributedTo, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && (!$filterForPerson || 'Person' === $item['type']))); + } elseif (\is_array($attributedTo)) { + $actors = array_filter($attributedTo, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && (!$filterForPerson || 'Person' === $item['type']))); if (\sizeof($actors) >= 1) { if (\is_string($actors[0])) { return $actors[0]; - } else if (!empty($actors[0]['id'])) { + } elseif (!empty($actors[0]['id'])) { return $actors[0]['id']; } } @@ -829,11 +829,11 @@ public function extractUrl(string|array|null $url): ?string if (\is_string($url)) { return $url; } elseif (\is_array($url)) { - $urls = \array_filter($url, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && 'Link' === $item['type'] && (empty($item['mediaType']) || 'text/html' === $item['mediaType']))); + $urls = array_filter($url, fn ($item) => \is_string($item) || (\is_array($item) && !empty($item['type']) && 'Link' === $item['type'] && (empty($item['mediaType']) || 'text/html' === $item['mediaType']))); if (\sizeof($urls) >= 1) { if (\is_string($urls[0])) { return $urls[0]; - } else if (!empty($urls[0]['href'])) { + } elseif (!empty($urls[0]['href'])) { return $urls[0]['href']; } } From 634299c20be7e998c8c11aff336ed2a6aa715d87 Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Thu, 16 May 2024 10:59:46 +0200 Subject: [PATCH 5/9] Add Video as supported type in `ChainActivityHandler` --- src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php b/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php index 13eca0413..849dc1cb2 100644 --- a/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php @@ -41,7 +41,7 @@ public function __invoke(ChainActivityMessage $message): void if (!$message->chain || 0 === \sizeof($message->chain)) { return; } - $validObjectTypes = ['Page', 'Note', 'Article', 'Question']; + $validObjectTypes = ['Page', 'Note', 'Article', 'Question', 'Video']; $object = $message->chain[0]; if (!\in_array($object['type'], $validObjectTypes)) { $this->logger->error('cannot get the dependencies of the object, its type {t} is not one we can handle. {m]', ['t' => $object['type'], 'm' => $message]); From b36b095cced0fbc6cb6c3ca0260e121550e438ce Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Thu, 16 May 2024 11:05:40 +0200 Subject: [PATCH 6/9] Make `ChainActivityHandler` create a page for `Video` types --- src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php b/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php index 849dc1cb2..ceed0d8ae 100644 --- a/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/ChainActivityHandler.php @@ -107,6 +107,7 @@ private function retrieveObject(string $apUrl): Entry|EntryComment|Post|PostComm return $this->note->create($object); case 'Page': case 'Article': + case 'Video': $this->logger->debug('creating page {o}', ['o' => $object]); return $this->page->create($object); From df366642fd33aaeeea0307caf1d351be4f747953 Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Fri, 14 Jun 2024 18:13:00 +0200 Subject: [PATCH 7/9] Introduce `ap_like_count`, `ap_dislike_count` and `ap_share_count` The fields are used when an `Entry`|`EntryComment`|`Post`|`PostComment` is updated and the updated object contains a `likes`|`dislikes`|`shares` field which is an url pointing an object with a field `totalItems`. If the `ap_*` field is not null than this values is used as the displayed counter. When voting these fields are adjusted when a local user votes --- migrations/Version20240614120443.php | 48 +++++++++++++++ src/DTO/EntryCommentDto.php | 3 + src/DTO/EntryDto.php | 3 + src/DTO/PostCommentDto.php | 3 + src/DTO/PostDto.php | 3 + .../Traits/ActivityPubActivityTrait.php | 9 +++ src/Factory/EntryCommentFactory.php | 4 ++ src/Factory/EntryFactory.php | 3 + src/Factory/PostCommentFactory.php | 4 ++ src/Factory/PostFactory.php | 3 + .../ActivityPub/Inbox/UpdateHandler.php | 59 ++++++++----------- src/Service/ActivityPub/ApHttpClient.php | 2 +- src/Service/ActivityPub/Note.php | 28 ++++----- src/Service/ActivityPub/Page.php | 9 ++- src/Service/ActivityPubManager.php | 42 +++++++++++++ src/Service/EntryCommentManager.php | 7 +++ src/Service/EntryManager.php | 7 +++ src/Service/FavouriteManager.php | 27 +++++++++ src/Service/PostCommentManager.php | 7 +++ src/Service/PostManager.php | 7 +++ src/Service/VoteManager.php | 42 +++++++++++++ templates/components/boost.html.twig | 6 +- templates/components/vote.html.twig | 4 +- 23 files changed, 270 insertions(+), 60 deletions(-) create mode 100644 migrations/Version20240614120443.php diff --git a/migrations/Version20240614120443.php b/migrations/Version20240614120443.php new file mode 100644 index 000000000..1e5e850cc --- /dev/null +++ b/migrations/Version20240614120443.php @@ -0,0 +1,48 @@ +addSql('ALTER TABLE entry ADD ap_like_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE entry ADD ap_dislike_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE entry ADD ap_share_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE entry_comment ADD ap_like_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE entry_comment ADD ap_dislike_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE entry_comment ADD ap_share_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE post ADD ap_like_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE post ADD ap_dislike_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE post ADD ap_share_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE post_comment ADD ap_like_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE post_comment ADD ap_dislike_count INT DEFAULT NULL'); + $this->addSql('ALTER TABLE post_comment ADD ap_share_count INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE entry_comment DROP ap_like_count'); + $this->addSql('ALTER TABLE entry_comment DROP ap_dislike_count'); + $this->addSql('ALTER TABLE entry_comment DROP ap_share_count'); + $this->addSql('ALTER TABLE post_comment DROP ap_like_count'); + $this->addSql('ALTER TABLE post_comment DROP ap_dislike_count'); + $this->addSql('ALTER TABLE post_comment DROP ap_share_count'); + $this->addSql('ALTER TABLE post DROP ap_like_count'); + $this->addSql('ALTER TABLE post DROP ap_dislike_count'); + $this->addSql('ALTER TABLE post DROP ap_share_count'); + $this->addSql('ALTER TABLE entry DROP ap_like_count'); + $this->addSql('ALTER TABLE entry DROP ap_dislike_count'); + $this->addSql('ALTER TABLE entry DROP ap_share_count'); + } +} diff --git a/src/DTO/EntryCommentDto.php b/src/DTO/EntryCommentDto.php index 787fec59d..e21cc9b9c 100644 --- a/src/DTO/EntryCommentDto.php +++ b/src/DTO/EntryCommentDto.php @@ -38,6 +38,9 @@ class EntryCommentDto public ?string $visibility = VisibilityInterface::VISIBILITY_VISIBLE; public ?string $ip = null; public ?string $apId = null; + public ?int $apLikeCount = null; + public ?int $apDislikeCount = null; + public ?int $apShareCount = null; public ?array $mentions = null; public ?\DateTimeImmutable $createdAt = null; public ?\DateTimeImmutable $editedAt = null; diff --git a/src/DTO/EntryDto.php b/src/DTO/EntryDto.php index 9c2d4197b..fb8eff5cc 100644 --- a/src/DTO/EntryDto.php +++ b/src/DTO/EntryDto.php @@ -47,6 +47,9 @@ class EntryDto implements ContentVisibilityInterface public ?string $visibility = VisibilityInterface::VISIBILITY_VISIBLE; public ?string $ip = null; public ?string $apId = null; + public ?int $apLikeCount = null; + public ?int $apDislikeCount = null; + public ?int $apShareCount = null; public ?array $tags = null; public ?\DateTimeImmutable $createdAt = null; public ?\DateTimeImmutable $editedAt = null; diff --git a/src/DTO/PostCommentDto.php b/src/DTO/PostCommentDto.php index 9061e4496..0d8073123 100644 --- a/src/DTO/PostCommentDto.php +++ b/src/DTO/PostCommentDto.php @@ -38,6 +38,9 @@ class PostCommentDto implements ContentVisibilityInterface public ?string $visibility = VisibilityInterface::VISIBILITY_VISIBLE; public ?string $ip = null; public ?string $apId = null; + public ?int $apLikeCount = null; + public ?int $apDislikeCount = null; + public ?int $apShareCount = null; public ?array $mentions = null; public ?\DateTimeImmutable $createdAt = null; public ?\DateTimeImmutable $editedAt = null; diff --git a/src/DTO/PostDto.php b/src/DTO/PostDto.php index 439541507..efdb60f6c 100644 --- a/src/DTO/PostDto.php +++ b/src/DTO/PostDto.php @@ -38,6 +38,9 @@ class PostDto implements ContentVisibilityInterface public ?string $ip = null; public ?array $mentions = null; public ?string $apId = null; + public ?int $apLikeCount = null; + public ?int $apDislikeCount = null; + public ?int $apShareCount = null; public ?\DateTimeImmutable $createdAt = null; public ?\DateTimeImmutable $editedAt = null; public ?\DateTime $lastActive = null; diff --git a/src/Entity/Traits/ActivityPubActivityTrait.php b/src/Entity/Traits/ActivityPubActivityTrait.php index 8d84ba77b..fc88a8535 100644 --- a/src/Entity/Traits/ActivityPubActivityTrait.php +++ b/src/Entity/Traits/ActivityPubActivityTrait.php @@ -10,4 +10,13 @@ trait ActivityPubActivityTrait { #[Column(type: 'string', unique: true, nullable: true)] public ?string $apId = null; + + #[Column(type: 'integer', nullable: true)] + public ?int $apLikeCount = null; + + #[Column(type: 'integer', nullable: true)] + public ?int $apDislikeCount = null; + + #[Column(type: 'integer', nullable: true)] + public ?int $apShareCount = null; } diff --git a/src/Factory/EntryCommentFactory.php b/src/Factory/EntryCommentFactory.php index c8d7a66fb..79b0a0add 100644 --- a/src/Factory/EntryCommentFactory.php +++ b/src/Factory/EntryCommentFactory.php @@ -101,6 +101,10 @@ public function createDto(EntryComment $comment): EntryCommentDto $dto->createdAt = $comment->createdAt; $dto->editedAt = $comment->editedAt; $dto->lastActive = $comment->lastActive; + $dto->apId = $comment->apId; + $dto->apLikeCount = $comment->apLikeCount; + $dto->apDislikeCount = $comment->apDislikeCount; + $dto->apShareCount = $comment->apShareCount; $dto->setId($comment->getId()); $currentUser = $this->security->getUser(); diff --git a/src/Factory/EntryFactory.php b/src/Factory/EntryFactory.php index b88e9dc8a..b6c1ae1c6 100644 --- a/src/Factory/EntryFactory.php +++ b/src/Factory/EntryFactory.php @@ -104,6 +104,9 @@ public function createDto(Entry $entry): EntryDto $dto->isPinned = $entry->sticky; $dto->type = $entry->type; $dto->apId = $entry->apId; + $dto->apLikeCount = $entry->apLikeCount; + $dto->apDislikeCount = $entry->apDislikeCount; + $dto->apShareCount = $entry->apShareCount; $dto->tags = $this->tagLinkRepository->getTagsOfEntry($entry); $currentUser = $this->security->getUser(); diff --git a/src/Factory/PostCommentFactory.php b/src/Factory/PostCommentFactory.php index 81bc0bc8b..c58cc38bb 100644 --- a/src/Factory/PostCommentFactory.php +++ b/src/Factory/PostCommentFactory.php @@ -103,6 +103,10 @@ public function createDto(PostComment $comment): PostCommentDto $dto->setId($comment->getId()); $dto->parent = $comment->parent; $dto->mentions = $comment->mentions; + $dto->apId = $comment->apId; + $dto->apLikeCount = $comment->apLikeCount; + $dto->apDislikeCount = $comment->apDislikeCount; + $dto->apShareCount = $comment->apShareCount; $currentUser = $this->security->getUser(); // Only return the user's vote if permission to control voting has been given diff --git a/src/Factory/PostFactory.php b/src/Factory/PostFactory.php index 6b189c8f5..894853333 100644 --- a/src/Factory/PostFactory.php +++ b/src/Factory/PostFactory.php @@ -82,6 +82,9 @@ public function createDto(Post $post): PostDto $dto->ip = $post->ip; $dto->mentions = $post->mentions; $dto->apId = $post->apId; + $dto->apLikeCount = $post->apLikeCount; + $dto->apDislikeCount = $post->apDislikeCount; + $dto->apShareCount = $post->apShareCount; $dto->setId($post->getId()); $currentUser = $this->security->getUser(); diff --git a/src/MessageHandler/ActivityPub/Inbox/UpdateHandler.php b/src/MessageHandler/ActivityPub/Inbox/UpdateHandler.php index 0c251ddf0..54d9c1995 100644 --- a/src/MessageHandler/ActivityPub/Inbox/UpdateHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/UpdateHandler.php @@ -4,6 +4,10 @@ namespace App\MessageHandler\ActivityPub\Inbox; +use App\DTO\EntryCommentDto; +use App\DTO\EntryDto; +use App\DTO\PostCommentDto; +use App\DTO\PostDto; use App\Entity\Entry; use App\Entity\EntryComment; use App\Entity\Post; @@ -16,7 +20,6 @@ use App\Message\ActivityPub\Inbox\UpdateMessage; use App\Repository\ApActivityRepository; use App\Service\ActivityPub\ApObjectExtractor; -use App\Service\ActivityPub\MarkdownConverter; use App\Service\ActivityPubManager; use App\Service\EntryCommentManager; use App\Service\EntryManager; @@ -24,7 +27,6 @@ use App\Service\PostManager; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Attribute\AsMessageHandler; -use Symfony\Component\Messenger\MessageBusInterface; #[AsMessageHandler] class UpdateHandler @@ -39,12 +41,10 @@ public function __construct( private readonly EntryCommentManager $entryCommentManager, private readonly PostManager $postManager, private readonly PostCommentManager $postCommentManager, - private readonly MarkdownConverter $markdownConverter, private readonly EntryFactory $entryFactory, private readonly EntryCommentFactory $entryCommentFactory, private readonly PostFactory $postFactory, private readonly PostCommentFactory $postCommentFactory, - private readonly MessageBusInterface $bus, private readonly ApObjectExtractor $objectExtractor, ) { } @@ -68,23 +68,19 @@ public function __invoke(UpdateMessage $message): void $object = $this->entityManager->getRepository($object['type'])->find((int) $object['id']); if (Entry::class === \get_class($object)) { - $fn = 'editEntry'; + $this->editEntry($object, $actor); } if (EntryComment::class === \get_class($object)) { - $fn = 'editEntryComment'; + $this->editEntryComment($object, $actor); } if (Post::class === \get_class($object)) { - $fn = 'editPost'; + $this->editPost($object, $actor); } if (PostComment::class === \get_class($object)) { - $fn = 'editPostComment'; - } - - if (isset($fn, $object, $actor)) { - $this->$fn($object, $actor); + $this->editPostComment($object, $actor); } // Dead-code introduced by Ernest "Temp disable handler dispatch", in commit: @@ -101,57 +97,52 @@ public function __invoke(UpdateMessage $message): void // } } - private function editEntry(Entry $entry, User $user) + private function editEntry(Entry $entry, User $user): void { $dto = $this->entryFactory->createDto($entry); $dto->title = $this->payload['object']['name']; - if (!empty($this->payload['object']['content'])) { - $dto->body = $this->objectExtractor->getMarkdownBody($this->payload['object']); - } else { - $dto->body = null; - } - + $this->extractChanges($dto); $this->entryManager->edit($entry, $dto); } - private function editEntryComment(EntryComment $comment, User $user) + private function editEntryComment(EntryComment $comment, User $user): void { $dto = $this->entryCommentFactory->createDto($comment); - if (!empty($this->payload['object']['content'])) { - $dto->body = $this->objectExtractor->getMarkdownBody($this->payload['object']); - } else { - $dto->body = null; - } + $this->extractChanges($dto); $this->entryCommentManager->edit($comment, $dto); } - private function editPost(Post $post, User $user) + private function editPost(Post $post, User $user): void { $dto = $this->postFactory->createDto($post); - if (!empty($this->payload['object']['content'])) { - $dto->body = $this->objectExtractor->getMarkdownBody($this->payload['object']); - } else { - $dto->body = null; - } + $this->extractChanges($dto); $this->postManager->edit($post, $dto); } - private function editPostComment(PostComment $comment, User $user) + private function editPostComment(PostComment $comment, User $user): void { $dto = $this->postCommentFactory->createDto($comment); + $this->extractChanges($dto); + + $this->postCommentManager->edit($comment, $dto); + } + + private function extractChanges(EntryDto|EntryCommentDto|PostDto|PostCommentDto $dto): void + { if (!empty($this->payload['object']['content'])) { $dto->body = $this->objectExtractor->getMarkdownBody($this->payload['object']); } else { $dto->body = null; } - - $this->postCommentManager->edit($comment, $dto); + $dto->apLikeCount = $this->activityPubManager->extractRemoteLikeCount($this->payload['object']); + $dto->apDislikeCount = $this->activityPubManager->extractRemoteDislikeCount($this->payload['object']); + $dto->apShareCount = $this->activityPubManager->extractRemoteShareCount($this->payload['object']); } } diff --git a/src/Service/ActivityPub/ApHttpClient.php b/src/Service/ActivityPub/ApHttpClient.php index eb08cb6d2..f0945d73f 100644 --- a/src/Service/ActivityPub/ApHttpClient.php +++ b/src/Service/ActivityPub/ApHttpClient.php @@ -213,7 +213,7 @@ function (ItemInterface $item) use ($apProfileId) { /** * @throws InvalidArgumentException */ - public function getCollectionObject(string $apAddress) + public function getCollectionObject(string $apAddress): ?array { $resp = $this->cache->get( 'ap_collection'.hash('sha256', $apAddress), diff --git a/src/Service/ActivityPub/Note.php b/src/Service/ActivityPub/Note.php index af7e973d5..31666bd2a 100644 --- a/src/Service/ActivityPub/Note.php +++ b/src/Service/ActivityPub/Note.php @@ -140,11 +140,11 @@ private function createEntryComment(array $object, ActivityPubActivityInterface $dto->lang = $this->settingsManager->get('KBIN_DEFAULT_LANG'); } - return $this->entryCommentManager->create( - $dto, - $actor, - false - ); + $dto->apLikeCount = $this->activityPubManager->extractRemoteLikeCount($object); + $dto->apDislikeCount = $this->activityPubManager->extractRemoteDislikeCount($object); + $dto->apShareCount = $this->activityPubManager->extractRemoteShareCount($object); + + return $this->entryCommentManager->create($dto, $actor, false); } else { throw new \Exception('Actor could not be found for entry comment.'); } @@ -219,12 +219,11 @@ private function createPost(array $object): Post } else { $dto->lang = $this->settingsManager->get('KBIN_DEFAULT_LANG'); } + $dto->apLikeCount = $this->activityPubManager->extractRemoteLikeCount($object); + $dto->apDislikeCount = $this->activityPubManager->extractRemoteDislikeCount($object); + $dto->apShareCount = $this->activityPubManager->extractRemoteShareCount($object); - return $this->postManager->create( - $dto, - $actor, - false - ); + return $this->postManager->create($dto, $actor, false); } else { throw new \Exception('Actor could not be found for post.'); } @@ -267,12 +266,11 @@ private function createPostComment(array $object, ActivityPubActivityInterface $ } else { $dto->lang = $this->settingsManager->get('KBIN_DEFAULT_LANG'); } + $dto->apLikeCount = $this->activityPubManager->extractRemoteLikeCount($object); + $dto->apDislikeCount = $this->activityPubManager->extractRemoteDislikeCount($object); + $dto->apShareCount = $this->activityPubManager->extractRemoteShareCount($object); - return $this->postCommentManager->create( - $dto, - $actor, - false - ); + return $this->postCommentManager->create($dto, $actor, false); } else { throw new \Exception('Actor could not be found for post comment.'); } diff --git a/src/Service/ActivityPub/Page.php b/src/Service/ActivityPub/Page.php index b7cc66328..3cae8e4d9 100644 --- a/src/Service/ActivityPub/Page.php +++ b/src/Service/ActivityPub/Page.php @@ -96,14 +96,13 @@ public function create(array $object): Entry } else { $dto->lang = $this->settingsManager->get('KBIN_DEFAULT_LANG'); } + $dto->apLikeCount = $this->activityPubManager->extractRemoteLikeCount($object); + $dto->apDislikeCount = $this->activityPubManager->extractRemoteDislikeCount($object); + $dto->apShareCount = $this->activityPubManager->extractRemoteShareCount($object); $this->logger->debug('creating page'); - return $this->entryManager->create( - $dto, - $actor, - false - ); + return $this->entryManager->create($dto, $actor, false); } else { throw new \Exception('Actor could not be found for entry.'); } diff --git a/src/Service/ActivityPubManager.php b/src/Service/ActivityPubManager.php index fa811bba8..0b21a0b24 100644 --- a/src/Service/ActivityPubManager.php +++ b/src/Service/ActivityPubManager.php @@ -865,4 +865,46 @@ public function extractUrl(string|array|null $url): ?string return null; } + + public function extractRemoteLikeCount(array $apObject): ?int + { + if (!empty($apObject['likes'])) { + if (false !== filter_var($apObject['likes'], FILTER_VALIDATE_URL)) { + $collection = $this->apHttpClient->getCollectionObject($apObject['likes']); + if (isset($collection['totalItems']) && \is_int($collection['totalItems'])) { + return $collection['totalItems']; + } + } + } + + return null; + } + + public function extractRemoteDislikeCount(array $apObject): ?int + { + if (!empty($apObject['dislikes'])) { + if (false !== filter_var($apObject['dislikes'], FILTER_VALIDATE_URL)) { + $collection = $this->apHttpClient->getCollectionObject($apObject['dislikes']); + if (isset($collection['totalItems']) && \is_int($collection['totalItems'])) { + return $collection['totalItems']; + } + } + } + + return null; + } + + public function extractRemoteShareCount(array $apObject): ?int + { + if (!empty($apObject['shares'])) { + if (false !== filter_var($apObject['shares'], FILTER_VALIDATE_URL)) { + $collection = $this->apHttpClient->getCollectionObject($apObject['shares']); + if (isset($collection['totalItems']) && \is_int($collection['totalItems'])) { + return $collection['totalItems']; + } + } + } + + return null; + } } diff --git a/src/Service/EntryCommentManager.php b/src/Service/EntryCommentManager.php index 4afd81d1e..1e390c203 100644 --- a/src/Service/EntryCommentManager.php +++ b/src/Service/EntryCommentManager.php @@ -76,6 +76,9 @@ public function create(EntryCommentDto $dto, User $user, $rateLimit = true): Ent : $dto->mentions; $comment->visibility = $dto->visibility; $comment->apId = $dto->apId; + $comment->apLikeCount = $dto->apLikeCount; + $comment->apDislikeCount = $dto->apDislikeCount; + $comment->apShareCount = $dto->apShareCount; $comment->magazine->lastActive = new \DateTime(); $comment->user->lastActive = new \DateTime(); $comment->lastActive = $dto->lastActive ?? $comment->lastActive; @@ -117,6 +120,10 @@ public function edit(EntryComment $comment, EntryCommentDto $dto): EntryComment throw new \Exception('Comment body and image cannot be empty'); } + $comment->apLikeCount = $dto->apLikeCount; + $comment->apDislikeCount = $dto->apDislikeCount; + $comment->apShareCount = $dto->apShareCount; + $this->entityManager->flush(); if ($oldImage && $comment->image !== $oldImage) { diff --git a/src/Service/EntryManager.php b/src/Service/EntryManager.php index cd17948d5..9adbc716e 100644 --- a/src/Service/EntryManager.php +++ b/src/Service/EntryManager.php @@ -96,6 +96,9 @@ public function create(EntryDto $dto, User $user, bool $rateLimit = true): Entry $entry->mentions = $dto->body ? $this->mentionManager->extract($dto->body) : null; $entry->visibility = $dto->visibility; $entry->apId = $dto->apId; + $entry->apLikeCount = $dto->apLikeCount; + $entry->apDislikeCount = $dto->apDislikeCount; + $entry->apShareCount = $dto->apShareCount; $entry->magazine->lastActive = new \DateTime(); $entry->user->lastActive = new \DateTime(); $entry->lastActive = $dto->lastActive ?? $entry->lastActive; @@ -177,6 +180,10 @@ public function edit(Entry $entry, EntryDto $dto): Entry throw new \Exception('Entry body, name, url and image cannot all be empty'); } + $entry->apLikeCount = $dto->apLikeCount; + $entry->apDislikeCount = $dto->apDislikeCount; + $entry->apShareCount = $dto->apShareCount; + $this->entityManager->flush(); if ($oldImage && $entry->image !== $oldImage) { diff --git a/src/Service/FavouriteManager.php b/src/Service/FavouriteManager.php index 2342317b4..ea123ee00 100644 --- a/src/Service/FavouriteManager.php +++ b/src/Service/FavouriteManager.php @@ -5,7 +5,11 @@ namespace App\Service; use App\Entity\Contracts\FavouriteInterface; +use App\Entity\Entry; +use App\Entity\EntryComment; use App\Entity\Favourite; +use App\Entity\Post; +use App\Entity\PostComment; use App\Entity\User; use App\Event\FavouriteEvent; use App\Factory\FavouriteFactory; @@ -30,6 +34,12 @@ public function toggle(User $user, FavouriteInterface $subject, string $type = n { if (!($favourite = $this->repository->findBySubject($user, $subject))) { if (self::TYPE_UNLIKE === $type) { + if ($subject instanceof Entry || $subject instanceof EntryComment || $subject instanceof Post || $subject instanceof PostComment) { + if (null !== $subject->apLikeCount) { + --$subject->apLikeCount; + } + } + return null; } @@ -40,8 +50,20 @@ public function toggle(User $user, FavouriteInterface $subject, string $type = n $subject->updateCounts(); $subject->updateScore(); $subject->updateRanking(); + + if ($subject instanceof Entry || $subject instanceof EntryComment || $subject instanceof Post || $subject instanceof PostComment) { + if (null !== $subject->apLikeCount) { + ++$subject->apLikeCount; + } + } } else { if (self::TYPE_LIKE === $type) { + if ($subject instanceof Entry || $subject instanceof EntryComment || $subject instanceof Post || $subject instanceof PostComment) { + if (null !== $subject->apLikeCount) { + ++$subject->apLikeCount; + } + } + return $favourite; } @@ -50,6 +72,11 @@ public function toggle(User $user, FavouriteInterface $subject, string $type = n $subject->updateScore(); $subject->updateRanking(); $favourite = null; + if ($subject instanceof Entry || $subject instanceof EntryComment || $subject instanceof Post || $subject instanceof PostComment) { + if (null !== $subject->apLikeCount) { + --$subject->apLikeCount; + } + } } $this->entityManager->flush(); diff --git a/src/Service/PostCommentManager.php b/src/Service/PostCommentManager.php index baaa49792..95aee7dfb 100644 --- a/src/Service/PostCommentManager.php +++ b/src/Service/PostCommentManager.php @@ -82,6 +82,9 @@ public function create(PostCommentDto $dto, User $user, $rateLimit = true): Post : $dto->mentions; $comment->visibility = $dto->visibility; $comment->apId = $dto->apId; + $comment->apLikeCount = $dto->apLikeCount; + $comment->apDislikeCount = $dto->apDislikeCount; + $comment->apShareCount = $dto->apShareCount; $comment->magazine->lastActive = new \DateTime(); $comment->user->lastActive = new \DateTime(); $comment->lastActive = $dto->lastActive ?? $comment->lastActive; @@ -126,6 +129,10 @@ public function edit(PostComment $comment, PostCommentDto $dto): PostComment throw new \Exception('Comment body and image cannot be empty'); } + $comment->apLikeCount = $dto->apLikeCount; + $comment->apDislikeCount = $dto->apDislikeCount; + $comment->apShareCount = $dto->apShareCount; + $this->entityManager->flush(); if ($oldImage && $comment->image !== $oldImage) { diff --git a/src/Service/PostManager.php b/src/Service/PostManager.php index d4dcf26fb..657b964c5 100644 --- a/src/Service/PostManager.php +++ b/src/Service/PostManager.php @@ -91,6 +91,9 @@ public function create(PostDto $dto, User $user, $rateLimit = true): Post $post->mentions = $dto->body ? $this->mentionManager->extract($dto->body) : null; $post->visibility = $dto->visibility; $post->apId = $dto->apId; + $post->apLikeCount = $dto->apLikeCount; + $post->apDislikeCount = $dto->apDislikeCount; + $post->apShareCount = $dto->apShareCount; $post->magazine->lastActive = new \DateTime(); $post->user->lastActive = new \DateTime(); $post->lastActive = $dto->lastActive ?? $post->lastActive; @@ -129,6 +132,10 @@ public function edit(Post $post, PostDto $dto): Post throw new \Exception('Post body and image cannot be empty'); } + $post->apLikeCount = $dto->apLikeCount; + $post->apDislikeCount = $dto->apDislikeCount; + $post->apShareCount = $dto->apShareCount; + $this->entityManager->flush(); if ($oldImage && $post->image !== $oldImage) { diff --git a/src/Service/VoteManager.php b/src/Service/VoteManager.php index a4198724c..c2014ae19 100644 --- a/src/Service/VoteManager.php +++ b/src/Service/VoteManager.php @@ -5,7 +5,9 @@ namespace App\Service; use App\Entity\Contracts\VotableInterface; +use App\Entity\Entry; use App\Entity\EntryComment; +use App\Entity\Post; use App\Entity\PostComment; use App\Entity\User; use App\Entity\Vote; @@ -46,12 +48,33 @@ public function vote(int $choice, VotableInterface $votable, User $user, $rateLi if ($vote) { $votedAgain = true; $choice = $this->guessUserChoice($choice, $votable->getUserChoice($user)); + + if ($votable instanceof Entry || $votable instanceof EntryComment || $votable instanceof Post || $votable instanceof PostComment) { + if (VotableInterface::VOTE_UP === $vote->choice && null !== $votable->apShareCount) { + --$votable->apShareCount; + } elseif (VotableInterface::VOTE_DOWN === $vote->choice && null !== $votable->apDislikeCount) { + --$votable->apDislikeCount; + } + + if (VotableInterface::VOTE_UP === $choice && null !== $votable->apShareCount) { + ++$votable->apShareCount; + } elseif (VotableInterface::VOTE_DOWN === $choice && null !== $votable->apDislikeCount) { + ++$votable->apDislikeCount; + } + } + $vote->choice = $choice; } else { if (VotableInterface::VOTE_UP === $choice) { return $this->upvote($votable, $user); } + if ($votable instanceof Entry || $votable instanceof EntryComment || $votable instanceof Post || $votable instanceof PostComment) { + if (null !== $votable->apDislikeCount) { + ++$votable->apDislikeCount; + } + } + $vote = $this->factory->create($choice, $votable, $user); $this->entityManager->persist($vote); } @@ -117,6 +140,12 @@ public function upvote(VotableInterface $votable, User $user): Vote $votable->entry->lastActive = new \DateTime(); } + if ($votable instanceof Entry || $votable instanceof EntryComment || $votable instanceof Post || $votable instanceof PostComment) { + if (null !== $votable->apShareCount) { + ++$votable->apShareCount; + } + } + $this->entityManager->flush(); $this->dispatcher->dispatch(new VoteEvent($votable, $vote, false)); @@ -136,6 +165,19 @@ public function removeVote(VotableInterface $votable, User $user): ?Vote if (!$vote) { return null; } + if (VotableInterface::VOTE_UP === $vote->choice) { + if ($votable instanceof Entry || $votable instanceof EntryComment || $votable instanceof Post || $votable instanceof PostComment) { + if (null !== $votable->apShareCount) { + --$votable->apShareCount; + } + } + } elseif (VotableInterface::VOTE_DOWN === $vote->choice) { + if ($votable instanceof Entry || $votable instanceof EntryComment || $votable instanceof Post || $votable instanceof PostComment) { + if (null !== $votable->apDislikeCount) { + --$votable->apDislikeCount; + } + } + } $vote->choice = VotableInterface::VOTE_NONE; diff --git a/templates/components/boost.html.twig b/templates/components/boost.html.twig index 18b46dc94..776850973 100644 --- a/templates/components/boost.html.twig +++ b/templates/components/boost.html.twig @@ -6,7 +6,7 @@ - \ No newline at end of file + diff --git a/templates/components/vote.html.twig b/templates/components/vote.html.twig index ccf27158b..42fe9ab55 100644 --- a/templates/components/vote.html.twig +++ b/templates/components/vote.html.twig @@ -29,7 +29,7 @@ title="{{ 'favourite'|trans }}" aria-label="{{ 'favourite'|trans }}" data-action="subject#vote"> - {{ subject.favouriteCount }} + {{ subject.apLikeCount ?? subject.favouriteCount }} @@ -43,7 +43,7 @@ title="{{ 'down_vote'|trans }}" aria-label="{{ 'down_vote'|trans }}" data-action="subject#vote"> - {{ subject.countDownvotes }} + {{ subject.apDislikeCount ??subject.countDownvotes }} From d168bf3fcddf2ef69094243b7b43c4926cf95851 Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Tue, 25 Jun 2024 17:40:27 +0200 Subject: [PATCH 8/9] Fix like counts changing - display correct counts in the activity component - fix like count getting lowered when initially downvoting - use ap like, dislike and share count in the score formula --- src/Entity/Entry.php | 2 +- src/Entity/Traits/ActivityPubActivityTrait.php | 9 --------- src/Entity/Traits/VotableTrait.php | 16 +++++++++++++--- src/Service/FavouriteManager.php | 6 ------ templates/components/vote.html.twig | 2 +- templates/entry/_options_activity.html.twig | 2 +- 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/Entity/Entry.php b/src/Entity/Entry.php index 852d2464a..65258f0a1 100644 --- a/src/Entity/Entry.php +++ b/src/Entity/Entry.php @@ -283,7 +283,7 @@ public function addVote(Vote $vote): self public function updateScore(): self { - $this->score = $this->getUpVotes()->count() + $this->favouriteCount - $this->getDownVotes()->count(); + $this->score = ($this->apShareCount ?? $this->getUpVotes()->count()) + ($this->apLikeCount ?? $this->favouriteCount) - ($this->apDislikeCount ?? $this->getDownVotes()->count()); return $this; } diff --git a/src/Entity/Traits/ActivityPubActivityTrait.php b/src/Entity/Traits/ActivityPubActivityTrait.php index fc88a8535..8d84ba77b 100644 --- a/src/Entity/Traits/ActivityPubActivityTrait.php +++ b/src/Entity/Traits/ActivityPubActivityTrait.php @@ -10,13 +10,4 @@ trait ActivityPubActivityTrait { #[Column(type: 'string', unique: true, nullable: true)] public ?string $apId = null; - - #[Column(type: 'integer', nullable: true)] - public ?int $apLikeCount = null; - - #[Column(type: 'integer', nullable: true)] - public ?int $apDislikeCount = null; - - #[Column(type: 'integer', nullable: true)] - public ?int $apShareCount = null; } diff --git a/src/Entity/Traits/VotableTrait.php b/src/Entity/Traits/VotableTrait.php index 7dfe43b51..dea79c86b 100644 --- a/src/Entity/Traits/VotableTrait.php +++ b/src/Entity/Traits/VotableTrait.php @@ -10,6 +10,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; +use Doctrine\ORM\Mapping\Column; trait VotableTrait { @@ -19,19 +20,28 @@ trait VotableTrait #[ORM\Column(type: 'integer')] private int $downVotes = 0; + #[Column(type: 'integer', nullable: true)] + public ?int $apLikeCount = null; + + #[Column(type: 'integer', nullable: true)] + public ?int $apDislikeCount = null; + + #[Column(type: 'integer', nullable: true)] + public ?int $apShareCount = null; + public function countUpVotes(): int { - return $this->upVotes; + return $this->apShareCount ?? $this->upVotes; } public function countDownVotes(): int { - return $this->downVotes; + return $this->apDislikeCount ?? $this->downVotes; } public function countVotes(): int { - return $this->downVotes + $this->upVotes; + return $this->countDownVotes() + $this->countUpVotes(); } public function getUserChoice(User $user): int diff --git a/src/Service/FavouriteManager.php b/src/Service/FavouriteManager.php index ea123ee00..1907c3183 100644 --- a/src/Service/FavouriteManager.php +++ b/src/Service/FavouriteManager.php @@ -34,12 +34,6 @@ public function toggle(User $user, FavouriteInterface $subject, string $type = n { if (!($favourite = $this->repository->findBySubject($user, $subject))) { if (self::TYPE_UNLIKE === $type) { - if ($subject instanceof Entry || $subject instanceof EntryComment || $subject instanceof Post || $subject instanceof PostComment) { - if (null !== $subject->apLikeCount) { - --$subject->apLikeCount; - } - } - return null; } diff --git a/templates/components/vote.html.twig b/templates/components/vote.html.twig index 42fe9ab55..a2847ee5c 100644 --- a/templates/components/vote.html.twig +++ b/templates/components/vote.html.twig @@ -43,7 +43,7 @@ title="{{ 'down_vote'|trans }}" aria-label="{{ 'down_vote'|trans }}" data-action="subject#vote"> - {{ subject.apDislikeCount ??subject.countDownvotes }} + {{ subject.apDislikeCount ?? subject.countDownvotes }} diff --git a/templates/entry/_options_activity.html.twig b/templates/entry/_options_activity.html.twig index 6ed1dab06..5b944ec05 100644 --- a/templates/entry/_options_activity.html.twig +++ b/templates/entry/_options_activity.html.twig @@ -12,7 +12,7 @@
  • - {{ 'favourites'|trans }} ({{ entry.favouriteCount }}) + {{ 'favourites'|trans }} ({{ entry.apLikeCount ?? entry.favouriteCount }})
  • From 2b3d785114905486cb82d99a6ff70cb9c58cf734 Mon Sep 17 00:00:00 2001 From: BentiGorlich Date: Wed, 26 Jun 2024 17:38:46 +0200 Subject: [PATCH 9/9] Update score and ranking on updates and on create --- src/Service/EntryCommentManager.php | 5 +++++ src/Service/EntryManager.php | 5 +++++ src/Service/PostCommentManager.php | 4 ++++ src/Service/PostManager.php | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/src/Service/EntryCommentManager.php b/src/Service/EntryCommentManager.php index 1e390c203..68d349e05 100644 --- a/src/Service/EntryCommentManager.php +++ b/src/Service/EntryCommentManager.php @@ -89,6 +89,9 @@ public function create(EntryCommentDto $dto, User $user, $rateLimit = true): Ent $comment->entry->addComment($comment); + $comment->updateScore(); + $comment->updateRanking(); + $this->entityManager->persist($comment); $this->entityManager->flush(); @@ -123,6 +126,8 @@ public function edit(EntryComment $comment, EntryCommentDto $dto): EntryComment $comment->apLikeCount = $dto->apLikeCount; $comment->apDislikeCount = $dto->apDislikeCount; $comment->apShareCount = $dto->apShareCount; + $comment->updateScore(); + $comment->updateRanking(); $this->entityManager->flush(); diff --git a/src/Service/EntryManager.php b/src/Service/EntryManager.php index 9adbc716e..daa73586e 100644 --- a/src/Service/EntryManager.php +++ b/src/Service/EntryManager.php @@ -113,6 +113,9 @@ public function create(EntryDto $dto, User $user, bool $rateLimit = true): Entry $this->badgeManager->assign($entry, $dto->badges); } + $entry->updateScore(); + $entry->updateRanking(); + $this->entityManager->persist($entry); $this->entityManager->flush(); @@ -183,6 +186,8 @@ public function edit(Entry $entry, EntryDto $dto): Entry $entry->apLikeCount = $dto->apLikeCount; $entry->apDislikeCount = $dto->apDislikeCount; $entry->apShareCount = $dto->apShareCount; + $entry->updateScore(); + $entry->updateRanking(); $this->entityManager->flush(); diff --git a/src/Service/PostCommentManager.php b/src/Service/PostCommentManager.php index 95aee7dfb..8fde901be 100644 --- a/src/Service/PostCommentManager.php +++ b/src/Service/PostCommentManager.php @@ -94,6 +94,8 @@ public function create(PostCommentDto $dto, User $user, $rateLimit = true): Post } $comment->post->addComment($comment); + $comment->updateScore(); + $comment->updateRanking(); $this->entityManager->persist($comment); $this->entityManager->flush(); @@ -132,6 +134,8 @@ public function edit(PostComment $comment, PostCommentDto $dto): PostComment $comment->apLikeCount = $dto->apLikeCount; $comment->apDislikeCount = $dto->apDislikeCount; $comment->apShareCount = $dto->apShareCount; + $comment->updateScore(); + $comment->updateRanking(); $this->entityManager->flush(); diff --git a/src/Service/PostManager.php b/src/Service/PostManager.php index 657b964c5..ee5824b0a 100644 --- a/src/Service/PostManager.php +++ b/src/Service/PostManager.php @@ -102,6 +102,9 @@ public function create(PostDto $dto, User $user, $rateLimit = true): Post throw new \Exception('Post body and image cannot be empty'); } + $post->updateScore(); + $post->updateRanking(); + $this->entityManager->persist($post); $this->entityManager->flush(); @@ -135,6 +138,8 @@ public function edit(Post $post, PostDto $dto): Post $post->apLikeCount = $dto->apLikeCount; $post->apDislikeCount = $dto->apDislikeCount; $post->apShareCount = $dto->apShareCount; + $post->updateScore(); + $post->updateRanking(); $this->entityManager->flush();