diff --git a/app/Config/Events.php b/app/Config/Events.php index a36233f8bee8cd6304eeae9c99d78530382d8e1a..49c50d4481adf0c600c947dce1ae7dc5c5a0d96a 100644 --- a/app/Config/Events.php +++ b/app/Config/Events.php @@ -219,11 +219,15 @@ Events::on('on_post_undo_reblog', function ($reblogPost): void { Events::on('on_post_reply', function ($reply): void { $post = $reply->reply_to_post; - model(EpisodeModel::class, false)->builder() - ->where('id', $post->episode_id) - ->increment('comments_count'); + if ($post->in_reply_to_id === null) { + model(EpisodeModel::class, false)->builder() + ->where('id', $post->episode_id) + ->increment('comments_count'); + } if ($post->actor->is_podcast) { + cache() + ->deleteMatching("podcast-{$post->actor->podcast->handle}*"); cache() ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() @@ -240,11 +244,15 @@ Events::on('on_post_reply', function ($reply): void { Events::on('on_reply_remove', function ($reply): void { $post = $reply->reply_to_post; - model(EpisodeModel::class, false)->builder() - ->where('id', $post->episode_id) - ->decrement('comments_count'); + if ($post->in_reply_to_id === null) { + model(EpisodeModel::class, false)->builder() + ->where('id', $post->episode_id) + ->decrement('comments_count'); + } if ($post->actor->is_podcast) { + cache() + ->deleteMatching("podcast-{$post->actor->podcast->handle}*"); cache() ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); cache() diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 60f89c448b442a8fcfaa0520763d019def99aa9f..62dedeafb257ede959d4431ea07fc90a855435c7 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -72,6 +72,7 @@ use RuntimeException; * @property bool $is_published_on_hubs * @property int $posts_count * @property int $comments_count + * @property EpisodeComment[]|null $comments * @property int $created_by * @property int $updated_by * @property string $publication_status; diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php index 839099824f2a238dd4660719cbb7e531fd4b36c4..1d98655c5979e427c3007fa99b337730265f59b0 100644 --- a/app/Models/EpisodeCommentModel.php +++ b/app/Models/EpisodeCommentModel.php @@ -14,9 +14,12 @@ use App\Entities\EpisodeComment; use App\Libraries\CommentObject; use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\BaseResult; +use CodeIgniter\I18n\Time; use Michalsn\Uuid\UuidModel; use Modules\Fediverse\Activities\CreateActivity; +use Modules\Fediverse\Activities\DeleteActivity; use Modules\Fediverse\Models\ActivityModel; +use Modules\Fediverse\Objects\TombstoneObject; class EpisodeCommentModel extends UuidModel { @@ -70,10 +73,9 @@ class EpisodeCommentModel extends UuidModel return $found; } - public function addComment(EpisodeComment $comment, bool $registerActivity = false): string | false + public function addComment(EpisodeComment $comment, bool $registerActivity = true): string | false { $this->db->transStart(); - // increment Episode's comments_count if (! ($newCommentId = $this->insert($comment, true))) { $this->db->transRollback(); @@ -128,15 +130,67 @@ class EpisodeCommentModel extends UuidModel $this->db->transComplete(); - // delete podcast and episode pages cache - cache() - ->deleteMatching('page_podcast#' . $comment->episode->podcast_id . '*'); - cache() - ->deleteMatching('page_episode#' . $comment->episode_id . '*'); + $this->clearCache($comment); return $newCommentId; } + public function removeComment(EpisodeComment $comment, bool $registerActivity = true): BaseResult | bool + { + $this->db->transStart(); + + // remove all replies + foreach ($comment->replies as $reply) { + $this->removeComment($reply); + } + + if ($registerActivity) { + $deleteActivity = new DeleteActivity(); + $tombstoneObject = new TombstoneObject(); + $tombstoneObject->set('id', $comment->uri); + $deleteActivity + ->set('actor', $comment->actor->uri) + ->set('object', $tombstoneObject); + + $activityId = model(ActivityModel::class, false) + ->newActivity( + 'Delete', + $comment->actor_id, + null, + null, + $deleteActivity->toJSON(), + Time::now(), + 'queued', + ); + + $deleteActivity->set('id', url_to('activity', esc($comment->actor->username), $activityId)); + + model(ActivityModel::class, false) + ->update($activityId, [ + 'payload' => $deleteActivity->toJSON(), + ]); + } + + $result = model(self::class, false) + ->delete($comment->id); + + if ($comment->in_reply_to_id === null) { + model(EpisodeModel::class, false)->builder() + ->where('id', $comment->episode_id) + ->decrement('comments_count'); + } else { + (new self())->builder() + ->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes()) + ->decrement('replies_count'); + } + + $this->clearCache($comment); + + $this->db->transComplete(); + + return $result; + } + /** * Retrieves all published posts for a given episode ordered by publication date * @@ -250,4 +304,18 @@ class EpisodeCommentModel extends UuidModel return $data; } + + protected function clearCache(EpisodeComment $comment): void + { + cache() + ->deleteMatching("comment#{$comment->id}*"); + + // delete podcast and episode pages cache + cache() + ->deleteMatching("podcast-{$comment->episode->podcast->handle}*"); + cache() + ->deleteMatching('page_podcast#' . $comment->episode->podcast_id . '*'); + cache() + ->deleteMatching('page_episode#' . $comment->episode_id . '*'); + } } diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index f218f47b86934fec32ba198c9750a5138374f1dc..6ceb231b75192ee84b4b6d5462bf3f3895204a06 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -337,33 +337,30 @@ class EpisodeModel extends Model public function resetCommentsCount(): int | false { - $episodeCommentsCount = $this->builder() - ->select('episodes.id, COUNT(*) as `comments_count`') - ->join('episode_comments', 'episodes.id = episode_comments.episode_id') + $episodeCommentsCount = (new EpisodeCommentModel())->builder() + ->select('episode_id, COUNT(*) as `comments_count`') ->where('in_reply_to_id', null) - ->groupBy('episodes.id') + ->groupBy('episode_id') ->getCompiledSelect(); - $episodePostsRepliesCount = $this->builder() - ->select('episodes.id, COUNT(*) as `comments_count`') - ->join( - config('Fediverse') - ->tablesPrefix . 'posts', - 'episodes.id = ' . config('Fediverse')->tablesPrefix . 'posts.episode_id' - ) - ->where('in_reply_to_id IS NOT', null) - ->groupBy('episodes.id') + $postsTable = config('Fediverse') + ->tablesPrefix . 'posts'; + $episodePostsRepliesCount = (new PostModel())->builder() + ->select($postsTable . '.episode_id as episode_id, COUNT(*) as `comments_count`') + ->join($postsTable . ' as fp', $postsTable . '.id = fp.in_reply_to_id') + ->where($postsTable . '.in_reply_to_id', null) + ->groupBy($postsTable . '.episode_id') ->getCompiledSelect(); /** @var BaseResult $query */ $query = $this->db->query( - 'SELECT `id`, SUM(`comments_count`) as `comments_count` FROM (' . $episodeCommentsCount . ' UNION ALL ' . $episodePostsRepliesCount . ') x GROUP BY `id`' + 'SELECT `episode_id` as `id`, SUM(`comments_count`) as `comments_count` FROM (' . $episodeCommentsCount . ' UNION ALL ' . $episodePostsRepliesCount . ') x GROUP BY `episode_id`' ); $countsPerEpisodeId = $query->getResultArray(); if ($countsPerEpisodeId !== []) { - return $this->updateBatch($countsPerEpisodeId, 'id'); + return (new self())->updateBatch($countsPerEpisodeId, 'id'); } return 0; diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index 86684bad46ab038d4478c05c2e8f6e3c5640fd4d..9fafb6e8fd066ea2d86f5d68b90e081ef313519d 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -722,6 +722,16 @@ class EpisodeController extends BaseController (new PostModel())->removePost($post); } + $allCommentsLinkedToEpisode = (new EpisodeCommentModel()) + ->where([ + 'episode_id' => $this->episode->id, + 'in_reply_to_id' => null, + ]) + ->findAll(); + foreach ($allCommentsLinkedToEpisode as $comment) { + (new EpisodeCommentModel())->removeComment($comment); + } + // set episode published_at to null to unpublish $this->episode->published_at = null; diff --git a/modules/Admin/Language/ar/Episode.php b/modules/Admin/Language/ar/Episode.php index e661b9838c2e02586766b4281b536831d1ff3a5e..f83664a693297f336d21567198dcc6080326fbd9 100644 --- a/modules/Admin/Language/ar/Episode.php +++ b/modules/Admin/Language/ar/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'إلغاء النشر', ], diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/en/Episode.php +++ b/modules/Admin/Language/en/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/fa/Episode.php b/modules/Admin/Language/fa/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/fa/Episode.php +++ b/modules/Admin/Language/fa/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/gd/Episode.php b/modules/Admin/Language/gd/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/gd/Episode.php +++ b/modules/Admin/Language/gd/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/id/Episode.php b/modules/Admin/Language/id/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/id/Episode.php +++ b/modules/Admin/Language/id/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/it/Episode.php b/modules/Admin/Language/it/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/it/Episode.php +++ b/modules/Admin/Language/it/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/nl/Episode.php b/modules/Admin/Language/nl/Episode.php index 009527816b277ffdff64d9ab07df7aa729ae0b8e..2fa67126c4212b22922d888fc3af64a276e02834 100644 --- a/modules/Admin/Language/nl/Episode.php +++ b/modules/Admin/Language/nl/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/oc/Episode.php b/modules/Admin/Language/oc/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/oc/Episode.php +++ b/modules/Admin/Language/oc/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/pt/Episode.php b/modules/Admin/Language/pt/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/pt/Episode.php +++ b/modules/Admin/Language/pt/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/ru/Episode.php b/modules/Admin/Language/ru/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/ru/Episode.php +++ b/modules/Admin/Language/ru/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/sk/Episode.php b/modules/Admin/Language/sk/Episode.php index 613267b9440c965cc5c037faa8f7b2e9c943842c..f174fc0cb72c064b445ba144a01d9738a024e675 100644 --- a/modules/Admin/Language/sk/Episode.php +++ b/modules/Admin/Language/sk/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ], diff --git a/modules/Admin/Language/sv/Episode.php b/modules/Admin/Language/sv/Episode.php index 539f6b89118818d7f6ee5c938f1f58f25c7e8b9f..ba0922f5192c6e298f1983ff0cf81692be4f0f02 100644 --- a/modules/Admin/Language/sv/Episode.php +++ b/modules/Admin/Language/sv/Episode.php @@ -178,7 +178,7 @@ return [ ], 'unpublish_form' => [ 'disclaimer' => - "Unpublishing the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.", + "Unpublishing the episode will delete all the comments and posts associated with it and remove it from the podcast's RSS feed.", 'understand' => 'I understand, I want to unpublish the episode', 'submit' => 'Unpublish', ],