diff --git a/app/Config/Events.php b/app/Config/Events.php index b2288b94..59bfed7d 100644 --- a/app/Config/Events.php +++ b/app/Config/Events.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Config; use App\Entities\Actor; -use App\Entities\Status; +use App\Entities\Post; use App\Entities\User; use CodeIgniter\Events\Events; use CodeIgniter\Exceptions\FrameworkException; @@ -120,82 +120,72 @@ Events::on('on_undo_follow', function ($actor, $targetActor): void { }); /** - * @param Status $status + * @param Post $post */ -Events::on('on_status_add', function ($status): void { - if ($status->in_reply_to_id !== null) { - $status = $status->reply_to_status; +Events::on('on_post_add', function ($post): void { + $isReply = $post->in_reply_to_id !== null; + + if ($isReply) { + $post = $post->reply_to_post; } - if ($status->episode_id) { - model('EpisodeModel') - ->where('id', $status->episode_id) - ->increment('statuses_total'); + if ($post->episode_id !== null) { + if ($isReply) { + model('EpisodeModel', false) + ->where('id', $post->episode_id) + ->increment('comments_count'); + } else { + model('EpisodeModel', false) + ->where('id', $post->episode_id) + ->increment('posts_count'); + } } - if ($status->actor->is_podcast) { + if ($post->actor->is_podcast) { // Removing all of the podcast pages is a bit overkill, but works to avoid caching bugs // same for other events below cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); } }); /** - * @param Status $status + * @param Post $post */ -Events::on('on_status_remove', function ($status): void { - if ($status->in_reply_to_id !== null) { - Events::trigger('on_status_remove', $status->reply_to_status); +Events::on('on_post_remove', function ($post): void { + if ($post->in_reply_to_id !== null) { + Events::trigger('on_post_remove', $post->reply_to_post); } - if ($episodeId = $status->episode_id) { - model('EpisodeModel') + if ($episodeId = $post->episode_id) { + model('EpisodeModel', false) ->where('id', $episodeId) - ->decrement('statuses_total', 1 + $status->reblogs_count); - - model('EpisodeModel') - ->where('id', $episodeId) - ->decrement('reblogs_total', $status->reblogs_count); - - model('EpisodeModel') - ->where('id', $episodeId) - ->decrement('favourites_total', $status->favourites_count); + ->decrement('posts_count'); } - if ($status->actor->is_podcast) { + if ($post->actor->is_podcast) { cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); } cache() - ->deleteMatching("page_status#{$status->id}*"); + ->deleteMatching("page_post#{$post->id}*"); }); /** * @param Actor $actor - * @param Status $status + * @param Post $post */ -Events::on('on_status_reblog', function ($actor, $status): void { - if ($episodeId = $status->episode_id) { - model('EpisodeModel') - ->where('id', $episodeId) - ->increment('reblogs_total'); - - model('EpisodeModel') - ->where('id', $episodeId) - ->increment('statuses_total'); - } - - if ($status->actor->is_podcast) { +Events::on('on_post_reblog', function ($actor, $post): void { + if ($post->actor->is_podcast) { cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); } if ($actor->is_podcast) { @@ -205,111 +195,96 @@ Events::on('on_status_reblog', function ($actor, $status): void { } cache() - ->deleteMatching("page_status#{$status->id}*"); + ->deleteMatching("page_post#{$post->id}*"); - if ($status->in_reply_to_id !== null) { - cache()->deleteMatching("page_status#{$status->in_reply_to_id}"); + if ($post->in_reply_to_id !== null) { + cache()->deleteMatching("page_post#{$post->in_reply_to_id}"); } }); /** - * @param Status $reblogStatus + * @param Post $reblogPost */ -Events::on('on_status_undo_reblog', function ($reblogStatus): void { - $status = $reblogStatus->reblog_of_status; - if ($episodeId = $status->episode_id) { - model('EpisodeModel') - ->where('id', $episodeId) - ->decrement('reblogs_total'); +Events::on('on_post_undo_reblog', function ($reblogPost): void { + $post = $reblogPost->reblog_of_post; - model('EpisodeModel') - ->where('id', $episodeId) - ->decrement('statuses_total'); - } - - if ($status->actor->is_podcast) { + if ($post->actor->is_podcast) { cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); } cache() - ->deleteMatching("page_status#{$status->id}*"); + ->deleteMatching("page_post#{$post->id}*"); cache() - ->deleteMatching("page_status#{$reblogStatus->id}*"); + ->deleteMatching("page_post#{$reblogPost->id}*"); - if ($status->in_reply_to_id !== null) { - cache()->deleteMatching("page_status#{$status->in_reply_to_id}"); + if ($post->in_reply_to_id !== null) { + cache()->deleteMatching("page_post#{$post->in_reply_to_id}"); } - if ($reblogStatus->actor->is_podcast) { + if ($reblogPost->actor->is_podcast) { cache() - ->deleteMatching("podcast#{$reblogStatus->actor->podcast->id}*"); + ->deleteMatching("podcast#{$reblogPost->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$reblogStatus->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$reblogPost->actor->podcast->id}*"); } }); /** - * @param Status $reply + * @param Post $reply */ -Events::on('on_status_reply', function ($reply): void { - $status = $reply->reply_to_status; +Events::on('on_post_reply', function ($reply): void { + $post = $reply->reply_to_post; - if ($status->actor->is_podcast) { + if ($post->actor->is_podcast) { cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); } cache() - ->deleteMatching("page_status#{$status->id}*"); + ->deleteMatching("page_post#{$post->id}*"); }); /** - * @param Status $reply + * @param Post $reply */ Events::on('on_reply_remove', function ($reply): void { - $status = $reply->reply_to_status; + $post = $reply->reply_to_post; - if ($status->actor->is_podcast) { + if ($post->actor->is_podcast) { cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); } cache() - ->deleteMatching("page_status#{$status->id}*"); + ->deleteMatching("page_post#{$post->id}*"); cache() - ->deleteMatching("page_status#{$reply->id}*"); + ->deleteMatching("page_post#{$reply->id}*"); }); /** * @param Actor $actor - * @param Status $status + * @param Post $post */ -Events::on('on_status_favourite', function ($actor, $status): void { - if ($status->episode_id) { - model('EpisodeModel') - ->where('id', $status->episode_id) - ->increment('favourites_total'); - } - - if ($status->actor->is_podcast) { +Events::on('on_post_favourite', function ($actor, $post): void { + if ($post->actor->is_podcast) { cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); } cache() - ->deleteMatching("page_status#{$status->id}*"); + ->deleteMatching("page_post#{$post->id}*"); - if ($status->in_reply_to_id !== null) { - cache()->deleteMatching("page_status#{$status->in_reply_to_id}*"); + if ($post->in_reply_to_id !== null) { + cache()->deleteMatching("page_post#{$post->in_reply_to_id}*"); } if ($actor->is_podcast) { @@ -321,27 +296,21 @@ Events::on('on_status_favourite', function ($actor, $status): void { /** * @param Actor $actor - * @param Status $status + * @param Post $post */ -Events::on('on_status_undo_favourite', function ($actor, $status): void { - if ($status->episode_id) { - model('EpisodeModel') - ->where('id', $status->episode_id) - ->decrement('favourites_total'); - } - - if ($status->actor->is_podcast) { +Events::on('on_post_undo_favourite', function ($actor, $post): void { + if ($post->actor->is_podcast) { cache() - ->deleteMatching("podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("podcast#{$post->actor->podcast->id}*"); cache() - ->deleteMatching("page_podcast#{$status->actor->podcast->id}*"); + ->deleteMatching("page_podcast#{$post->actor->podcast->id}*"); } cache() - ->deleteMatching("page_status#{$status->id}*"); + ->deleteMatching("page_post#{$post->id}*"); - if ($status->in_reply_to_id !== null) { - cache()->deleteMatching("page_status#{$status->in_reply_to_id}*"); + if ($post->in_reply_to_id !== null) { + cache()->deleteMatching("page_post#{$post->in_reply_to_id}*"); } if ($actor->is_podcast) { @@ -356,7 +325,7 @@ Events::on('on_block_actor', function (int $actorId): void { cache() ->deleteMatching('podcast*'); cache() - ->deleteMatching('page_status*'); + ->deleteMatching('page_post*'); }); Events::on('on_unblock_actor', function (int $actorId): void { @@ -364,7 +333,7 @@ Events::on('on_unblock_actor', function (int $actorId): void { cache() ->deleteMatching('podcast*'); cache() - ->deleteMatching('page_status*'); + ->deleteMatching('page_post*'); }); Events::on('on_block_domain', function (string $domainName): void { @@ -372,7 +341,7 @@ Events::on('on_block_domain', function (string $domainName): void { cache() ->deleteMatching('podcast*'); cache() - ->deleteMatching('page_status*'); + ->deleteMatching('page_post*'); }); Events::on('on_unblock_domain', function (string $domainName): void { @@ -380,5 +349,5 @@ Events::on('on_unblock_domain', function (string $domainName): void { cache() ->deleteMatching('podcast*'); cache() - ->deleteMatching('page_status*'); + ->deleteMatching('page_post*'); }); diff --git a/app/Config/Routes.php b/app/Config/Routes.php index f75b2240..88d4be2e 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -32,10 +32,10 @@ $routes->setAutoRoute(false); */ $routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}'); -$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,191}'); +$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,128}'); $routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}'); $routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding'); -$routes->addPlaceholder('statusAction', '\bfavourite|\breblog|\breply'); +$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply'); $routes->addPlaceholder('embeddablePlayerTheme', '\blight|\bdark|\blight-transparent|\bdark-transparent'); $routes->addPlaceholder( 'uuid', @@ -416,6 +416,25 @@ $routes->group( ], ); }); + + $routes->group('comments', function ($routes): void { + $routes->post( + 'new', + 'EpisodeController::attemptCommentCreate/$1/$2', + [ + 'as' => 'comment-attempt-create', + 'filter' => 'permission:podcast-manage_publications', + ] + ); + $routes->post( + 'delete', + 'EpisodeController::attemptCommentDelete/$1/$2', + [ + 'as' => 'comment-attempt-delete', + 'filter' => 'permission:podcast-manage_publications', + ] + ); + }); }); }); @@ -752,6 +771,12 @@ $routes->group('@(:podcastHandle)', function ($routes): void { 'controller-method' => 'EpisodeController::comments/$1/$2', ], ]); + $routes->get('comments/(:uuid)', 'EpisodeController::comment/$1/$2/$3', [ + 'as' => 'comment', + ]); + $routes->get('comments/(:uuid)/replies', 'EpisodeController::commentReplies/$1/$2/$3', [ + 'as' => 'comment-replies', + ]); $routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [ 'as' => 'episode-oembed-json', ]); @@ -803,73 +828,74 @@ $routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [ * Overwriting ActivityPub routes file */ $routes->group('@(:podcastHandle)', function ($routes): void { - $routes->post('statuses/new', 'StatusController::attemptCreate/$1', [ - 'as' => 'status-attempt-create', + $routes->post('posts/new', 'PostController::attemptCreate/$1', [ + 'as' => 'post-attempt-create', 'filter' => 'permission:podcast-manage_publications', ]); - // Status - $routes->group('statuses/(:uuid)', function ($routes): void { + + // Post + $routes->group('posts/(:uuid)', function ($routes): void { $routes->options('/', 'ActivityPubController::preflight'); - $routes->get('/', 'StatusController::view/$1/$2', [ - 'as' => 'status', + $routes->get('/', 'PostController::view/$1/$2', [ + 'as' => 'post', 'alternate-content' => [ 'application/activity+json' => [ 'namespace' => 'ActivityPub\Controllers', - 'controller-method' => 'StatusController/$2', + 'controller-method' => 'PostController/$2', ], 'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [ 'namespace' => 'ActivityPub\Controllers', - 'controller-method' => 'StatusController/$2', + 'controller-method' => 'PostController/$2', ], ], ]); $routes->options('replies', 'ActivityPubController::preflight'); - $routes->get('replies', 'StatusController/$1/$2', [ - 'as' => 'status-replies', + $routes->get('replies', 'PostController/$1/$2', [ + 'as' => 'post-replies', 'alternate-content' => [ 'application/activity+json' => [ 'namespace' => 'ActivityPub\Controllers', - 'controller-method' => 'StatusController::replies/$2', + 'controller-method' => 'PostController::replies/$2', ], 'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [ 'namespace' => 'ActivityPub\Controllers', - 'controller-method' => 'StatusController::replies/$2', + 'controller-method' => 'PostController::replies/$2', ], ], ]); // Actions - $routes->post('action', 'StatusController::attemptAction/$1/$2', [ - 'as' => 'status-attempt-action', + $routes->post('action', 'PostController::attemptAction/$1/$2', [ + 'as' => 'post-attempt-action', 'filter' => 'permission:podcast-interact_as', ]); $routes->post( 'block-actor', - 'StatusController::attemptBlockActor/$1/$2', + 'PostController::attemptBlockActor/$1/$2', [ - 'as' => 'status-attempt-block-actor', + 'as' => 'post-attempt-block-actor', 'filter' => 'permission:fediverse-block_actors', ], ); $routes->post( 'block-domain', - 'StatusController::attemptBlockDomain/$1/$2', + 'PostController::attemptBlockDomain/$1/$2', [ - 'as' => 'status-attempt-block-domain', + 'as' => 'post-attempt-block-domain', 'filter' => 'permission:fediverse-block_domains', ], ); - $routes->post('delete', 'StatusController::attemptDelete/$1/$2', [ - 'as' => 'status-attempt-delete', + $routes->post('delete', 'PostController::attemptDelete/$1/$2', [ + 'as' => 'post-attempt-delete', 'filter' => 'permission:podcast-manage_publications', ]); $routes->get( - 'remote/(:statusAction)', - 'StatusController::remoteAction/$1/$2/$3', + 'remote/(:postAction)', + 'PostController::remoteAction/$1/$2/$3', [ - 'as' => 'status-remote-action', + 'as' => 'post-remote-action', ], ); }); diff --git a/app/Controllers/Admin/EpisodeController.php b/app/Controllers/Admin/EpisodeController.php index bacf575f..fea2490d 100644 --- a/app/Controllers/Admin/EpisodeController.php +++ b/app/Controllers/Admin/EpisodeController.php @@ -10,15 +10,17 @@ declare(strict_types=1); namespace App\Controllers\Admin; +use App\Entities\Comment; use App\Entities\Episode; use App\Entities\Image; use App\Entities\Location; use App\Entities\Podcast; -use App\Entities\Status; +use App\Entities\Post; +use App\Models\CommentModel; use App\Models\EpisodeModel; use App\Models\PodcastModel; +use App\Models\PostModel; use App\Models\SoundbiteModel; -use App\Models\StatusModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; @@ -429,7 +431,7 @@ class EpisodeController extends BaseController $db = db_connect(); $db->transStart(); - $newStatus = new Status([ + $newPost = new Post([ 'actor_id' => $this->podcast->actor_id, 'episode_id' => $this->episode->id, 'message' => $this->request->getPost('message'), @@ -456,15 +458,15 @@ class EpisodeController extends BaseController $this->episode->published_at = Time::now(); } - $newStatus->published_at = $this->episode->published_at; + $newPost->published_at = $this->episode->published_at; - $statusModel = new StatusModel(); - if (! $statusModel->addStatus($newStatus)) { + $postModel = new PostModel(); + if (! $postModel->addPost($newPost)) { $db->transRollback(); return redirect() ->back() ->withInput() - ->with('errors', $statusModel->errors()); + ->with('errors', $postModel->errors()); } $episodeModel = new EpisodeModel(); @@ -489,7 +491,7 @@ class EpisodeController extends BaseController $data = [ 'podcast' => $this->podcast, 'episode' => $this->episode, - 'status' => (new StatusModel()) + 'post' => (new PostModel()) ->where([ 'actor_id' => $this->podcast->actor_id, 'episode_id' => $this->episode->id, @@ -513,7 +515,7 @@ class EpisodeController extends BaseController public function attemptPublishEdit(): RedirectResponse { $rules = [ - 'status_id' => 'required', + 'post_id' => 'required', 'publication_method' => 'required', 'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty', @@ -549,19 +551,19 @@ class EpisodeController extends BaseController $this->episode->published_at = Time::now(); } - $status = (new StatusModel())->getStatusById($this->request->getPost('status_id')); + $post = (new PostModel())->getPostById($this->request->getPost('post_id')); - if ($status !== null) { - $status->message = $this->request->getPost('message'); - $status->published_at = $this->episode->published_at; + if ($post !== null) { + $post->message = $this->request->getPost('message'); + $post->published_at = $this->episode->published_at; - $statusModel = new StatusModel(); - if (! $statusModel->editStatus($status)) { + $postModel = new PostModel(); + if (! $postModel->editPost($post)) { $db->transRollback(); return redirect() ->back() ->withInput() - ->with('errors', $statusModel->errors()); + ->with('errors', $postModel->errors()); } } @@ -585,14 +587,14 @@ class EpisodeController extends BaseController $db = db_connect(); $db->transStart(); - $statusModel = new StatusModel(); - $status = $statusModel + $postModel = new PostModel(); + $post = $postModel ->where([ 'actor_id' => $this->podcast->actor_id, 'episode_id' => $this->episode->id, ]) ->first(); - $statusModel->removeStatus($status); + $postModel->removePost($post); $this->episode->published_at = null; @@ -656,13 +658,13 @@ class EpisodeController extends BaseController $db->transStart(); - $allStatusesLinkedToEpisode = (new StatusModel()) + $allPostsLinkedToEpisode = (new PostModel()) ->where([ 'episode_id' => $this->episode->id, ]) ->findAll(); - foreach ($allStatusesLinkedToEpisode as $status) { - (new StatusModel())->removeStatus($status); + foreach ($allPostsLinkedToEpisode as $post) { + (new PostModel())->removePost($post); } // set episode published_at to null to unpublish @@ -782,4 +784,41 @@ class EpisodeController extends BaseController ]); return view('admin/episode/embeddable_player', $data); } + + public function attemptCommentCreate(): RedirectResponse + { + $rules = [ + 'message' => 'required|max_length[500]', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $message = $this->request->getPost('message'); + + $newComment = new Comment([ + 'actor_id' => interact_as_actor_id(), + 'episode_id' => $this->episode->id, + 'message' => $message, + 'created_at' => new Time('now'), + 'created_by' => user_id(), + ]); + + $commentModel = new CommentModel(); + if ( + ! $commentModel->addComment($newComment, true) + ) { + return redirect() + ->back() + ->withInput() + ->with('errors', $commentModel->errors()); + } + + // Comment has been successfully created + return redirect()->back(); + } } diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php index e51ba7b7..4e44af57 100644 --- a/app/Controllers/EpisodeController.php +++ b/app/Controllers/EpisodeController.php @@ -15,8 +15,10 @@ use ActivityPub\Objects\OrderedCollectionPage; use Analytics\AnalyticsTrait; use App\Entities\Episode; use App\Entities\Podcast; +use App\Libraries\CommentObject; use App\Libraries\NoteObject; use App\Libraries\PodcastEpisode; +use App\Models\CommentModel; use App\Models\EpisodeModel; use App\Models\PodcastModel; use CodeIgniter\Database\BaseBuilder; @@ -219,10 +221,10 @@ class EpisodeController extends BaseController /** * get comments: aggregated replies from posts referring to the episode */ - $episodeComments = model('StatusModel') + $episodeComments = model('PostModel') ->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder { return $builder->select('id') - ->from('activitypub_statuses') + ->from('activitypub_posts') ->where('episode_id', $this->episode->id); }) ->where('`published_at` <= NOW()', null, false) @@ -254,4 +256,57 @@ class EpisodeController extends BaseController ->setHeader('Access-Control-Allow-Origin', '*') ->setBody($collection->toJSON()); } + + /** + * @noRector ReturnTypeDeclarationRector + */ + public function comment(string $commentId): Response + { + if ( + ($comment = (new CommentModel())->getCommentById($commentId)) === null + ) { + throw PageNotFoundException::forPageNotFound(); + } + + $commentObject = new CommentObject($comment); + + return $this->response + ->setContentType('application/json') + ->setBody($commentObject->toJSON()); + } + + public function commentReplies(string $commentId): Response + { + /** + * get comment replies + */ + $commentReplies = model('CommentModel', false) + ->where('in_reply_to_id', service('uuid')->fromString($commentId)->getBytes()) + ->orderBy('created_at', 'ASC'); + + $pageNumber = (int) $this->request->getGet('page'); + + if ($pageNumber < 1) { + $commentReplies->paginate(12); + $pager = $commentReplies->pager; + $collection = new OrderedCollectionObject(null, $pager); + } else { + $paginatedReplies = $commentReplies->paginate(12, 'default', $pageNumber); + $pager = $commentReplies->pager; + + $orderedItems = []; + if ($paginatedReplies !== null) { + foreach ($paginatedReplies as $reply) { + $replyObject = new CommentObject($reply); + $orderedItems[] = $replyObject; + } + } + + $collection = new OrderedCollectionPage($pager, $orderedItems); + } + + return $this->response + ->setContentType('application/activity+json') + ->setBody($collection->toJSON()); + } } diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php index 2322535e..a21f117e 100644 --- a/app/Controllers/PodcastController.php +++ b/app/Controllers/PodcastController.php @@ -18,7 +18,7 @@ use App\Libraries\PodcastActor; use App\Libraries\PodcastEpisode; use App\Models\EpisodeModel; use App\Models\PodcastModel; -use App\Models\StatusModel; +use App\Models\PostModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\Response; @@ -81,7 +81,7 @@ class PodcastController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ 'podcast' => $this->podcast, - 'statuses' => (new StatusModel())->getActorPublishedStatuses($this->podcast->actor_id), + 'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id), ]; // if user is logged in then send to the authenticated activity view diff --git a/app/Controllers/StatusController.php b/app/Controllers/PostController.php similarity index 77% rename from app/Controllers/StatusController.php rename to app/Controllers/PostController.php index c6f5c2f0..c3761da6 100644 --- a/app/Controllers/StatusController.php +++ b/app/Controllers/PostController.php @@ -10,21 +10,21 @@ declare(strict_types=1); namespace App\Controllers; -use ActivityPub\Controllers\StatusController as ActivityPubStatusController; -use ActivityPub\Entities\Status as ActivityPubStatus; +use ActivityPub\Controllers\PostController as ActivityPubPostController; +use ActivityPub\Entities\Post as ActivityPubPost; use Analytics\AnalyticsTrait; use App\Entities\Actor; use App\Entities\Podcast; -use App\Entities\Status as CastopodStatus; +use App\Entities\Post as CastopodPost; use App\Models\EpisodeModel; use App\Models\PodcastModel; -use App\Models\StatusModel; +use App\Models\PostModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\URI; use CodeIgniter\I18n\Time; -class StatusController extends ActivityPubStatusController +class PostController extends ActivityPubPostController { use AnalyticsTrait; @@ -50,9 +50,9 @@ class StatusController extends ActivityPubStatusController if ( count($params) > 1 && - ($status = (new StatusModel())->getStatusById($params[1])) !== null + ($post = (new PostModel())->getPostById($params[1])) !== null ) { - $this->status = $status; + $this->post = $post; unset($params[0]); unset($params[1]); @@ -72,7 +72,7 @@ class StatusController extends ActivityPubStatusController '_', array_filter([ 'page', - "status#{$this->status->id}", + "post#{$this->post->id}", service('request') ->getLocale(), can_user_interact() ? '_authenticated' : null, @@ -83,15 +83,15 @@ class StatusController extends ActivityPubStatusController $data = [ 'podcast' => $this->podcast, 'actor' => $this->actor, - 'status' => $this->status, + 'post' => $this->post, ]; // if user is logged in then send to the authenticated activity view if (can_user_interact()) { helper('form'); - return view('podcast/status_authenticated', $data); + return view('podcast/post_authenticated', $data); } - return view('podcast/status', $data, [ + return view('podcast/post', $data, [ 'cache' => DECADE, 'cache_name' => $cacheName, ]); @@ -116,7 +116,7 @@ class StatusController extends ActivityPubStatusController $message = $this->request->getPost('message'); - $newStatus = new CastopodStatus([ + $newPost = new CastopodPost([ 'actor_id' => interact_as_actor_id(), 'published_at' => Time::now(), 'created_by' => user_id(), @@ -129,23 +129,23 @@ class StatusController extends ActivityPubStatusController ($params = extract_params_from_episode_uri(new URI($episodeUri))) && ($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug'])) ) { - $newStatus->episode_id = $episode->id; + $newPost->episode_id = $episode->id; } - $newStatus->message = $message; + $newPost->message = $message; - $statusModel = new StatusModel(); + $postModel = new PostModel(); if ( - ! $statusModel - ->addStatus($newStatus, ! (bool) $newStatus->episode_id, true) + ! $postModel + ->addPost($newPost, ! (bool) $newPost->episode_id, true) ) { return redirect() ->back() ->withInput() - ->with('errors', $statusModel->errors()); + ->with('errors', $postModel->errors()); } - // Status has been successfully created + // Post has been successfully created return redirect()->back(); } @@ -162,36 +162,36 @@ class StatusController extends ActivityPubStatusController ->with('errors', $this->validator->getErrors()); } - $newStatus = new ActivityPubStatus([ + $newPost = new ActivityPubPost([ 'actor_id' => interact_as_actor_id(), - 'in_reply_to_id' => $this->status->id, + 'in_reply_to_id' => $this->post->id, 'message' => $this->request->getPost('message'), 'published_at' => Time::now(), 'created_by' => user_id(), ]); - $statusModel = new StatusModel(); - if (! $statusModel->addReply($newStatus)) { + $postModel = new PostModel(); + if (! $postModel->addReply($newPost)) { return redirect() ->back() ->withInput() - ->with('errors', $statusModel->errors()); + ->with('errors', $postModel->errors()); } - // Reply status without preview card has been successfully created + // Reply post without preview card has been successfully created return redirect()->back(); } public function attemptFavourite(): RedirectResponse { - model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->status); + model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->post); return redirect()->back(); } public function attemptReblog(): RedirectResponse { - (new StatusModel())->toggleReblog(interact_as_actor(), $this->status); + (new PostModel())->toggleReblog(interact_as_actor(), $this->post); return redirect()->back(); } @@ -230,20 +230,20 @@ class StatusController extends ActivityPubStatusController $cacheName = implode( '_', - array_filter(['page', "status#{$this->status->id}", "remote_{$action}", service('request') ->getLocale()]), + array_filter(['page', "post#{$this->post->id}", "remote_{$action}", service('request') ->getLocale()]), ); if (! ($cachedView = cache($cacheName))) { $data = [ 'podcast' => $this->podcast, 'actor' => $this->actor, - 'status' => $this->status, + 'post' => $this->post, 'action' => $action, ]; helper('form'); - return view('podcast/status_remote_action', $data, [ + return view('podcast/post_remote_action', $data, [ 'cache' => DECADE, 'cache_name' => $cacheName, ]); diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2020-06-05-170000_add_episodes.php index a6d51796..310b1b70 100644 --- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php +++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php @@ -38,7 +38,7 @@ class AddEpisodes extends Migration ], 'slug' => [ 'type' => 'VARCHAR', - 'constraint' => 191, + 'constraint' => 128, ], 'audio_file_path' => [ 'type' => 'VARCHAR', @@ -147,17 +147,12 @@ class AddEpisodes extends Migration 'type' => 'JSON', 'null' => true, ], - 'favourites_total' => [ + 'posts_count' => [ 'type' => 'INT', 'unsigned' => true, 'default' => 0, ], - 'reblogs_total' => [ - 'type' => 'INT', - 'unsigned' => true, - 'default' => 0, - ], - 'statuses_total' => [ + 'comments_count' => [ 'type' => 'INT', 'unsigned' => true, 'default' => 0, diff --git a/app/Database/Migrations/2020-08-17-150000_add_pages.php b/app/Database/Migrations/2020-08-17-150000_add_pages.php index cee4ef58..ef5e2a3b 100644 --- a/app/Database/Migrations/2020-08-17-150000_add_pages.php +++ b/app/Database/Migrations/2020-08-17-150000_add_pages.php @@ -30,7 +30,7 @@ class AddPages extends Migration ], 'slug' => [ 'type' => 'VARCHAR', - 'constraint' => 191, + 'constraint' => 128, 'unique' => true, ], 'content_markdown' => [ diff --git a/app/Database/Migrations/2021-02-23-100000_add_episode_id_to_statuses.php b/app/Database/Migrations/2021-02-23-100000_add_episode_id_to_posts.php similarity index 52% rename from app/Database/Migrations/2021-02-23-100000_add_episode_id_to_statuses.php rename to app/Database/Migrations/2021-02-23-100000_add_episode_id_to_posts.php index a81a782f..b34c801f 100644 --- a/app/Database/Migrations/2021-02-23-100000_add_episode_id_to_statuses.php +++ b/app/Database/Migrations/2021-02-23-100000_add_episode_id_to_posts.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * Class AddEpisodeIdToStatuses Adds episode_id field to activitypub_statuses table in database + * Class AddEpisodeIdToPosts Adds episode_id field to activitypub_posts table in database * * @copyright 2020 Podlibre * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 @@ -14,23 +14,23 @@ namespace App\Database\Migrations; use CodeIgniter\Database\Migration; -class AddEpisodeIdToStatuses extends Migration +class AddEpisodeIdToPosts extends Migration { public function up(): void { $prefix = $this->db->getPrefix(); $createQuery = <<db->query($createQuery); } public function down(): void { - $this->forge->dropForeignKey('activitypub_statuses', 'activitypub_statuses_episode_id_foreign'); - $this->forge->dropColumn('activitypub_statuses', 'episode_id'); + $this->forge->dropForeignKey('activitypub_posts', 'activitypub_posts_episode_id_foreign'); + $this->forge->dropColumn('activitypub_posts', 'episode_id'); } } diff --git a/app/Database/Migrations/2021-03-09-113000_add_created_by_to_statuses.php b/app/Database/Migrations/2021-03-09-113000_add_created_by_to_posts.php similarity index 52% rename from app/Database/Migrations/2021-03-09-113000_add_created_by_to_statuses.php rename to app/Database/Migrations/2021-03-09-113000_add_created_by_to_posts.php index 2f9a29d6..9ccd81bd 100644 --- a/app/Database/Migrations/2021-03-09-113000_add_created_by_to_statuses.php +++ b/app/Database/Migrations/2021-03-09-113000_add_created_by_to_posts.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * Class AddCreatedByToStatuses Adds created_by field to activitypub_statuses table in database + * Class AddCreatedByToPosts Adds created_by field to activitypub_posts table in database * * @copyright 2020 Podlibre * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 @@ -14,23 +14,23 @@ namespace App\Database\Migrations; use CodeIgniter\Database\Migration; -class AddCreatedByToStatuses extends Migration +class AddCreatedByToPosts extends Migration { public function up(): void { $prefix = $this->db->getPrefix(); $createQuery = <<db->query($createQuery); } public function down(): void { - $this->forge->dropForeignKey('activitypub_statuses', 'activitypub_statuses_created_by_foreign'); - $this->forge->dropColumn('activitypub_statuses', 'created_by'); + $this->forge->dropForeignKey('activitypub_posts', 'activitypub_posts_created_by_foreign'); + $this->forge->dropColumn('activitypub_posts', 'created_by'); } } diff --git a/app/Database/Migrations/2021-08-12-150000_add_comments.php b/app/Database/Migrations/2021-08-12-150000_add_comments.php new file mode 100644 index 00000000..612d9f63 --- /dev/null +++ b/app/Database/Migrations/2021-08-12-150000_add_comments.php @@ -0,0 +1,85 @@ +forge->addField([ + 'id' => [ + 'type' => 'BINARY', + 'constraint' => 16, + ], + 'uri' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + ], + 'episode_id' => [ + 'type' => 'INT', + 'unsigned' => true, + ], + 'actor_id' => [ + 'type' => 'INT', + 'unsigned' => true, + ], + 'in_reply_to_id' => [ + 'type' => 'BINARY', + 'constraint' => 16, + 'null' => true, + ], + 'message' => [ + 'type' => 'VARCHAR', + 'constraint' => 500, + 'null' => true, + ], + 'message_html' => [ + 'type' => 'VARCHAR', + 'constraint' => 600, + 'null' => true, + ], + 'likes_count' => [ + 'type' => 'INT', + 'unsigned' => true, + ], + 'dislikes_count' => [ + 'type' => 'INT', + 'unsigned' => true, + ], + 'replies_count' => [ + 'type' => 'INT', + 'unsigned' => true, + ], + 'created_at' => [ + 'type' => 'DATETIME', + ], + 'created_by' => [ + 'type' => 'INT', + 'unsigned' => true, + 'null' => true, + ], + ]); + $this->forge->addPrimaryKey('id'); + $this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('created_by', 'users', 'id'); + $this->forge->createTable('comments'); + } + + public function down(): void + { + $this->forge->dropTable('comments'); + } +} diff --git a/app/Database/Seeds/AuthSeeder.php b/app/Database/Seeds/AuthSeeder.php index 1e46d1ad..3a76e362 100644 --- a/app/Database/Seeds/AuthSeeder.php +++ b/app/Database/Seeds/AuthSeeder.php @@ -162,13 +162,13 @@ class AuthSeeder extends Seeder [ 'name' => 'manage_publications', 'description' => - 'Publish / unpublish episodes & statuses of a podcast', + 'Publish / unpublish episodes & posts of a podcast', 'has_permission' => ['podcast_admin'], ], [ 'name' => 'interact_as', 'description' => - 'Interact as the podcast to favourite / share or reply to statuses.', + 'Interact as the podcast to favourite / share or reply to posts.', 'has_permission' => ['podcast_admin'], ], ], diff --git a/app/Entities/Comment.php b/app/Entities/Comment.php new file mode 100644 index 00000000..6235802f --- /dev/null +++ b/app/Entities/Comment.php @@ -0,0 +1,110 @@ + + */ + protected $casts = [ + 'id' => 'string', + 'uri' => 'string', + 'episode_id' => 'integer', + 'actor_id' => 'integer', + 'in_reply_to_id' => '?string', + 'message' => 'string', + 'message_html' => 'string', + 'likes_count' => 'integer', + 'dislikes_count' => 'integer', + 'replies_count' => 'integer', + 'created_by' => 'integer', + 'is_from_post' => 'boolean', + ]; + + /** + * Returns the comment's attached episode + */ + public function getEpisode(): ?Episode + { + if ($this->episode_id === null) { + throw new RuntimeException('Comment must have an episode_id before getting episode.'); + } + + if (! $this->episode instanceof Episode) { + $this->episode = (new EpisodeModel())->getEpisodeById($this->episode_id); + } + + return $this->episode; + } + + /** + * Returns the comment's actor + */ + public function getActor(): Actor + { + if ($this->actor_id === null) { + throw new RuntimeException('Comment must have an actor_id before getting actor.'); + } + + if ($this->actor === null) { + $this->actor = model('ActorModel', false) + ->getActorById($this->actor_id); + } + + return $this->actor; + } + + public function setMessage(string $message): static + { + helper('activitypub'); + + $messageWithoutTags = strip_tags($message); + + $this->attributes['message'] = $messageWithoutTags; + $this->attributes['message_html'] = str_replace("\n", '
', linkify($messageWithoutTags)); + + return $this; + } +} diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 31ae9da2..7b976a2e 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -11,10 +11,11 @@ declare(strict_types=1); namespace App\Entities; use App\Libraries\SimpleRSSElement; +use App\Models\CommentModel; use App\Models\PersonModel; use App\Models\PodcastModel; +use App\Models\PostModel; use App\Models\SoundbiteModel; -use App\Models\StatusModel; use CodeIgniter\Entity\Entity; use CodeIgniter\Files\File; use CodeIgniter\HTTP\Files\UploadedFile; @@ -65,9 +66,8 @@ use RuntimeException; * @property string|null $location_osm * @property array|null $custom_rss * @property string $custom_rss_string - * @property int $favourites_total - * @property int $reblogs_total - * @property int $statuses_total + * @property int $posts_count + * @property int $comments_count * @property int $created_by * @property int $updated_by * @property string $publication_status; @@ -117,12 +117,12 @@ class Episode extends Entity protected ?array $soundbites = null; /** - * @var Status[]|null + * @var Post[]|null */ - protected ?array $statuses = null; + protected ?array $posts = null; /** - * @var Status[]|null + * @var Comment[]|null */ protected ?array $comments = null; @@ -168,9 +168,8 @@ class Episode extends Entity 'location_geo' => '?string', 'location_osm' => '?string', 'custom_rss' => '?json-array', - 'favourites_total' => 'integer', - 'reblogs_total' => 'integer', - 'statuses_total' => 'integer', + 'posts_count' => 'integer', + 'comments_count' => 'integer', 'created_by' => 'integer', 'updated_by' => 'integer', ]; @@ -387,23 +386,23 @@ class Episode extends Entity } /** - * @return Status[] + * @return Post[] */ - public function getStatuses(): array + public function getPosts(): array { if ($this->id === null) { - throw new RuntimeException('Episode must be created before getting statuses.'); + throw new RuntimeException('Episode must be created before getting posts.'); } - if ($this->statuses === null) { - $this->statuses = (new StatusModel())->getEpisodeStatuses($this->id); + if ($this->posts === null) { + $this->posts = (new PostModel())->getEpisodePosts($this->id); } - return $this->statuses; + return $this->posts; } /** - * @return Status[] + * @return Comment[] */ public function getComments(): array { @@ -412,7 +411,7 @@ class Episode extends Entity } if ($this->comments === null) { - $this->comments = (new StatusModel())->getEpisodeComments($this->id); + $this->comments = (new CommentModel())->getEpisodeComments($this->id); } return $this->comments; @@ -420,7 +419,7 @@ class Episode extends Entity public function getLink(): string { - return url_to('episode', $this->getPodcast()->name, $this->attributes['slug']); + return url_to('episode', $this->getPodcast()->handle, $this->attributes['slug']); } public function getEmbeddablePlayerUrl(string $theme = null): string diff --git a/app/Entities/Status.php b/app/Entities/Post.php similarity index 83% rename from app/Entities/Status.php rename to app/Entities/Post.php index c2e305d6..a76d8633 100644 --- a/app/Entities/Status.php +++ b/app/Entities/Post.php @@ -10,7 +10,7 @@ declare(strict_types=1); namespace App\Entities; -use ActivityPub\Entities\Status as ActivityPubStatus; +use ActivityPub\Entities\Post as ActivityPubPost; use App\Models\EpisodeModel; use RuntimeException; @@ -18,7 +18,7 @@ use RuntimeException; * @property int|null $episode_id * @property Episode|null $episode */ -class Status extends ActivityPubStatus +class Post extends ActivityPubPost { protected ?Episode $episode = null; @@ -41,12 +41,12 @@ class Status extends ActivityPubStatus ]; /** - * Returns the status' attached episode + * Returns the post's attached episode */ public function getEpisode(): ?Episode { if ($this->episode_id === null) { - throw new RuntimeException('Status must have an episode_id before getting episode.'); + throw new RuntimeException('Post must have an episode_id before getting episode.'); } if (! $this->episode instanceof Episode) { diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php index 661e222c..649114ce 100644 --- a/app/Helpers/components_helper.php +++ b/app/Helpers/components_helper.php @@ -255,7 +255,7 @@ if (! function_exists('publication_button')) { /** * Publication button component * - * Displays the appropriate publication button depending on the publication status. + * Displays the appropriate publication button depending on the publication post. */ function publication_button(int $podcastId, int $episodeId, string $publicationStatus): string { diff --git a/app/Helpers/url_helper.php b/app/Helpers/url_helper.php index 461ba0c1..09bb644b 100644 --- a/app/Helpers/url_helper.php +++ b/app/Helpers/url_helper.php @@ -40,7 +40,7 @@ if (! function_exists('extract_params_from_episode_uri')) { function extract_params_from_episode_uri(URI $episodeUri): ?array { preg_match( - '~@(?P[a-zA-Z0-9\_]{1,32})\/episodes\/(?P[a-zA-Z0-9\-]{1,191})~', + '~@(?P[a-zA-Z0-9\_]{1,32})\/episodes\/(?P[a-zA-Z0-9\-]{1,128})~', $episodeUri->getPath(), $matches, ); diff --git a/app/Language/en/Comment.php b/app/Language/en/Comment.php new file mode 100644 index 00000000..7073865a --- /dev/null +++ b/app/Language/en/Comment.php @@ -0,0 +1,27 @@ + [ + 'episode_message_placeholder' => 'Write a comment...', + 'reply_to_placeholder' => 'Reply to @{actorUsername}', + 'submit' => 'Send!', + 'submit_reply' => 'Reply', + ], + 'like' => 'Like', + 'dislike' => 'Dislike', + 'replies' => '{numberOfReplies, plural, + one {# reply} + other {# replies} + }', + 'block_actor' => 'Block user @{actorUsername}', + 'block_domain' => 'Block domain @{actorDomain}', + 'delete' => 'Delete comment', +]; diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php index 7749b06a..06855b9d 100644 --- a/app/Language/en/Episode.php +++ b/app/Language/en/Episode.php @@ -16,19 +16,12 @@ return [ 'season_episode' => 'Season {seasonNumber} episode {episodeNumber}', 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', 'back_to_episodes' => 'Back to episodes of {podcast}', + 'comments' => 'Comments', 'activity' => 'Activity', 'description' => 'Description', - 'total_favourites' => '{numberOfTotalFavourites, plural, - one {# total favourite} - other {# total favourites} - }', - 'total_reblogs' => '{numberOfTotalReblogs, plural, - one {# total share} - other {# total shares} - }', - 'total_statuses' => '{numberOfTotalStatuses, plural, - one {# total post} - other {# total posts} + 'number_of_comments' => '{numberOfComments, plural, + one {# comment} + other {# comments} }', 'all_podcast_episodes' => 'All podcast episodes', 'back_to_podcast' => 'Go back to podcast', @@ -116,14 +109,14 @@ return [ 'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.', 'block' => 'Episode should be hidden from all platforms', 'block_hint' => - 'The episode show or hide status. If you want this episode removed from the Apple directory, toggle this on.', + 'The episode show or hide post. If you want this episode removed from the Apple directory, toggle this on.', 'submit_create' => 'Create episode', 'submit_edit' => 'Save episode', ], 'publish_form' => [ 'back_to_episode_dashboard' => 'Back to episode dashboard', - 'status' => 'Your announcement post', - 'status_hint' => + 'post' => 'Your announcement post', + 'post_hint' => "Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.", 'publication_date' => 'Publication date', 'publication_method' => [ diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index f6118e02..946b0b1b 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -223,7 +223,7 @@ return [ one {# follower} other {# followers} }', - 'statuses' => '{numberOfStatuses, plural, + 'posts' => '{numberOfPosts, plural, one {# post} other {# posts} }', diff --git a/app/Language/en/Status.php b/app/Language/en/Post.php similarity index 95% rename from app/Language/en/Status.php rename to app/Language/en/Post.php index 8f5f3a12..a60ef30f 100644 --- a/app/Language/en/Status.php +++ b/app/Language/en/Post.php @@ -10,7 +10,7 @@ declare(strict_types=1); return [ 'title' => "{actorDisplayName}'s post", - 'back_to_actor_statuses' => 'Back to {actor} posts', + 'back_to_actor_posts' => 'Back to {actor} posts', 'actor_shared' => '{actor} shared', 'reply_to' => 'Reply to @{actorUsername}', 'form' => [ diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php index 678ed65b..fc3d4936 100644 --- a/app/Language/fr/Episode.php +++ b/app/Language/fr/Episode.php @@ -16,19 +16,12 @@ return [ 'season_episode' => 'Saison {seasonNumber} épisode {episodeNumber}', 'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}', 'back_to_episodes' => 'Retour aux épisodes de {podcast}', + 'comments' => 'Commentaires', 'activity' => 'Activité', 'description' => 'Description', - 'total_favourites' => '{numberOfTotalFavourites, plural, - one {# favori en tout} - other {# favoris en tout} - }', - 'total_reblogs' => '{numberOfTotalReblogs, plural, - one {# partage en tout} - other {# partages en tout} - }', - 'total_statuses' => '{numberOfTotalStatuses, plural, - one {# message} - other {# messages} + 'number_of_comments' => '{numberOfComments, plural, + one {# commentaire} + other {# commentaires} }', 'all_podcast_episodes' => 'Tous les épisodes du podcast', 'back_to_podcast' => 'Revenir au podcast', @@ -125,8 +118,8 @@ return [ ], 'publish_form' => [ 'back_to_episode_dashboard' => 'Retour au tableau de bord de l’épisode', - 'status' => 'Votre message de publication', - 'status_hint' => + 'post' => 'Votre message de publication', + 'post_hint' => 'Écrivez un message pour annoncer la publication de votre épisode. Le message sera diffusé à toutes les personnes qui vous suivent dans le fédiverse et mis en évidence sur la page d’accueil de votre podcast.', 'publication_date' => 'Date de publication', 'publication_date_clear' => 'Effacer la date de publication', diff --git a/app/Language/fr/Podcast.php b/app/Language/fr/Podcast.php index 4b571ac4..1ef22cd7 100644 --- a/app/Language/fr/Podcast.php +++ b/app/Language/fr/Podcast.php @@ -225,7 +225,7 @@ return [ one {# abonné·e} other {# abonné·e·s} }', - 'statuses' => '{numberOfStatuses, plural, + 'posts' => '{numberOfPosts, plural, one {# publication} other {# publications} }', diff --git a/app/Language/fr/Status.php b/app/Language/fr/Post.php similarity index 94% rename from app/Language/fr/Status.php rename to app/Language/fr/Post.php index c19d0e29..a12bdd6f 100644 --- a/app/Language/fr/Status.php +++ b/app/Language/fr/Post.php @@ -10,7 +10,7 @@ declare(strict_types=1); return [ 'title' => 'Publication de {actorDisplayName}', - 'back_to_actor_statuses' => 'Retour aux publications de {actor}', + 'back_to_actor_posts' => 'Retour aux publications de {actor}', 'actor_shared' => '{actor} a partagé', 'reply_to' => 'Répondre à @{actorUsername}', 'form' => [ diff --git a/app/Libraries/ActivityPub/Activities/AnnounceActivity.php b/app/Libraries/ActivityPub/Activities/AnnounceActivity.php index 3eb4ba50..75988ac6 100644 --- a/app/Libraries/ActivityPub/Activities/AnnounceActivity.php +++ b/app/Libraries/ActivityPub/Activities/AnnounceActivity.php @@ -14,19 +14,19 @@ declare(strict_types=1); namespace ActivityPub\Activities; use ActivityPub\Core\Activity; -use ActivityPub\Entities\Status; +use ActivityPub\Entities\Post; class AnnounceActivity extends Activity { protected string $type = 'Announce'; - public function __construct(Status $reblogStatus) + public function __construct(Post $reblogPost) { - $this->actor = $reblogStatus->actor->uri; - $this->object = $reblogStatus->reblog_of_status->uri; + $this->actor = $reblogPost->actor->uri; + $this->object = $reblogPost->reblog_of_post->uri; - $this->published = $reblogStatus->published_at->format(DATE_W3C); + $this->published = $reblogPost->published_at->format(DATE_W3C); - $this->cc = [$reblogStatus->actor->uri, $reblogStatus->actor->followers_url]; + $this->cc = [$reblogPost->actor->uri, $reblogPost->actor->followers_url]; } } diff --git a/app/Libraries/ActivityPub/Config/Routes.php b/app/Libraries/ActivityPub/Config/Routes.php index a667e721..dd5dd0b1 100644 --- a/app/Libraries/ActivityPub/Config/Routes.php +++ b/app/Libraries/ActivityPub/Config/Routes.php @@ -13,7 +13,7 @@ $routes->addPlaceholder( 'uuid', '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}', ); -$routes->addPlaceholder('statusAction', '\bfavourite|\breblog|\breply'); +$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply'); /** * ActivityPub routes file @@ -54,24 +54,24 @@ $routes->group('', [ ]); }); - // Status - $routes->post('statuses/new', 'StatusController::attemptCreate/$1', [ - 'as' => 'status-attempt-create', + // Post + $routes->post('posts/new', 'PostController::attemptCreate/$1', [ + 'as' => 'post-attempt-create', ]); - $routes->get('statuses/(:uuid)', 'StatusController/$1', [ - 'as' => 'status', + $routes->get('posts/(:uuid)', 'PostController/$1', [ + 'as' => 'post', ]); - $routes->get('statuses/(:uuid)/replies', 'StatusController/$1', [ - 'as' => 'status-replies', + $routes->get('posts/(:uuid)/replies', 'PostController/$1', [ + 'as' => 'post-replies', ]); $routes->post( - 'statuses/(:uuid)/remote/(:statusAction)', - 'StatusController::attemptRemoteAction/$1/$2/$3', + 'posts/(:uuid)/remote/(:postAction)', + 'PostController::attemptRemoteAction/$1/$2/$3', [ - 'as' => 'status-attempt-remote-action', + 'as' => 'post-attempt-remote-action', ], ); diff --git a/app/Libraries/ActivityPub/Controllers/ActorController.php b/app/Libraries/ActivityPub/Controllers/ActorController.php index 6bb9167c..7a940897 100644 --- a/app/Libraries/ActivityPub/Controllers/ActorController.php +++ b/app/Libraries/ActivityPub/Controllers/ActorController.php @@ -12,7 +12,7 @@ namespace ActivityPub\Controllers; use ActivityPub\Config\ActivityPub; use ActivityPub\Entities\Actor; -use ActivityPub\Entities\Status; +use ActivityPub\Entities\Post; use ActivityPub\Objects\OrderedCollectionObject; use ActivityPub\Objects\OrderedCollectionPage; use CodeIgniter\Controller; @@ -101,30 +101,30 @@ class ActorController extends Controller ->setJSON([]); } - $replyToStatus = model('StatusModel') - ->getStatusByUri($payload->object->inReplyTo); + $replyToPost = model('PostModel') + ->getPostByUri($payload->object->inReplyTo); $reply = null; - if ($replyToStatus !== null) { + if ($replyToPost !== null) { // TODO: strip content from html to retrieve message // remove all html tags and reconstruct message with mentions? extract_text_from_html($payload->object->content); - $reply = new Status([ + $reply = new Post([ 'uri' => $payload->object->id, 'actor_id' => $payloadActor->id, - 'in_reply_to_id' => $replyToStatus->id, + 'in_reply_to_id' => $replyToPost->id, 'message' => $payload->object->content, 'published_at' => Time::parse($payload->object->published), ]); } if ($reply !== null) { - $statusId = model('StatusModel') + $postId = model('PostModel') ->addReply($reply, true, false); model('ActivityModel') ->update($activityId, [ - 'status_id' => $statusId, + 'post_id' => $postId, ]); } @@ -135,12 +135,12 @@ class ActorController extends Controller return $this->response->setStatusCode(501) ->setJSON([]); case 'Delete': - $statusToDelete = model('StatusModel') - ->getStatusByUri($payload->object->id); + $postToDelete = model('PostModel') + ->getPostByUri($payload->object->id); - if ($statusToDelete !== null) { - model('StatusModel') - ->removeStatus($statusToDelete, false); + if ($postToDelete !== null) { + model('PostModel') + ->removePost($postToDelete, false); } return $this->response->setStatusCode(200) @@ -158,35 +158,35 @@ class ActorController extends Controller ->setJSON([]); case 'Like': - // get favourited status - $status = model('StatusModel') - ->getStatusByUri($payload->object); + // get favourited post + $post = model('PostModel') + ->getPostByUri($payload->object); - if ($status !== null) { + if ($post !== null) { // Like side-effect model('FavouriteModel') - ->addFavourite($payloadActor, $status, false); + ->addFavourite($payloadActor, $post, false); model('ActivityModel') ->update($activityId, [ - 'status_id' => $status->id, + 'post_id' => $post->id, ]); } return $this->response->setStatusCode(200) ->setJSON([]); case 'Announce': - $status = model('StatusModel') - ->getStatusByUri($payload->object); + $post = model('PostModel') + ->getPostByUri($payload->object); - if ($status !== null) { + if ($post !== null) { model('ActivityModel') ->update($activityId, [ - 'status_id' => $status->id, + 'post_id' => $post->id, ]); - model('StatusModel') - ->reblog($payloadActor, $status, false); + model('PostModel') + ->reblog($payloadActor, $post, false); } return $this->response->setStatusCode(200) @@ -204,45 +204,45 @@ class ActorController extends Controller return $this->response->setStatusCode(202) ->setJSON([]); case 'Like': - $status = model('StatusModel') - ->getStatusByUri($payload->object->object); + $post = model('PostModel') + ->getPostByUri($payload->object->object); - if ($status !== null) { + if ($post !== null) { // revert side-effect by removing favourite from database model('FavouriteModel') - ->removeFavourite($payloadActor, $status, false); + ->removeFavourite($payloadActor, $post, false); model('ActivityModel') ->update($activityId, [ - 'status_id' => $status->id, + 'post_id' => $post->id, ]); } return $this->response->setStatusCode(200) ->setJSON([]); case 'Announce': - $status = model('StatusModel') - ->getStatusByUri($payload->object->object); + $post = model('PostModel') + ->getPostByUri($payload->object->object); - $reblogStatus = null; - if ($status !== null) { - $reblogStatus = model('StatusModel') + $reblogPost = null; + if ($post !== null) { + $reblogPost = model('PostModel') ->where([ 'actor_id' => $payloadActor->id, 'reblog_of_id' => service('uuid') - ->fromString($status->id) + ->fromString($post->id) ->getBytes(), ]) ->first(); } - if ($reblogStatus !== null) { - model('StatusModel') - ->undoReblog($reblogStatus, false); + if ($reblogPost !== null) { + model('PostModel') + ->undoReblog($reblogPost, false); model('ActivityModel') ->update($activityId, [ - 'status_id' => $status->id, + 'post_id' => $post->id, ]); } diff --git a/app/Libraries/ActivityPub/Controllers/StatusController.php b/app/Libraries/ActivityPub/Controllers/PostController.php similarity index 81% rename from app/Libraries/ActivityPub/Controllers/StatusController.php rename to app/Libraries/ActivityPub/Controllers/PostController.php index 022304f2..73de6f5e 100644 --- a/app/Libraries/ActivityPub/Controllers/StatusController.php +++ b/app/Libraries/ActivityPub/Controllers/PostController.php @@ -11,7 +11,7 @@ declare(strict_types=1); namespace ActivityPub\Controllers; use ActivityPub\Config\ActivityPub; -use ActivityPub\Entities\Status; +use ActivityPub\Entities\Post; use ActivityPub\Objects\OrderedCollectionObject; use ActivityPub\Objects\OrderedCollectionPage; use CodeIgniter\Controller; @@ -21,14 +21,14 @@ use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\I18n\Time; -class StatusController extends Controller +class PostController extends Controller { /** * @var string[] */ protected $helpers = ['activitypub']; - protected Status $status; + protected Post $post; protected ActivityPub $config; @@ -39,11 +39,11 @@ class StatusController extends Controller public function _remap(string $method, string ...$params): mixed { - if (($status = model('StatusModel')->getStatusById($params[0])) === null) { + if (($post = model('PostModel')->getPostById($params[0])) === null) { throw PageNotFoundException::forPageNotFound(); } - $this->status = $status; + $this->post = $post; unset($params[0]); @@ -56,7 +56,7 @@ class StatusController extends Controller public function index(): Response { $noteObjectClass = $this->config->noteObject; - $noteObject = new $noteObjectClass($this->status); + $noteObject = new $noteObjectClass($this->post); return $this->response ->setContentType('application/activity+json') @@ -69,22 +69,22 @@ class StatusController extends Controller public function replies(): Response { /** - * get status replies + * get post replies */ - $statusReplies = model('StatusModel') - ->where('in_reply_to_id', service('uuid') ->fromString($this->status->id) ->getBytes()) + $postReplies = model('PostModel') + ->where('in_reply_to_id', service('uuid') ->fromString($this->post->id) ->getBytes()) ->where('`published_at` <= NOW()', null, false) ->orderBy('published_at', 'ASC'); $pageNumber = (int) $this->request->getGet('page'); if ($pageNumber < 1) { - $statusReplies->paginate(12); - $pager = $statusReplies->pager; + $postReplies->paginate(12); + $pager = $postReplies->pager; $collection = new OrderedCollectionObject(null, $pager); } else { - $paginatedReplies = $statusReplies->paginate(12, 'default', $pageNumber); - $pager = $statusReplies->pager; + $paginatedReplies = $postReplies->paginate(12, 'default', $pageNumber); + $pager = $postReplies->pager; $orderedItems = []; $noteObjectClass = $this->config->noteObject; @@ -118,21 +118,21 @@ class StatusController extends Controller ->with('errors', $this->validator->getErrors()); } - $newStatus = new Status([ + $newPost = new Post([ 'actor_id' => $this->request->getPost('actor_id'), 'message' => $this->request->getPost('message'), 'published_at' => Time::now(), ]); - if (! model('StatusModel')->addStatus($newStatus)) { + if (! model('PostModel')->addPost($newPost)) { return redirect() ->back() ->withInput() // TODO: translate - ->with('error', "Couldn't create Status"); + ->with('error', "Couldn't create Post"); } - // Status without preview card has been successfully created + // Post without preview card has been successfully created return redirect()->back(); } @@ -153,7 +153,7 @@ class StatusController extends Controller ->getActorById($this->request->getPost('actor_id')); model('FavouriteModel') - ->toggleFavourite($actor, $this->status->id); + ->toggleFavourite($actor, $this->post->id); return redirect()->back(); } @@ -174,8 +174,8 @@ class StatusController extends Controller $actor = model('ActorModel') ->getActorById($this->request->getPost('actor_id')); - model('StatusModel') - ->toggleReblog($actor, $this->status); + model('PostModel') + ->toggleReblog($actor, $this->post); return redirect()->back(); } @@ -194,14 +194,14 @@ class StatusController extends Controller ->with('errors', $this->validator->getErrors()); } - $newReplyStatus = new Status([ + $newReplyPost = new Post([ 'actor_id' => $this->request->getPost('actor_id'), - 'in_reply_to_id' => $this->status->id, + 'in_reply_to_id' => $this->post->id, 'message' => $this->request->getPost('message'), 'published_at' => Time::now(), ]); - if (! model('StatusModel')->addReply($newReplyStatus)) { + if (! model('PostModel')->addReply($newReplyPost)) { return redirect() ->back() ->withInput() @@ -209,7 +209,7 @@ class StatusController extends Controller ->with('error', "Couldn't create Reply"); } - // Reply status without preview card has been successfully created + // Reply post without preview card has been successfully created return redirect()->back(); } @@ -249,33 +249,33 @@ class StatusController extends Controller ); if (! $ostatusKey) { - // TODO: error, couldn't remote favourite/share/reply to status - // The instance doesn't allow its users remote actions on statuses + // TODO: error, couldn't remote favourite/share/reply to post + // The instance doesn't allow its users remote actions on posts return $this->response->setJSON([]); } return redirect()->to( - str_replace('{uri}', urlencode($this->status->uri), $data->links[$ostatusKey]->template), + str_replace('{uri}', urlencode($this->post->uri), $data->links[$ostatusKey]->template), ); } public function attemptBlockActor(): RedirectResponse { - model('ActorModel')->blockActor($this->status->actor->id); + model('ActorModel')->blockActor($this->post->actor->id); return redirect()->back(); } public function attemptBlockDomain(): RedirectResponse { - model('BlockedDomainModel')->blockDomain($this->status->actor->domain); + model('BlockedDomainModel')->blockDomain($this->post->actor->domain); return redirect()->back(); } public function attemptDelete(): RedirectResponse { - model('StatusModel', false)->removeStatus($this->status); + model('PostModel', false)->removePost($this->post); return redirect()->back(); } diff --git a/app/Libraries/ActivityPub/Controllers/SchedulerController.php b/app/Libraries/ActivityPub/Controllers/SchedulerController.php index 057360a3..4b7928e2 100644 --- a/app/Libraries/ActivityPub/Controllers/SchedulerController.php +++ b/app/Libraries/ActivityPub/Controllers/SchedulerController.php @@ -33,7 +33,7 @@ class SchedulerController extends Controller json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR), ); - // set activity status to delivered + // set activity post to delivered model('ActivityModel') ->update($scheduledActivity->id, [ 'task_status' => 'delivered', diff --git a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-010000_add_actors.php b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-010000_add_actors.php index 4fcc2973..d247fbc7 100644 --- a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-010000_add_actors.php +++ b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-010000_add_actors.php @@ -34,7 +34,7 @@ class AddActors extends Migration ], 'domain' => [ 'type' => 'VARCHAR', - 'constraint' => 191, + 'constraint' => 255, ], 'private_key' => [ 'type' => 'TEXT', @@ -93,7 +93,7 @@ class AddActors extends Migration 'unsigned' => true, 'default' => 0, ], - 'statuses_count' => [ + 'posts_count' => [ 'type' => 'INT', 'unsigned' => true, 'default' => 0, diff --git a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-020000_add_statuses.php b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-020000_add_posts.php similarity index 85% rename from app/Libraries/ActivityPub/Database/Migrations/2018-01-01-020000_add_statuses.php rename to app/Libraries/ActivityPub/Database/Migrations/2018-01-01-020000_add_posts.php index 68fe53fc..05cc28b4 100644 --- a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-020000_add_statuses.php +++ b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-020000_add_posts.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * Class AddStatuses Creates activitypub_statuses table in database + * Class AddPosts Creates activitypub_posts table in database * * @copyright 2021 Podlibre * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 @@ -14,7 +14,7 @@ namespace ActivityPub\Database\Migrations; use CodeIgniter\Database\Migration; -class AddStatuses extends Migration +class AddPosts extends Migration { public function up(): void { @@ -25,7 +25,7 @@ class AddStatuses extends Migration ], 'uri' => [ 'type' => 'VARCHAR', - 'constraint' => 191, + 'constraint' => 255, ], 'actor_id' => [ 'type' => 'INT', @@ -76,16 +76,16 @@ class AddStatuses extends Migration ]); $this->forge->addPrimaryKey('id'); $this->forge->addUniqueKey('uri'); - // FIXME: an actor must reblog a status only once + // FIXME: an actor must reblog a post only once // $this->forge->addUniqueKey(['actor_id', 'reblog_of_id']); $this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('in_reply_to_id', 'activitypub_statuses', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('reblog_of_id', 'activitypub_statuses', 'id', '', 'CASCADE'); - $this->forge->createTable('activitypub_statuses'); + $this->forge->addForeignKey('in_reply_to_id', 'activitypub_posts', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('reblog_of_id', 'activitypub_posts', 'id', '', 'CASCADE'); + $this->forge->createTable('activitypub_posts'); } public function down(): void { - $this->forge->dropTable('activitypub_statuses'); + $this->forge->dropTable('activitypub_posts'); } } diff --git a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_activities.php b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_activities.php index 443a3ea9..1b9e99b5 100644 --- a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_activities.php +++ b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_activities.php @@ -32,7 +32,7 @@ class AddActivities extends Migration 'unsigned' => true, 'null' => true, ], - 'status_id' => [ + 'post_id' => [ 'type' => 'BINARY', 'constraint' => 16, 'null' => true, @@ -62,7 +62,7 @@ class AddActivities extends Migration $this->forge->addPrimaryKey('id'); $this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE'); $this->forge->addForeignKey('target_actor_id', 'activitypub_actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('status_id', 'activitypub_statuses', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE'); $this->forge->createTable('activitypub_activities'); } diff --git a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_favourites.php b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_favourites.php index 049284bb..cfdc98ab 100644 --- a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_favourites.php +++ b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-100000_add_favourites.php @@ -23,15 +23,15 @@ class AddFavourites extends Migration 'type' => 'INT', 'unsigned' => true, ], - 'status_id' => [ + 'post_id' => [ 'type' => 'BINARY', 'constraint' => 16, ], ]); $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()'); - $this->forge->addPrimaryKey(['actor_id', 'status_id']); + $this->forge->addPrimaryKey(['actor_id', 'post_id']); $this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE'); - $this->forge->addForeignKey('status_id', 'activitypub_statuses', 'id', '', 'CASCADE'); + $this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE'); $this->forge->createTable('activitypub_favourites'); } diff --git a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-110000_add_statuses_preview_cards.php b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php similarity index 59% rename from app/Libraries/ActivityPub/Database/Migrations/2018-01-01-110000_add_statuses_preview_cards.php rename to app/Libraries/ActivityPub/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php index fe35855f..9b677174 100644 --- a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-110000_add_statuses_preview_cards.php +++ b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * Class AddStatusesPreviewCards Creates activitypub_statuses_preview_cards table in database + * Class AddPostsPreviewCards Creates activitypub_posts_preview_cards table in database * * @copyright 2021 Podlibre * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 @@ -14,12 +14,12 @@ namespace ActivityPub\Database\Migrations; use CodeIgniter\Database\Migration; -class AddStatusesPreviewCards extends Migration +class AddPostsPreviewCards extends Migration { public function up(): void { $this->forge->addField([ - 'status_id' => [ + 'post_id' => [ 'type' => 'BINARY', 'constraint' => 16, ], @@ -29,14 +29,14 @@ class AddStatusesPreviewCards extends Migration ], ]); - $this->forge->addPrimaryKey(['status_id', 'preview_card_id']); - $this->forge->addForeignKey('status_id', 'activitypub_statuses', 'id', '', 'CASCADE'); + $this->forge->addPrimaryKey(['post_id', 'preview_card_id']); + $this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE'); $this->forge->addForeignKey('preview_card_id', 'activitypub_preview_cards', 'id', '', 'CASCADE'); - $this->forge->createTable('activitypub_statuses_preview_cards'); + $this->forge->createTable('activitypub_posts_preview_cards'); } public function down(): void { - $this->forge->dropTable('activitypub_statuses_preview_cards'); + $this->forge->dropTable('activitypub_posts_preview_cards'); } } diff --git a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-120000_add_blocked_domains.php b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-120000_add_blocked_domains.php index b7b0b49a..dbbea722 100644 --- a/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-120000_add_blocked_domains.php +++ b/app/Libraries/ActivityPub/Database/Migrations/2018-01-01-120000_add_blocked_domains.php @@ -21,7 +21,7 @@ class AddBlockedDomains extends Migration $this->forge->addField([ 'name' => [ 'type' => 'VARCHAR', - 'constraint' => 191, + 'constraint' => 255, ], 'created_at' => [ 'type' => 'DATETIME', diff --git a/app/Libraries/ActivityPub/Entities/Activity.php b/app/Libraries/ActivityPub/Entities/Activity.php index 4a67bdb0..e60995c9 100644 --- a/app/Libraries/ActivityPub/Entities/Activity.php +++ b/app/Libraries/ActivityPub/Entities/Activity.php @@ -19,8 +19,8 @@ use RuntimeException; * @property Actor $actor * @property int|null $target_actor_id * @property Actor $target_actor - * @property string|null $status_id - * @property Status $status + * @property string|null $post_id + * @property Post $post * @property string $type * @property object $payload * @property string|null $task_status @@ -33,12 +33,12 @@ class Activity extends UuidEntity protected ?Actor $target_actor = null; - protected ?Status $status = null; + protected ?Post $post = null; /** * @var string[] */ - protected $uuids = ['id', 'status_id']; + protected $uuids = ['id', 'post_id']; /** * @var string[] @@ -52,7 +52,7 @@ class Activity extends UuidEntity 'id' => 'string', 'actor_id' => 'integer', 'target_actor_id' => '?integer', - 'status_id' => '?string', + 'post_id' => '?string', 'type' => 'string', 'payload' => 'json', 'task_status' => '?string', @@ -86,17 +86,17 @@ class Activity extends UuidEntity return $this->target_actor; } - public function getStatus(): Status + public function getPost(): Post { - if ($this->status_id === null) { - throw new RuntimeException('Activity must have a status_id before getting status.'); + if ($this->post_id === null) { + throw new RuntimeException('Activity must have a post_id before getting post.'); } - if ($this->status === null) { - $this->status = model('StatusModel', false) - ->getStatusById($this->status_id); + if ($this->post === null) { + $this->post = model('PostModel', false) + ->getPostById($this->post_id); } - return $this->status; + return $this->post; } } diff --git a/app/Libraries/ActivityPub/Entities/Actor.php b/app/Libraries/ActivityPub/Entities/Actor.php index 25845f00..bf07cf53 100644 --- a/app/Libraries/ActivityPub/Entities/Actor.php +++ b/app/Libraries/ActivityPub/Entities/Actor.php @@ -31,7 +31,7 @@ use RuntimeException; * @property string|null $outbox_url * @property string|null $followers_url * @property int $followers_count - * @property int $statuses_count + * @property int $posts_count * @property bool $is_blocked * * @property Actor[] $followers @@ -68,7 +68,7 @@ class Actor extends Entity 'outbox_url' => '?string', 'followers_url' => '?string', 'followers_count' => 'integer', - 'statuses_count' => 'integer', + 'posts_count' => 'integer', 'is_blocked' => 'boolean', ]; diff --git a/app/Libraries/ActivityPub/Entities/Favourite.php b/app/Libraries/ActivityPub/Entities/Favourite.php index 04014bb6..b3d4028a 100644 --- a/app/Libraries/ActivityPub/Entities/Favourite.php +++ b/app/Libraries/ActivityPub/Entities/Favourite.php @@ -14,20 +14,20 @@ use Michalsn\Uuid\UuidEntity; /** * @property int $actor_id - * @property string $status_id + * @property string $post_id */ class Favourite extends UuidEntity { /** * @var string[] */ - protected $uuids = ['status_id']; + protected $uuids = ['post_id']; /** * @var array */ protected $casts = [ 'actor_id' => 'integer', - 'status_id' => 'string', + 'post_id' => 'string', ]; } diff --git a/app/Libraries/ActivityPub/Entities/Status.php b/app/Libraries/ActivityPub/Entities/Post.php similarity index 66% rename from app/Libraries/ActivityPub/Entities/Status.php rename to app/Libraries/ActivityPub/Entities/Post.php index 94cfa0c7..7f33f0a9 100644 --- a/app/Libraries/ActivityPub/Entities/Status.php +++ b/app/Libraries/ActivityPub/Entities/Post.php @@ -20,9 +20,9 @@ use RuntimeException; * @property int $actor_id * @property Actor $actor * @property string|null $in_reply_to_id - * @property Status|null $reply_to_status + * @property Post|null $reply_to_post * @property string|null $reblog_of_id - * @property Status|null $reblog_of_status + * @property Post|null $reblog_of_post * @property string $message * @property string $message_html * @property int $favourites_count @@ -35,30 +35,30 @@ use RuntimeException; * @property PreviewCard|null $preview_card * * @property bool $has_replies - * @property Status[] $replies - * @property Status[] $reblogs + * @property Post[] $replies + * @property Post[] $reblogs */ -class Status extends UuidEntity +class Post extends UuidEntity { protected ?Actor $actor = null; - protected ?Status $reply_to_status = null; + protected ?Post $reply_to_post = null; - protected ?Status $reblog_of_status = null; + protected ?Post $reblog_of_post = null; protected ?PreviewCard $preview_card = null; protected bool $has_preview_card = false; /** - * @var Status[]|null + * @var Post[]|null */ protected ?array $replies = null; protected bool $has_replies = false; /** - * @var Status[]|null + * @var Post[]|null */ protected ?array $reblogs = null; @@ -89,12 +89,12 @@ class Status extends UuidEntity ]; /** - * Returns the status's actor + * Returns the post's actor */ public function getActor(): Actor { if ($this->actor_id === null) { - throw new RuntimeException('Status must have an actor_id before getting actor.'); + throw new RuntimeException('Post must have an actor_id before getting actor.'); } if ($this->actor === null) { @@ -108,12 +108,12 @@ class Status extends UuidEntity public function getPreviewCard(): ?PreviewCard { if ($this->id === null) { - throw new RuntimeException('Status must be created before getting preview_card.'); + throw new RuntimeException('Post must be created before getting preview_card.'); } if ($this->preview_card === null) { $this->preview_card = model('PreviewCardModel', false) - ->getStatusPreviewCard($this->id); + ->getPostPreviewCard($this->id); } return $this->preview_card; @@ -125,17 +125,17 @@ class Status extends UuidEntity } /** - * @return Status[] + * @return Post[] */ public function getReplies(): array { if ($this->id === null) { - throw new RuntimeException('Status must be created before getting replies.'); + throw new RuntimeException('Post must be created before getting replies.'); } if ($this->replies === null) { - $this->replies = (array) model('StatusModel', false) - ->getStatusReplies($this->id); + $this->replies = (array) model('PostModel', false) + ->getPostReplies($this->id); } return $this->replies; @@ -146,49 +146,49 @@ class Status extends UuidEntity return $this->getReplies() !== null; } - public function getReplyToStatus(): ?self + public function getReplyToPost(): ?self { if ($this->in_reply_to_id === null) { - throw new RuntimeException('Status is not a reply.'); + throw new RuntimeException('Post is not a reply.'); } - if ($this->reply_to_status === null) { - $this->reply_to_status = model('StatusModel', false) - ->getStatusById($this->in_reply_to_id); + if ($this->reply_to_post === null) { + $this->reply_to_post = model('PostModel', false) + ->getPostById($this->in_reply_to_id); } - return $this->reply_to_status; + return $this->reply_to_post; } /** - * @return Status[] + * @return Post[] */ public function getReblogs(): array { if ($this->id === null) { - throw new RuntimeException('Status must be created before getting reblogs.'); + throw new RuntimeException('Post must be created before getting reblogs.'); } if ($this->reblogs === null) { - $this->reblogs = (array) model('StatusModel', false) - ->getStatusReblogs($this->id); + $this->reblogs = (array) model('PostModel', false) + ->getPostReblogs($this->id); } return $this->reblogs; } - public function getReblogOfStatus(): ?self + public function getReblogOfPost(): ?self { if ($this->reblog_of_id === null) { - throw new RuntimeException('Status is not a reblog.'); + throw new RuntimeException('Post is not a reblog.'); } - if ($this->reblog_of_status === null) { - $this->reblog_of_status = model('StatusModel', false) - ->getStatusById($this->reblog_of_id); + if ($this->reblog_of_post === null) { + $this->reblog_of_post = model('PostModel', false) + ->getPostById($this->reblog_of_id); } - return $this->reblog_of_status; + return $this->reblog_of_post; } public function setMessage(string $message): static diff --git a/app/Libraries/ActivityPub/Entities/PreviewCard.php b/app/Libraries/ActivityPub/Entities/PreviewCard.php index 630ccde1..8303daba 100644 --- a/app/Libraries/ActivityPub/Entities/PreviewCard.php +++ b/app/Libraries/ActivityPub/Entities/PreviewCard.php @@ -14,7 +14,7 @@ use CodeIgniter\Entity\Entity; /** * @property int $id - * @property string $status_id + * @property string $post_id * @property string $url * @property string $title * @property string $description @@ -33,7 +33,7 @@ class PreviewCard extends Entity */ protected $casts = [ 'id' => 'integer', - 'status_id' => 'string', + 'post_id' => 'string', 'url' => 'string', 'title' => 'string', 'description' => 'string', diff --git a/app/Libraries/ActivityPub/Models/ActivityModel.php b/app/Libraries/ActivityPub/Models/ActivityModel.php index 9b8af37c..708d1dc2 100644 --- a/app/Libraries/ActivityPub/Models/ActivityModel.php +++ b/app/Libraries/ActivityPub/Models/ActivityModel.php @@ -31,7 +31,7 @@ class ActivityModel extends UuidModel /** * @var string[] */ - protected $uuidFields = ['id', 'status_id']; + protected $uuidFields = ['id', 'post_id']; /** * @var string[] @@ -40,7 +40,7 @@ class ActivityModel extends UuidModel 'id', 'actor_id', 'target_actor_id', - 'status_id', + 'post_id', 'type', 'payload', 'task_status', @@ -88,7 +88,7 @@ class ActivityModel extends UuidModel string $type, int $actorId, ?int $targetActorId, - ?string $statusId, + ?string $postId, string $payload, DateTimeInterface $scheduledAt = null, ?string $taskStatus = null @@ -97,7 +97,7 @@ class ActivityModel extends UuidModel [ 'actor_id' => $actorId, 'target_actor_id' => $targetActorId, - 'status_id' => $statusId, + 'post_id' => $postId, 'type' => $type, 'payload' => $payload, 'scheduled_at' => $scheduledAt, diff --git a/app/Libraries/ActivityPub/Models/ActorModel.php b/app/Libraries/ActivityPub/Models/ActorModel.php index 8fe539a1..48e7ff26 100644 --- a/app/Libraries/ActivityPub/Models/ActorModel.php +++ b/app/Libraries/ActivityPub/Models/ActorModel.php @@ -41,7 +41,7 @@ class ActorModel extends Model 'outbox_url', 'followers_url', 'followers_count', - 'statuses_count', + 'posts_count', 'is_blocked', ]; diff --git a/app/Libraries/ActivityPub/Models/FavouriteModel.php b/app/Libraries/ActivityPub/Models/FavouriteModel.php index 5ea58981..3e22785b 100644 --- a/app/Libraries/ActivityPub/Models/FavouriteModel.php +++ b/app/Libraries/ActivityPub/Models/FavouriteModel.php @@ -14,7 +14,7 @@ use ActivityPub\Activities\LikeActivity; use ActivityPub\Activities\UndoActivity; use ActivityPub\Entities\Actor; use ActivityPub\Entities\Favourite; -use ActivityPub\Entities\Status; +use ActivityPub\Entities\Post; use CodeIgniter\Events\Events; use Michalsn\Uuid\UuidModel; @@ -28,12 +28,12 @@ class FavouriteModel extends UuidModel /** * @var string[] */ - protected $uuidFields = ['status_id']; + protected $uuidFields = ['post_id']; /** * @var string[] */ - protected $allowedFields = ['actor_id', 'status_id']; + protected $allowedFields = ['actor_id', 'post_id']; /** * @var string @@ -47,32 +47,32 @@ class FavouriteModel extends UuidModel protected $updatedField; - public function addFavourite(Actor $actor, Status $status, bool $registerActivity = true): void + public function addFavourite(Actor $actor, Post $post, bool $registerActivity = true): void { $this->db->transStart(); $this->insert([ 'actor_id' => $actor->id, - 'status_id' => $status->id, + 'post_id' => $post->id, ]); - model('StatusModel') - ->where('id', service('uuid') ->fromString($status->id) ->getBytes()) + model('PostModel') + ->where('id', service('uuid') ->fromString($post->id) ->getBytes()) ->increment('favourites_count'); if ($registerActivity) { $likeActivity = new LikeActivity(); $likeActivity->set('actor', $actor->uri) - ->set('object', $status->uri); + ->set('object', $post->uri); $activityId = model('ActivityModel') ->newActivity( 'Like', $actor->id, null, - $status->id, + $post->id, $likeActivity->toJSON(), - $status->published_at, + $post->published_at, 'queued', ); @@ -84,28 +84,28 @@ class FavouriteModel extends UuidModel ]); } - Events::trigger('on_status_favourite', $actor, $status); + Events::trigger('on_post_favourite', $actor, $post); - model('StatusModel') - ->clearCache($status); + model('PostModel') + ->clearCache($post); $this->db->transComplete(); } - public function removeFavourite(Actor $actor, Status $status, bool $registerActivity = true): void + public function removeFavourite(Actor $actor, Post $post, bool $registerActivity = true): void { $this->db->transStart(); - model('StatusModel') - ->where('id', service('uuid') ->fromString($status->id) ->getBytes()) + model('PostModel') + ->where('id', service('uuid') ->fromString($post->id) ->getBytes()) ->decrement('favourites_count'); $this->db ->table('activitypub_favourites') ->where([ 'actor_id' => $actor->id, - 'status_id' => service('uuid') - ->fromString($status->id) + 'post_id' => service('uuid') + ->fromString($post->id) ->getBytes(), ]) ->delete(); @@ -117,8 +117,8 @@ class FavouriteModel extends UuidModel ->where([ 'type' => 'Like', 'actor_id' => $actor->id, - 'status_id' => service('uuid') - ->fromString($status->id) + 'post_id' => service('uuid') + ->fromString($post->id) ->getBytes(), ]) ->first(); @@ -127,7 +127,7 @@ class FavouriteModel extends UuidModel $likeActivity ->set('id', url_to('activity', $actor->username, $activity->id)) ->set('actor', $actor->uri) - ->set('object', $status->uri); + ->set('object', $post->uri); $undoActivity ->set('actor', $actor->uri) @@ -138,9 +138,9 @@ class FavouriteModel extends UuidModel 'Undo', $actor->id, null, - $status->id, + $post->id, $undoActivity->toJSON(), - $status->published_at, + $post->published_at, 'queued', ); @@ -152,10 +152,10 @@ class FavouriteModel extends UuidModel ]); } - Events::trigger('on_status_undo_favourite', $actor, $status); + Events::trigger('on_post_undo_favourite', $actor, $post); - model('StatusModel') - ->clearCache($status); + model('PostModel') + ->clearCache($post); $this->db->transComplete(); } @@ -163,19 +163,19 @@ class FavouriteModel extends UuidModel /** * Adds or removes favourite from database and increments count */ - public function toggleFavourite(Actor $actor, Status $status): void + public function toggleFavourite(Actor $actor, Post $post): void { if ( $this->where([ 'actor_id' => $actor->id, - 'status_id' => service('uuid') - ->fromString($status->id) + 'post_id' => service('uuid') + ->fromString($post->id) ->getBytes(), ])->first() ) { - $this->removeFavourite($actor, $status); + $this->removeFavourite($actor, $post); } else { - $this->addFavourite($actor, $status); + $this->addFavourite($actor, $post); } } } diff --git a/app/Libraries/ActivityPub/Models/StatusModel.php b/app/Libraries/ActivityPub/Models/PostModel.php similarity index 53% rename from app/Libraries/ActivityPub/Models/StatusModel.php rename to app/Libraries/ActivityPub/Models/PostModel.php index 3fc6b2dc..f7dc644a 100644 --- a/app/Libraries/ActivityPub/Models/StatusModel.php +++ b/app/Libraries/ActivityPub/Models/PostModel.php @@ -15,7 +15,7 @@ use ActivityPub\Activities\CreateActivity; use ActivityPub\Activities\DeleteActivity; use ActivityPub\Activities\UndoActivity; use ActivityPub\Entities\Actor; -use ActivityPub\Entities\Status; +use ActivityPub\Entities\Post; use ActivityPub\Objects\TombstoneObject; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\Query; @@ -25,12 +25,12 @@ use CodeIgniter\I18n\Time; use Exception; use Michalsn\Uuid\UuidModel; -class StatusModel extends UuidModel +class PostModel extends UuidModel { /** * @var string */ - protected $table = 'activitypub_statuses'; + protected $table = 'activitypub_posts'; /** * @var string @@ -62,7 +62,7 @@ class StatusModel extends UuidModel /** * @var string */ - protected $returnType = Status::class; + protected $returnType = Post::class; /** * @var bool @@ -87,14 +87,14 @@ class StatusModel extends UuidModel /** * @var string[] */ - protected $beforeInsert = ['setStatusId']; + protected $beforeInsert = ['setPostId']; - public function getStatusById(string $statusId): ?Status + public function getPostById(string $postId): ?Post { $cacheName = config('ActivityPub') - ->cachePrefix . "status#{$statusId}"; + ->cachePrefix . "post#{$postId}"; if (! ($found = cache($cacheName))) { - $found = $this->find($statusId); + $found = $this->find($postId); cache() ->save($cacheName, $found, DECADE); @@ -103,14 +103,14 @@ class StatusModel extends UuidModel return $found; } - public function getStatusByUri(string $statusUri): ?Status + public function getPostByUri(string $postUri): ?Post { - $hashedStatusUri = md5($statusUri); + $hashedPostUri = md5($postUri); $cacheName = config('ActivityPub') - ->cachePrefix . "status-{$hashedStatusUri}"; + ->cachePrefix . "post-{$hashedPostUri}"; if (! ($found = cache($cacheName))) { - $found = $this->where('uri', $statusUri) + $found = $this->where('uri', $postUri) ->first(); cache() @@ -121,16 +121,16 @@ class StatusModel extends UuidModel } /** - * Retrieves all published statuses for a given actor ordered by publication date + * Retrieves all published posts for a given actor ordered by publication date * - * @return Status[] + * @return Post[] */ - public function getActorPublishedStatuses(int $actorId): array + public function getActorPublishedPosts(int $actorId): array { $cacheName = config('ActivityPub') ->cachePrefix . - "actor#{$actorId}_published_statuses"; + "actor#{$actorId}_published_posts"; if (! ($found = cache($cacheName))) { $found = $this->where([ 'actor_id' => $actorId, @@ -140,20 +140,20 @@ class StatusModel extends UuidModel ->orderBy('published_at', 'DESC') ->findAll(); - $secondsToNextUnpublishedStatus = $this->getSecondsToNextUnpublishedStatuses($actorId); + $secondsToNextUnpublishedPost = $this->getSecondsToNextUnpublishedPosts($actorId); cache() - ->save($cacheName, $found, $secondsToNextUnpublishedStatus ? $secondsToNextUnpublishedStatus : DECADE); + ->save($cacheName, $found, $secondsToNextUnpublishedPost ? $secondsToNextUnpublishedPost : DECADE); } return $found; } /** - * Returns the timestamp difference in seconds between the next status to publish and the current timestamp. Returns - * false if there's no status to publish + * Returns the timestamp difference in seconds between the next post to publish and the current timestamp. Returns + * false if there's no post to publish */ - public function getSecondsToNextUnpublishedStatuses(int $actorId): int | false + public function getSecondsToNextUnpublishedPosts(int $actorId): int | false { $result = $this->select('TIMESTAMPDIFF(SECOND, NOW(), `published_at`) as timestamp_diff') ->where([ @@ -170,26 +170,26 @@ class StatusModel extends UuidModel } /** - * Retrieves all published replies for a given status. By default, it does not get replies from blocked actors. + * Retrieves all published replies for a given post. By default, it does not get replies from blocked actors. * - * @return Status[] + * @return Post[] */ - public function getStatusReplies(string $statusId, bool $withBlocked = false): array + public function getPostReplies(string $postId, bool $withBlocked = false): array { $cacheName = config('ActivityPub') ->cachePrefix . - "status#{$statusId}_replies" . + "post#{$postId}_replies" . ($withBlocked ? '_withBlocked' : ''); if (! ($found = cache($cacheName))) { if (! $withBlocked) { - $this->select('activitypub_statuses.*') - ->join('activitypub_actors', 'activitypub_actors.id = activitypub_statuses.actor_id', 'inner') + $this->select('activitypub_posts.*') + ->join('activitypub_actors', 'activitypub_actors.id = activitypub_posts.actor_id', 'inner') ->where('activitypub_actors.is_blocked', 0); } - $this->where('in_reply_to_id', $this->uuid->fromString($statusId) ->getBytes()) + $this->where('in_reply_to_id', $this->uuid->fromString($postId) ->getBytes()) ->where('`published_at` <= NOW()', null, false) ->orderBy('published_at', 'ASC'); $found = $this->findAll(); @@ -202,18 +202,18 @@ class StatusModel extends UuidModel } /** - * Retrieves all published reblogs for a given status + * Retrieves all published reblogs for a given post * - * @return Status[] + * @return Post[] */ - public function getStatusReblogs(string $statusId): array + public function getPostReblogs(string $postId): array { $cacheName = config('ActivityPub') - ->cachePrefix . "status#{$statusId}_reblogs"; + ->cachePrefix . "post#{$postId}_reblogs"; if (! ($found = cache($cacheName))) { - $found = $this->where('reblog_of_id', $this->uuid->fromString($statusId) ->getBytes()) + $found = $this->where('reblog_of_id', $this->uuid->fromString($postId) ->getBytes()) ->where('`published_at` <= NOW()', null, false) ->orderBy('published_at', 'ASC') ->findAll(); @@ -225,23 +225,23 @@ class StatusModel extends UuidModel return $found; } - public function addPreviewCard(string $statusId, int $previewCardId): Query | bool + public function addPreviewCard(string $postId, int $previewCardId): Query | bool { - return $this->db->table('activitypub_statuses_preview_cards') + return $this->db->table('activitypub_posts_preview_cards') ->insert([ - 'status_id' => $this->uuid->fromString($statusId) + 'post_id' => $this->uuid->fromString($postId) ->getBytes(), 'preview_card_id' => $previewCardId, ]); } /** - * Adds status in database along preview card if relevant + * Adds post in database along preview card if relevant * - * @return string|false returns the new status id if success or false otherwise + * @return string|false returns the new post id if success or false otherwise */ - public function addStatus( - Status $status, + public function addPost( + Post $post, bool $createPreviewCard = true, bool $registerActivity = true ): string | false { @@ -249,101 +249,101 @@ class StatusModel extends UuidModel $this->db->transStart(); - if (! ($newStatusId = $this->insert($status, true))) { + if (! ($newPostId = $this->insert($post, true))) { $this->db->transRollback(); - // Couldn't insert status + // Couldn't insert post return false; } if ($createPreviewCard) { // parse message - $messageUrls = extract_urls_from_message($status->message); + $messageUrls = extract_urls_from_message($post->message); if ( $messageUrls !== [] && ($previewCard = get_or_create_preview_card_from_url(new URI($messageUrls[0]))) && - ! $this->addPreviewCard($newStatusId, $previewCard->id) + ! $this->addPreviewCard($newPostId, $previewCard->id) ) { $this->db->transRollback(); - // problem when linking status to preview card + // problem when linking post to preview card return false; } } - model('ActorModel') - ->where('id', $status->actor_id) - ->increment('statuses_count'); + model('ActorModel', false) + ->where('id', $post->actor_id) + ->increment('posts_count'); if ($registerActivity) { - // set status id and uri to construct NoteObject - $status->id = $newStatusId; - $status->uri = url_to('status', $status->actor->username, $newStatusId); + // set post id and uri to construct NoteObject + $post->id = $newPostId; + $post->uri = url_to('post', $post->actor->username, $newPostId); $createActivity = new CreateActivity(); $noteObjectClass = config('ActivityPub') ->noteObject; $createActivity - ->set('actor', $status->actor->uri) - ->set('object', new $noteObjectClass($status)); + ->set('actor', $post->actor->uri) + ->set('object', new $noteObjectClass($post)); - $activityId = model('ActivityModel') + $activityId = model('ActivityModel', false) ->newActivity( 'Create', - $status->actor_id, + $post->actor_id, null, - $newStatusId, + $newPostId, $createActivity->toJSON(), - $status->published_at, + $post->published_at, 'queued', ); - $createActivity->set('id', url_to('activity', $status->actor->username, $activityId)); + $createActivity->set('id', url_to('activity', $post->actor->username, $activityId)); - model('ActivityModel') + model('ActivityModel', false) ->update($activityId, [ 'payload' => $createActivity->toJSON(), ]); } - Events::trigger('on_status_add', $status); + Events::trigger('on_post_add', $post); - $this->clearCache($status); + $this->clearCache($post); $this->db->transComplete(); - return $newStatusId; + return $newPostId; } - public function editStatus(Status $updatedStatus): bool + public function editPost(Post $updatedPost): bool { $this->db->transStart(); - // update status create activity schedule in database - $scheduledActivity = model('ActivityModel') + // update post create activity schedule in database + $scheduledActivity = model('ActivityModel', false) ->where([ 'type' => 'Create', - 'status_id' => $this->uuid - ->fromString($updatedStatus->id) + 'post_id' => $this->uuid + ->fromString($updatedPost->id) ->getBytes(), ]) ->first(); // update published date in payload $newPayload = $scheduledActivity->payload; - $newPayload->object->published = $updatedStatus->published_at->format(DATE_W3C); - model('ActivityModel') + $newPayload->object->published = $updatedPost->published_at->format(DATE_W3C); + model('ActivityModel', false) ->update($scheduledActivity->id, [ 'payload' => json_encode($newPayload, JSON_THROW_ON_ERROR), - 'scheduled_at' => $updatedStatus->published_at, + 'scheduled_at' => $updatedPost->published_at, ]); - // update status - $updateResult = $this->update($updatedStatus->id, $updatedStatus); + // update post + $updateResult = $this->update($updatedPost->id, $updatedPost); - Events::trigger('on_status_edit', $updatedStatus); + Events::trigger('on_post_edit', $updatedPost); - $this->clearCache($updatedStatus); + $this->clearCache($updatedPost); $this->db->transComplete(); @@ -351,59 +351,59 @@ class StatusModel extends UuidModel } /** - * Removes a status from the database and decrements meta data + * Removes a post from the database and decrements meta data */ - public function removeStatus(Status $status, bool $registerActivity = true): BaseResult | bool + public function removePost(Post $post, bool $registerActivity = true): BaseResult | bool { $this->db->transStart(); - model('ActorModel') - ->where('id', $status->actor_id) - ->decrement('statuses_count'); + model('ActorModel', false) + ->where('id', $post->actor_id) + ->decrement('posts_count'); - if ($status->in_reply_to_id !== null) { - // Status to remove is a reply - model('StatusModel') - ->where('id', $this->uuid->fromString($status->in_reply_to_id) ->getBytes()) + if ($post->in_reply_to_id !== null) { + // Post to remove is a reply + model('PostModel', false) + ->where('id', $this->uuid->fromString($post->in_reply_to_id) ->getBytes()) ->decrement('replies_count'); - Events::trigger('on_reply_remove', $status); + Events::trigger('on_reply_remove', $post); } - // remove all status reblogs - foreach ($status->reblogs as $reblog) { + // remove all post reblogs + foreach ($post->reblogs as $reblog) { // FIXME: issue when actor is not local, can't get actor information - $this->removeStatus($reblog); + $this->removePost($reblog); } - // remove all status replies - foreach ($status->replies as $reply) { - $this->removeStatus($reply); + // remove all post replies + foreach ($post->replies as $reply) { + $this->removePost($reply); } // check that preview card is no longer used elsewhere before deleting it if ( - $status->preview_card && + $post->preview_card && $this->db - ->table('activitypub_statuses_preview_cards') - ->where('preview_card_id', $status->preview_card->id) + ->table('activitypub_posts_preview_cards') + ->where('preview_card_id', $post->preview_card->id) ->countAll() <= 1 ) { - model('PreviewCardModel')->deletePreviewCard($status->preview_card->id, $status->preview_card->url); + model('PreviewCardModel', false)->deletePreviewCard($post->preview_card->id, $post->preview_card->url); } if ($registerActivity) { $deleteActivity = new DeleteActivity(); $tombstoneObject = new TombstoneObject(); - $tombstoneObject->set('id', $status->uri); + $tombstoneObject->set('id', $post->uri); $deleteActivity - ->set('actor', $status->actor->uri) + ->set('actor', $post->actor->uri) ->set('object', $tombstoneObject); - $activityId = model('ActivityModel') + $activityId = model('ActivityModel', false) ->newActivity( 'Delete', - $status->actor_id, + $post->actor_id, null, null, $deleteActivity->toJSON(), @@ -411,20 +411,20 @@ class StatusModel extends UuidModel 'queued', ); - $deleteActivity->set('id', url_to('activity', $status->actor->username, $activityId)); + $deleteActivity->set('id', url_to('activity', $post->actor->username, $activityId)); - model('ActivityModel') + model('ActivityModel', false) ->update($activityId, [ 'payload' => $deleteActivity->toJSON(), ]); } - $result = model('StatusModel', false) - ->delete($status->id); + $result = model('PostModel', false) + ->delete($post->id); - Events::trigger('on_status_remove', $status); + Events::trigger('on_post_remove', $post); - $this->clearCache($status); + $this->clearCache($post); $this->db->transComplete(); @@ -432,182 +432,182 @@ class StatusModel extends UuidModel } public function addReply( - Status $reply, + Post $reply, bool $createPreviewCard = true, bool $registerActivity = true ): string | false { if (! $reply->in_reply_to_id) { - throw new Exception('Passed status is not a reply!'); + throw new Exception('Passed post is not a reply!'); } $this->db->transStart(); - $statusId = $this->addStatus($reply, $createPreviewCard, $registerActivity); + $postId = $this->addPost($reply, $createPreviewCard, $registerActivity); - model('StatusModel') + model('PostModel', false) ->where('id', $this->uuid->fromString($reply->in_reply_to_id) ->getBytes()) ->increment('replies_count'); - Events::trigger('on_status_reply', $reply); + Events::trigger('on_post_reply', $reply); $this->clearCache($reply); $this->db->transComplete(); - return $statusId; + return $postId; } - public function reblog(Actor $actor, Status $status, bool $registerActivity = true): string | false + public function reblog(Actor $actor, Post $post, bool $registerActivity = true): string | false { $this->db->transStart(); - $reblog = new Status([ + $reblog = new Post([ 'actor_id' => $actor->id, - 'reblog_of_id' => $status->id, + 'reblog_of_id' => $post->id, 'published_at' => Time::now(), ]); // add reblog $reblogId = $this->insert($reblog); - model('ActorModel') + model('ActorModel', false) ->where('id', $actor->id) - ->increment('statuses_count'); + ->increment('posts_count'); - model('StatusModel') - ->where('id', $this->uuid->fromString($status->id)->getBytes()) + model('PostModel', false) + ->where('id', $this->uuid->fromString($post->id)->getBytes()) ->increment('reblogs_count'); if ($registerActivity) { $announceActivity = new AnnounceActivity($reblog); - $activityId = model('ActivityModel') + $activityId = model('ActivityModel', false) ->newActivity( 'Announce', $actor->id, null, - $status->id, + $post->id, $announceActivity->toJSON(), $reblog->published_at, 'queued', ); - $announceActivity->set('id', url_to('activity', $status->actor->username, $activityId)); + $announceActivity->set('id', url_to('activity', $post->actor->username, $activityId)); - model('ActivityModel') + model('ActivityModel', false) ->update($activityId, [ 'payload' => $announceActivity->toJSON(), ]); } - Events::trigger('on_status_reblog', $actor, $status); + Events::trigger('on_post_reblog', $actor, $post); - $this->clearCache($status); + $this->clearCache($post); $this->db->transComplete(); return $reblogId; } - public function undoReblog(Status $reblogStatus, bool $registerActivity = true): BaseResult | bool + public function undoReblog(Post $reblogPost, bool $registerActivity = true): BaseResult | bool { $this->db->transStart(); - model('ActorModel') - ->where('id', $reblogStatus->actor_id) - ->decrement('statuses_count'); + model('ActorModel', false) + ->where('id', $reblogPost->actor_id) + ->decrement('posts_count'); - model('StatusModel') - ->where('id', $this->uuid->fromString($reblogStatus->reblog_of_id) ->getBytes()) + model('PostModel', false) + ->where('id', $this->uuid->fromString($reblogPost->reblog_of_id) ->getBytes()) ->decrement('reblogs_count'); if ($registerActivity) { $undoActivity = new UndoActivity(); // get like activity - $activity = model('ActivityModel') + $activity = model('ActivityModel', false) ->where([ 'type' => 'Announce', - 'actor_id' => $reblogStatus->actor_id, - 'status_id' => $this->uuid - ->fromString($reblogStatus->reblog_of_id) + 'actor_id' => $reblogPost->actor_id, + 'post_id' => $this->uuid + ->fromString($reblogPost->reblog_of_id) ->getBytes(), ]) ->first(); - $announceActivity = new AnnounceActivity($reblogStatus); - $announceActivity->set('id', url_to('activity', $reblogStatus->actor->username, $activity->id),); + $announceActivity = new AnnounceActivity($reblogPost); + $announceActivity->set('id', url_to('activity', $reblogPost->actor->username, $activity->id),); $undoActivity - ->set('actor', $reblogStatus->actor->uri) + ->set('actor', $reblogPost->actor->uri) ->set('object', $announceActivity); - $activityId = model('ActivityModel') + $activityId = model('ActivityModel', false) ->newActivity( 'Undo', - $reblogStatus->actor_id, + $reblogPost->actor_id, null, - $reblogStatus->reblog_of_id, + $reblogPost->reblog_of_id, $undoActivity->toJSON(), Time::now(), 'queued', ); - $undoActivity->set('id', url_to('activity', $reblogStatus->actor->username, $activityId)); + $undoActivity->set('id', url_to('activity', $reblogPost->actor->username, $activityId)); - model('ActivityModel') + model('ActivityModel', false) ->update($activityId, [ 'payload' => $undoActivity->toJSON(), ]); } - $result = model('StatusModel', false) - ->delete($reblogStatus->id); + $result = model('PostModel', false) + ->delete($reblogPost->id); - Events::trigger('on_status_undo_reblog', $reblogStatus); + Events::trigger('on_post_undo_reblog', $reblogPost); - $this->clearCache($reblogStatus); + $this->clearCache($reblogPost); $this->db->transComplete(); return $result; } - public function toggleReblog(Actor $actor, Status $status): void + public function toggleReblog(Actor $actor, Post $post): void { if ( - ! ($reblogStatus = $this->where([ + ! ($reblogPost = $this->where([ 'actor_id' => $actor->id, 'reblog_of_id' => $this->uuid - ->fromString($status->id) + ->fromString($post->id) ->getBytes(), ])->first()) ) { - $this->reblog($actor, $status); + $this->reblog($actor, $post); } else { - $this->undoReblog($reblogStatus); + $this->undoReblog($reblogPost); } } - public function clearCache(Status $status): void + public function clearCache(Post $post): void { $cachePrefix = config('ActivityPub') ->cachePrefix; - $hashedStatusUri = md5($status->uri); + $hashedPostUri = md5($post->uri); - model('ActorModel') - ->clearCache($status->actor); + model('ActorModel', false) + ->clearCache($post->actor); cache() - ->deleteMatching($cachePrefix . "status#{$status->id}*"); + ->deleteMatching($cachePrefix . "post#{$post->id}*"); cache() - ->deleteMatching($cachePrefix . "status-{$hashedStatusUri}*"); + ->deleteMatching($cachePrefix . "post-{$hashedPostUri}*"); - if ($status->in_reply_to_id !== null) { - $this->clearCache($status->reply_to_status); + if ($post->in_reply_to_id !== null) { + $this->clearCache($post->reply_to_post); } - if ($status->reblog_of_id !== null) { - $this->clearCache($status->reblog_of_status); + if ($post->reblog_of_id !== null) { + $this->clearCache($post->reblog_of_post); } } @@ -615,16 +615,16 @@ class StatusModel extends UuidModel * @param array> $data * @return array> */ - protected function setStatusId(array $data): array + protected function setPostId(array $data): array { $uuid4 = $this->uuid->{$this->uuidVersion}(); $data['data']['id'] = $uuid4->toString(); if (! isset($data['data']['uri'])) { - $actor = model('ActorModel') + $actor = model('ActorModel', false) ->getActorById((int) $data['data']['actor_id']); - $data['data']['uri'] = url_to('status', $actor->username, $uuid4->toString()); + $data['data']['uri'] = url_to('post', $actor->username, $uuid4->toString()); } return $data; diff --git a/app/Libraries/ActivityPub/Models/PreviewCardModel.php b/app/Libraries/ActivityPub/Models/PreviewCardModel.php index 161052f7..08879ff7 100644 --- a/app/Libraries/ActivityPub/Models/PreviewCardModel.php +++ b/app/Libraries/ActivityPub/Models/PreviewCardModel.php @@ -70,18 +70,18 @@ class PreviewCardModel extends Model return $found; } - public function getStatusPreviewCard(string $statusId): ?PreviewCard + public function getPostPreviewCard(string $postId): ?PreviewCard { $cacheName = config('ActivityPub') - ->cachePrefix . "status#{$statusId}_preview_card"; + ->cachePrefix . "post#{$postId}_preview_card"; if (! ($found = cache($cacheName))) { $found = $this->join( - 'activitypub_statuses_preview_cards', - 'activitypub_statuses_preview_cards.preview_card_id = id', + 'activitypub_posts_preview_cards', + 'activitypub_posts_preview_cards.preview_card_id = id', 'inner', ) - ->where('status_id', service('uuid') ->fromString($statusId) ->getBytes()) + ->where('post_id', service('uuid') ->fromString($postId) ->getBytes()) ->first(); cache() diff --git a/app/Libraries/ActivityPub/Objects/NoteObject.php b/app/Libraries/ActivityPub/Objects/NoteObject.php index 067eef84..5588f9bb 100644 --- a/app/Libraries/ActivityPub/Objects/NoteObject.php +++ b/app/Libraries/ActivityPub/Objects/NoteObject.php @@ -15,7 +15,7 @@ declare(strict_types=1); namespace ActivityPub\Objects; use ActivityPub\Core\ObjectType; -use ActivityPub\Entities\Status; +use ActivityPub\Entities\Post; class NoteObject extends ObjectType { @@ -27,20 +27,20 @@ class NoteObject extends ObjectType protected string $replies; - public function __construct(Status $status) + public function __construct(Post $post) { - $this->id = $status->uri; + $this->id = $post->uri; - $this->content = $status->message_html; - $this->published = $status->published_at->format(DATE_W3C); - $this->attributedTo = $status->actor->uri; + $this->content = $post->message_html; + $this->published = $post->published_at->format(DATE_W3C); + $this->attributedTo = $post->actor->uri; - if ($status->in_reply_to_id !== null) { - $this->inReplyTo = $status->reply_to_status->uri; + if ($post->in_reply_to_id !== null) { + $this->inReplyTo = $post->reply_to_post->uri; } - $this->replies = url_to('status-replies', $status->actor->username, $status->id); + $this->replies = url_to('post-replies', $post->actor->username, $post->id); - $this->cc = [$status->actor->followers_url]; + $this->cc = [$post->actor->followers_url]; } } diff --git a/app/Libraries/CommentObject.php b/app/Libraries/CommentObject.php new file mode 100644 index 00000000..d127bcc2 --- /dev/null +++ b/app/Libraries/CommentObject.php @@ -0,0 +1,42 @@ +id = $comment->uri; + + $this->content = $comment->message_html; + $this->published = $comment->created_at->format(DATE_W3C); + $this->attributedTo = $comment->actor->uri; + + if ($comment->in_reply_to_id !== null) { + $this->inReplyTo = $comment->reply_to_comment->uri; + } + + $this->replies = url_to('comment-replies', $comment->actor->username, $comment->episode->slug, $comment->id); + + $this->cc = [$comment->actor->followers_url]; + } +} diff --git a/app/Libraries/NoteObject.php b/app/Libraries/NoteObject.php index 2b4ea701..7c28bd69 100644 --- a/app/Libraries/NoteObject.php +++ b/app/Libraries/NoteObject.php @@ -11,25 +11,25 @@ declare(strict_types=1); namespace App\Libraries; use ActivityPub\Objects\NoteObject as ActivityPubNoteObject; -use App\Entities\Status; +use App\Entities\Post; class NoteObject extends ActivityPubNoteObject { /** - * @param Status $status + * @param Post $post */ - public function __construct(\ActivityPub\Entities\Status $status) + public function __construct(\ActivityPub\Entities\Post $post) { - parent::__construct($status); + parent::__construct($post); - if ($status->episode_id) { + if ($post->episode_id) { $this->content = '' . - $status->episode->title . + $post->episode->title . '
' . - $status->message_html; + $post->message_html; } } } diff --git a/app/Models/CommentModel.php b/app/Models/CommentModel.php new file mode 100644 index 00000000..ba7215fa --- /dev/null +++ b/app/Models/CommentModel.php @@ -0,0 +1,184 @@ +find($commentId); + + cache() + ->save($cacheName, $found, DECADE); + } + + return $found; + } + + public function addComment(Comment $comment, bool $registerActivity = false): string | false + { + $this->db->transStart(); + // increment Episode's comments_count + + if (! ($newCommentId = $this->insert($comment, true))) { + $this->db->transRollback(); + + // Couldn't insert comment + return false; + } + + (new EpisodeModel()) + ->where('id', $comment->episode_id) + ->increment('comments_count'); + + if ($registerActivity) { + // set post id and uri to construct NoteObject + $comment->id = $newCommentId; + $comment->uri = url_to('comment', $comment->actor->username, $comment->episode->slug, $comment->id); + + $createActivity = new CreateActivity(); + $createActivity + ->set('actor', $comment->actor->uri) + ->set('object', new CommentObject($comment)); + + $activityId = model('ActivityModel', false) + ->newActivity( + 'Create', + $comment->actor_id, + null, + null, + $createActivity->toJSON(), + $comment->created_at, + 'queued', + ); + + $createActivity->set('id', url_to('activity', $comment->actor->username, $activityId)); + + model('ActivityModel', false) + ->update($activityId, [ + 'payload' => $createActivity->toJSON(), + ]); + } + + $this->db->transComplete(); + + return $newCommentId; + } + + /** + * Retrieves all published posts for a given episode ordered by publication date + * + * @return Comment[] + */ + public function getEpisodeComments(int $episodeId): array + { + // TODO: merge with replies from posts linked to episode linked + $episodeComments = $this->select('*, 0 as is_from_post') + ->where('episode_id', $episodeId) + ->getCompiledSelect(); + + $episodePostsReplies = $this->db->table('activitypub_posts') + ->select( + 'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, 0 as dislikes_count, replies_count, published_at as created_at, created_by, 1 as is_from_post' + ) + ->whereIn('in_reply_to_id', function (BaseBuilder $builder) use (&$episodeId): BaseBuilder { + return $builder->select('id') + ->from('activitypub_posts') + ->where('episode_id', $episodeId); + }) + ->where('`created_at` <= NOW()', null, false) + ->getCompiledSelect(); + + $allEpisodeComments = $this->db->query( + $episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC' + ); + + return $allEpisodeComments->getCustomResultObject($this->returnType); + } + + /** + * Retrieves all replies for a given comment + * + * @return Comment[] + */ + public function getCommentReplies(int $episodeId, string $commentId): array + { + // TODO: get all replies for a given comment + return $this->findAll(); + } + + /** + * @param array> $data + * @return array> + */ + protected function setCommentId(array $data): array + { + $uuid4 = $this->uuid->{$this->uuidVersion}(); + $data['data']['id'] = $uuid4->toString(); + + if (! isset($data['data']['uri'])) { + $actor = model('ActorModel', false) + ->getActorById((int) $data['data']['actor_id']); + $episode = model('EpisodeModel', false) + ->find((int) $data['data']['episode_id']); + + $data['data']['uri'] = url_to('comment', $actor->username, $episode->slug, $uuid4->toString()); + } + + return $data; + } +} diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index d5540624..f3973d1c 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -90,9 +90,8 @@ class EpisodeModel extends Model 'location_geo', 'location_osm', 'custom_rss', - 'favourites_total', - 'reblogs_total', - 'statuses_total', + 'posts_count', + 'comments_count', 'published_at', 'created_by', 'updated_by', diff --git a/app/Models/PostModel.php b/app/Models/PostModel.php new file mode 100644 index 00000000..db01aee4 --- /dev/null +++ b/app/Models/PostModel.php @@ -0,0 +1,56 @@ +where([ + 'episode_id' => $episodeId, + ]) + ->where('`published_at` <= NOW()', null, false) + ->orderBy('published_at', 'DESC') + ->findAll(); + } +} diff --git a/app/Models/StatusModel.php b/app/Models/StatusModel.php deleted file mode 100644 index 132c48ef..00000000 --- a/app/Models/StatusModel.php +++ /dev/null @@ -1,74 +0,0 @@ -where([ - 'episode_id' => $episodeId, - ]) - ->where('`published_at` <= NOW()', null, false) - ->orderBy('published_at', 'DESC') - ->findAll(); - } - - /** - * Retrieves all published statuses for a given episode ordered by publication date - * - * @return Status[] - */ - public function getEpisodeComments(int $episodeId): array - { - return $this->whereIn('in_reply_to_id', function (BaseBuilder $builder) use (&$episodeId): BaseBuilder { - return $builder->select('id') - ->from('activitypub_statuses') - ->where('episode_id', $episodeId); - }) - ->where('`published_at` <= NOW()', null, false) - ->orderBy('published_at', 'ASC') - ->findAll(); - } -} diff --git a/app/Resources/icons/thumb-down.svg b/app/Resources/icons/thumb-down.svg new file mode 100644 index 00000000..ff06be8a --- /dev/null +++ b/app/Resources/icons/thumb-down.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/Resources/icons/thumb-up.svg b/app/Resources/icons/thumb-up.svg new file mode 100644 index 00000000..f0d66702 --- /dev/null +++ b/app/Resources/icons/thumb-up.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/Resources/styles/index.css b/app/Resources/styles/index.css index 23780f33..37bc69b1 100644 --- a/app/Resources/styles/index.css +++ b/app/Resources/styles/index.css @@ -7,7 +7,7 @@ @import "./radioBtn.css"; @import "./switch.css"; @import "./charts.css"; -@import "./status.css"; +@import "./post.css"; @import "./tabs.css"; @import "./radioToggler.css"; @import "./formInputTabs.css"; diff --git a/app/Resources/styles/status.css b/app/Resources/styles/post.css similarity index 87% rename from app/Resources/styles/status.css rename to app/Resources/styles/post.css index e3d2b794..966ec606 100644 --- a/app/Resources/styles/status.css +++ b/app/Resources/styles/post.css @@ -1,11 +1,11 @@ @layer components { - .status-content { + .post-content { & a { @apply text-sm font-semibold text-pine-600 hover:underline; } } - .status-replies > * { + .post-replies > * { @apply relative; & img { diff --git a/app/Resources/styles/tabs.css b/app/Resources/styles/tabs.css index f0f279b4..6fb8e781 100644 --- a/app/Resources/styles/tabs.css +++ b/app/Resources/styles/tabs.css @@ -1,6 +1,6 @@ @layer components { .tabset { - @apply grid grid-cols-2; + @apply grid grid-cols-3; } .tabset > input[type="radio"] { @@ -11,9 +11,10 @@ @apply hidden; } - /* Logic for 2 tabs at most */ + /* Logic for 3 tabs at most */ .tabset > input:first-child:checked ~ .tab-panels > .tab-panel:first-child, - .tabset > input:nth-child(3):checked ~ .tab-panels > .tab-panel:nth-child(2) { + .tabset > input:nth-child(3):checked ~ .tab-panels > .tab-panel:nth-child(2), + .tabset > input:nth-child(5):checked ~ .tab-panels > .tab-panel:nth-child(3) { @apply block; } @@ -23,7 +24,7 @@ } .tabset > input:checked + label::after { - @apply absolute inset-x-0 bottom-0 w-1/2 h-1 mx-auto bg-pine-700; + @apply absolute inset-x-0 bottom-0 w-1/3 h-1 mx-auto bg-pine-700; content: ""; } @@ -32,6 +33,6 @@ } .tabset .tab-panels { - @apply col-span-2 p-6; + @apply col-span-3 p-6; } } diff --git a/app/Views/admin/episode/list.php b/app/Views/admin/episode/list.php index a78dae29..c12c39b0 100644 --- a/app/Views/admin/episode/list.php +++ b/app/Views/admin/episode/list.php @@ -71,7 +71,7 @@ [ 'header' => lang('Episode.list.comments'), 'cell' => function ($episode): int { - return count($episode->comments); + return $episode->comments_count; }, ], [ diff --git a/app/Views/admin/episode/publish.php b/app/Views/admin/episode/publish.php index a3c192a2..238d3b96 100644 --- a/app/Views/admin/episode/publish.php +++ b/app/Views/admin/episode/publish.php @@ -27,9 +27,9 @@ - +
<?= $podcast
diff --git a/app/Views/admin/episode/publish_edit.php b/app/Views/admin/episode/publish_edit.php
index 8d273db3..b83914a0 100644
--- a/app/Views/admin/episode/publish_edit.php
+++ b/app/Views/admin/episode/publish_edit.php
@@ -24,13 +24,13 @@
 ]) ?>
 <?= csrf_field() ?>
 <?= form_hidden('client_timezone', 'UTC') ?>
-<?= form_hidden('status_id', $status->id) ?>
+<?= form_hidden('post_id', $post->id) ?>
 
 
 <label for= - +
<?= $podcast->actor
@@ -42,7 +42,7 @@
                 <span class=@actor->username ?>

- published_at, 'text-xs text-gray-500') ?> + published_at, 'text-xs text-gray-500') ?>
@@ -54,7 +54,7 @@ 'placeholder' => 'Write your message...', 'autofocus' => '' ], - old('message', $status->message, false), + old('message', $post->message, false), ['rows' => 2], ) ?>
diff --git a/app/Views/podcast/_partials/comment.php b/app/Views/podcast/_partials/comment.php new file mode 100644 index 00000000..587df069 --- /dev/null +++ b/app/Views/podcast/_partials/comment.php @@ -0,0 +1,43 @@ +
+ <?= $comment->display_name ?> +
+
+ actor->is_local + ? '' + : 'target="_blank" rel="noopener noreferrer"' ?>> + actor + ->display_name ?> + @actor + ->username . + ($comment->actor->is_local + ? '' + : '@' . $comment->actor->domain) ?> + created_at, 'text-xs text-gray-500 ml-auto') ?> + +
+
message_html ?>
+
+ handle, $episode->slug, $comment->id, 'like'), + icon('thumb-up', 'text-lg mr-1 text-gray-400 group-hover:text-gray-600') . 0, + [ + 'class' => 'inline-flex items-center hover:underline group', + 'width' => 420, + 'height' => 620, + 'title' => lang('Comment.like'), + ], + ) ?> + handle, $episode->slug, $comment->id, 'dislike'), + icon('thumb-down', 'text-lg text-gray-400 group-hover:text-gray-600'), + [ + 'class' => 'inline-flex items-center hover:underline group', + 'width' => 420, + 'height' => 620, + 'title' => lang('Comment.dislike'), + ], + ) ?> +
+
+
diff --git a/app/Views/podcast/_partials/episode_card.php b/app/Views/podcast/_partials/episode_card.php index 1aff9de6..01b5f412 100644 --- a/app/Views/podcast/_partials/episode_card.php +++ b/app/Views/podcast/_partials/episode_card.php @@ -28,12 +28,12 @@ handle, $episode->slug), icon('chat', 'text-xl mr-1 text-gray-400') . - $episode->statuses_total, + $episode->comments_count, [ 'class' => - 'inline-flex items-center hover:underline', - 'title' => lang('Episode.total_statuses', [ - 'numberOfTotalStatuses' => $episode->statuses_total, + 'inline-flex items-center hover:underline', + 'title' => lang('Episode.number_of_comments', [ + 'numberOfComments' => $episode->comments_count, ]), ], ) ?> diff --git a/app/Views/podcast/_partials/header.php b/app/Views/podcast/_partials/header.php index 8714792f..85679ae9 100644 --- a/app/Views/podcast/_partials/header.php +++ b/app/Views/podcast/_partials/header.php @@ -50,8 +50,8 @@ $podcast->actor->statuses_count, + ) ?>" class="hover:underline"> $podcast->actor->posts_count, ]) ?>
diff --git a/app/Views/podcast/_partials/post.php b/app/Views/podcast/_partials/post.php new file mode 100644 index 00000000..7ca26266 --- /dev/null +++ b/app/Views/podcast/_partials/post.php @@ -0,0 +1,36 @@ + diff --git a/app/Views/podcast/_partials/post_actions.php b/app/Views/podcast/_partials/post_actions.php new file mode 100644 index 00000000..2468b3c1 --- /dev/null +++ b/app/Views/podcast/_partials/post_actions.php @@ -0,0 +1,36 @@ +
+ handle, $post->id), + icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count, + [ + 'class' => 'inline-flex items-center hover:underline', + 'title' => lang('Post.replies', [ + 'numberOfReplies' => $post->replies_count, + ]), + ], + ) ?> + handle, $post->id, 'reblog'), + icon('repeat', 'text-2xl mr-1 text-gray-400') . $post->reblogs_count, + [ + 'class' => 'inline-flex items-center hover:underline', + 'width' => 420, + 'height' => 620, + 'title' => lang('Post.reblogs', [ + 'numberOfReblogs' => $post->reblogs_count, + ]), + ], + ) ?> + handle, $post->id, 'favourite'), + icon('heart', 'text-2xl mr-1 text-gray-400') . $post->favourites_count, + [ + 'class' => 'inline-flex items-center hover:underline', + 'width' => 420, + 'height' => 620, + 'title' => lang('Post.favourites', [ + 'numberOfFavourites' => $post->favourites_count, + ]), + ], + ) ?> +
diff --git a/app/Views/podcast/_partials/status_actions_authenticated.php b/app/Views/podcast/_partials/post_actions_authenticated.php similarity index 61% rename from app/Views/podcast/_partials/status_actions_authenticated.php rename to app/Views/podcast/_partials/post_actions_authenticated.php index 9ee2cf6e..4cb02a32 100644 --- a/app/Views/podcast/_partials/status_actions_authenticated.php +++ b/app/Views/podcast/_partials/post_actions_authenticated.php @@ -1,87 +1,87 @@