diff --git a/app/Config/Events.php b/app/Config/Events.php index b2288b940b7cc6d269dccf1c7d11393fd9f30d0e..59bfed7dad0034127b6d1e1fee828959bff73bf1 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') - ->where('id', $episodeId) - ->decrement('statuses_total', 1 + $status->reblogs_count); - - model('EpisodeModel') - ->where('id', $episodeId) - ->decrement('reblogs_total', $status->reblogs_count); - - model('EpisodeModel') + if ($episodeId = $post->episode_id) { + model('EpisodeModel', false) ->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'); - - model('EpisodeModel') - ->where('id', $episodeId) - ->decrement('statuses_total'); - } +Events::on('on_post_undo_reblog', function ($reblogPost): void { + $post = $reblogPost->reblog_of_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}*"); 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 f75b2240dd691a7b35e574d1f9d8d9df17f4d7f8..88d4be2e1e0434c20b31846c5f72ac2573f5e743 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 bacf575f52a7a7052f546349aafaf96c320a2a22..fea2490da04c44f62c9ce49afbf49961845244ca 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 e51ba7b75696a0958e3776ba569db63b8ec5dadc..4e44af57446d372f7b67008af797fa746add336f 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 2322535eed9ab093e9a58eb16f28d0886db75d1d..a21f117ec351057beaaeaf6c90844615f5cee216 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 c6f5c2f0627a8d94decfff849bb089cc9fc84448..c3761da63be47e1078f52a0542174b784a962e05 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 a6d517966a342288e9f3a944c1f25349cdaa838d..310b1b70a5dbe14d429561c03e871420d1e185d2 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' => [ - 'type' => 'INT', - 'unsigned' => true, - 'default' => 0, - ], - 'reblogs_total' => [ + 'posts_count' => [ '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 cee4ef58779fbe157af4ad9f469e1860cdc1e294..ef5e2a3b9b0b7ef3122a56dab388abd484e15347 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 a81a782f882875e3cbcdba0ec8d26c02b4788370..b34c801f508aa5258ee6ac2a1360de3041ef1902 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 = <<<CODE_SAMPLE - ALTER TABLE {$prefix}activitypub_statuses + ALTER TABLE {$prefix}activitypub_posts ADD COLUMN `episode_id` INT UNSIGNED NULL AFTER `replies_count`, - ADD FOREIGN KEY {$prefix}activitypub_statuses_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE; + ADD FOREIGN KEY {$prefix}activitypub_posts_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE; CODE_SAMPLE; $this->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 2f9a29d6e4fddf26c47dd5fc3e3507b8813aac85..9ccd81bde1b588a92c33e53174dd3c21487ee6f7 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 = <<<CODE_SAMPLE - ALTER TABLE {$prefix}activitypub_statuses + ALTER TABLE {$prefix}activitypub_posts ADD COLUMN `created_by` INT UNSIGNED AFTER `episode_id`, - ADD FOREIGN KEY {$prefix}activitypub_statuses_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE; + ADD FOREIGN KEY {$prefix}activitypub_posts_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE; CODE_SAMPLE; $this->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 0000000000000000000000000000000000000000..612d9f63439f251b276ea4a65f374ba9a87f42c0 --- /dev/null +++ b/app/Database/Migrations/2021-08-12-150000_add_comments.php @@ -0,0 +1,85 @@ +<?php + +declare(strict_types=1); + +/** + * Class AddComments creates comments table in database + * + * @copyright 2020 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace App\Database\Migrations; + +use CodeIgniter\Database\Migration; + +class AddComments extends Migration +{ + public function up(): void + { + $this->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 1e46d1ad58437d626c0aef7e79c068a57742003e..3a76e36215330e471cf9a022dde39cee4f41c226 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 0000000000000000000000000000000000000000..6235802f8fed5b6cce13b6ac9b1dc90424cdbb79 --- /dev/null +++ b/app/Entities/Comment.php @@ -0,0 +1,110 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2020 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace App\Entities; + +use App\Models\EpisodeModel; +use CodeIgniter\I18n\Time; +use Michalsn\Uuid\UuidEntity; +use RuntimeException; + +/** + * @property string $id + * @property string $uri + * @property int $episode_id + * @property Episode|null $episode + * @property int $actor_id + * @property Actor|null $actor + * @property string $in_reply_to_id + * @property Comment|null $reply_to_comment + * @property string $message + * @property string $message_html + * @property int $likes_count + * @property int $dislikes_count + * @property int $replies_count + * @property Time $created_at + * @property int $created_by + */ +class Comment extends UuidEntity +{ + protected ?Episode $episode = null; + + protected ?Actor $actor = null; + + protected ?Comment $reply_to_comment = null; + + /** + * @var string[] + */ + protected $dates = ['created_at']; + + /** + * @var array<string, string> + */ + 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", '<br />', linkify($messageWithoutTags)); + + return $this; + } +} diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 31ae9da29116679719832f2bc3921c2633cccf3d..7b976a2ec4982d42967cb100ac5fb8a68aa437f7 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 c2e305d633150eacc9d08f69e017f3de6996f876..a76d86333601e24c1fcae64eb82ab1aefa402ae1 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 661e222c20b29ca5c07e42bb6b8827d473a48f71..649114ce6a4345bec2e722fa93bb469d869885ee 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 461ba0c120f748e2d2862f9567d4012c987048fc..09bb644b81eaa6e8291f3f3d6d8f997dac7af9fa 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<podcastHandle>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[a-zA-Z0-9\-]{1,191})~', + '~@(?P<podcastHandle>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[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 0000000000000000000000000000000000000000..7073865a09c5f8f42c901e02af2f802141be29f9 --- /dev/null +++ b/app/Language/en/Comment.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2020 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +return [ + 'form' => [ + '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 7749b06a2e0ccc9b2e0d331843a08692964bb6e9..06855b9d713b30e269d58a8f0d54e5260af1fa87 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 f6118e02f28a1567e1c73059528d705630234ca5..946b0b1be436b3db30532ec98e148056ddd11c73 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -223,7 +223,7 @@ return [ one {<span class="font-semibold">#</span> follower} other {<span class="font-semibold">#</span> followers} }', - 'statuses' => '{numberOfStatuses, plural, + 'posts' => '{numberOfPosts, plural, one {<span class="font-semibold">#</span> post} other {<span class="font-semibold">#</span> 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 8f5f3a129bb9be22e2d392dd72edc2673e3ba92a..a60ef30fec25eb5bbcd3f54e7fb2efc53e559174 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 678ed65be255367635fecc112d48b0f5108829ed..fc3d49364ebfb4b7a95e982dbbc8e38b81a668de 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 4b571ac48cee0e1bd3257bfb316d375ca6c329f7..1ef22cd7767298458d81d26c995b43a835c4b378 100644 --- a/app/Language/fr/Podcast.php +++ b/app/Language/fr/Podcast.php @@ -225,7 +225,7 @@ return [ one {<span class="font-semibold">#</span> abonné·e} other {<span class="font-semibold">#</span> abonné·e·s} }', - 'statuses' => '{numberOfStatuses, plural, + 'posts' => '{numberOfPosts, plural, one {<span class="font-semibold">#</span> publication} other {<span class="font-semibold">#</span> 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 c19d0e2963de7b76ebf52008095d92f7cb33960b..a12bdd6f25608cd1f330a1399882ea787e4af13d 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 3eb4ba5097b168989a7aa5a1adea1774c6255b92..75988ac635e758e12b0a1bb5356bd7f9b96e5560 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 a667e7219b5b9da998e34375fe778e7d92520d20..dd5dd0b1a1f307083ca0b3eddffff46bc8b3b12d 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 6bb9167cb9e71de3bace25015fb2091b4830f112..7a9408975fdefa95d328fdd1b37cea822e57a185 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 022304f2710fdb4ddc5eff394aa6315a18ccebf8..73de6f5e77c3b0146baa9695f31975a7849d07df 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 057360a3f8af74b2be9845afe24fbf85015f7433..4b7928e23426ebd14d024230cb881269066338fe 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 4fcc2973728de2853f2d6b62212679fdddd276e9..d247fbc70d72c32b4b6844641f4db7b2b925d756 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 68fe53fc5b272fbdffaad4e84f08ad3880200684..05cc28b419600175be4f97073e339dcea81b7fe8 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 443a3ea9e65b9ac2c2f0823e48d282d51145335a..1b9e99b597c6852c43bc317204966899ac0e0e4a 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 049284bb8d4eeec54f957ca0eeb7230f25fab283..cfdc98ab01fde8a5ed102726f1909ad36dc90270 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 fe35855fe938e8254921a42c9beffada7ff5e8ba..9b677174ba0ba5a33dc04554a0bb26687733f5f1 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 b7b0b49a20a10110ec79bde8b81c388d918216f9..dbbea7228c0e28dd24f53c4209556f9f7c28d280 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 4a67bdb0cef92610ec38f6ef57a95a8614cd3818..e60995c9394a3947a94937b3f718ffc087dade3a 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 25845f0080822bad324a1ba6cd9045ff756064be..bf07cf53dee9ae604a5578c6f32002a59e2dee95 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 04014bb63f0ddb3681f6dda343a1194378af9a3e..b3d4028a7d0ade7f81504d601dfc61df528f4890 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<string, string> */ 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 94cfa0c7c5ea862bebb51daa549fb3639a792a18..7f33f0a9873ef917b084540343128f70834c5b24 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 630ccde178e183decbe4eb2081944725fd0b53bc..8303dabacb35de81c7af99ba88c76c6654c52ec8 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 9b8af37ce08f730063424f8c471f7b21852e6fa6..708d1dc270990d2989b25e9b9d3d3018f603769b 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 8fe539a153be7c34fbf82033ba1906a8cf5ebd33..48e7ff26c52f5f45973e1d699e520db79a2c1e6a 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 5ea58981e9d3a8cb16fb84743b6fcb0a817e1315..3e22785bce1cf697331970e0f822eae71c457632 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 3fc6b2dcd278ee3c0190a38b31ccfadbb199cbfb..f7dc644a6ffadf25957dabd1da9cbfb4b5a51cf2 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<string, array<string|int, mixed>> $data * @return array<string, array<string|int, mixed>> */ - 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 161052f7b11718010ae351c39951d1e8986b53c5..08879ff7953262726186f5ef190157f23a557848 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 067eef84e5fed56bca7eca777b1472560eb77469..5588f9bb647ece80f0c9f55ea624c52d1ee8dc08 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 0000000000000000000000000000000000000000..d127bcc2d3662da3d3919af7c2076be5a24b1daa --- /dev/null +++ b/app/Libraries/CommentObject.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2021 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace App\Libraries; + +use ActivityPub\Core\ObjectType; +use App\Entities\Comment; + +class CommentObject extends ObjectType +{ + protected string $type = 'Note'; + + protected string $attributedTo; + + protected string $inReplyTo; + + protected string $replies; + + public function __construct(Comment $comment) + { + $this->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 2b4ea701c0d2758c04f000008338f7b4c60fbcbc..7c28bd69b1161b8101757b9c943bbd5932e0a221 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 = '<a href="' . - $status->episode->link . + $post->episode->link . '" target="_blank" rel="noopener noreferrer">' . - $status->episode->title . + $post->episode->title . '</a><br/>' . - $status->message_html; + $post->message_html; } } } diff --git a/app/Models/CommentModel.php b/app/Models/CommentModel.php new file mode 100644 index 0000000000000000000000000000000000000000..ba7215faf04079470277c2e586fb1ac523f13693 --- /dev/null +++ b/app/Models/CommentModel.php @@ -0,0 +1,184 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2021 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace App\Models; + +use ActivityPub\Activities\CreateActivity; +use App\Entities\Comment; +use App\Libraries\CommentObject; +use CodeIgniter\Database\BaseBuilder; +use Michalsn\Uuid\UuidModel; + +class CommentModel extends UuidModel +{ + /** + * @var string + */ + protected $returnType = Comment::class; + + /** + * @var string + */ + protected $table = 'comments'; + + /** + * @var string[] + */ + protected $uuidFields = ['id', 'in_reply_to_id']; + + /** + * @var string[] + */ + protected $allowedFields = [ + 'id', + 'uri', + 'episode_id', + 'actor_id', + 'in_reply_to_id', + 'message', + 'message_html', + 'likes_count', + 'dislikes_count', + 'replies_count', + 'created_at', + 'created_by', + ]; + + /** + * @var string[] + */ + protected $beforeInsert = ['setCommentId']; + + public function getCommentById(string $commentId): ?Comment + { + $cacheName = "comment#{$commentId}"; + if (! ($found = cache($cacheName))) { + $found = $this->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<string, array<string|int, mixed>> $data + * @return array<string, array<string|int, mixed>> + */ + 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 d554062486ff9e1bf0a6d3781580f5095687e308..f3973d1c5b74663fbdadbafa9b25df6e8e7ffde5 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 0000000000000000000000000000000000000000..db01aee40a396601ceb9450652e2f1bd07b8dc76 --- /dev/null +++ b/app/Models/PostModel.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2021 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace App\Models; + +use ActivityPub\Models\PostModel as ActivityPubPostModel; +use App\Entities\Post; + +class PostModel extends ActivityPubPostModel +{ + /** + * @var string + */ + protected $returnType = Post::class; + + /** + * @var string[] + */ + protected $allowedFields = [ + 'id', + 'uri', + 'actor_id', + 'in_reply_to_id', + 'reblog_of_id', + 'episode_id', + 'message', + 'message_html', + 'favourites_count', + 'reblogs_count', + 'replies_count', + 'created_by', + 'published_at', + ]; + + /** + * Retrieves all published posts for a given episode ordered by publication date + * + * @return Post[] + */ + public function getEpisodePosts(int $episodeId): array + { + return $this->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 132c48efd248c33aa296a37871dc41d459cf6be3..0000000000000000000000000000000000000000 --- a/app/Models/StatusModel.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * @copyright 2021 Podlibre - * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 - * @link https://castopod.org/ - */ - -namespace App\Models; - -use ActivityPub\Models\StatusModel as ActivityPubStatusModel; -use App\Entities\Status; -use CodeIgniter\Database\BaseBuilder; - -class StatusModel extends ActivityPubStatusModel -{ - /** - * @var string - */ - protected $returnType = Status::class; - - /** - * @var string[] - */ - protected $allowedFields = [ - 'id', - 'uri', - 'actor_id', - 'in_reply_to_id', - 'reblog_of_id', - 'episode_id', - 'message', - 'message_html', - 'favourites_count', - 'reblogs_count', - 'replies_count', - 'created_by', - 'published_at', - ]; - - /** - * Retrieves all published statuses for a given episode ordered by publication date - * - * @return Status[] - */ - public function getEpisodeStatuses(int $episodeId): array - { - return $this->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 0000000000000000000000000000000000000000..ff06be8ab1660324571b416b23b8c93a808ecd73 --- /dev/null +++ b/app/Resources/icons/thumb-down.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <g> + <path fill="none" d="M0 0h24v24H0z"/> + <path d="M22 15h-3V3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-5.293 1.293l-6.4 6.4a.5.5 0 0 1-.654.047L8.8 22.1a1.5 1.5 0 0 1-.553-1.57L9.4 16H3a2 2 0 0 1-2-2v-2.104a2 2 0 0 1 .15-.762L4.246 3.62A1 1 0 0 1 5.17 3H16a1 1 0 0 1 1 1v11.586a1 1 0 0 1-.293.707z"/> + </g> +</svg> diff --git a/app/Resources/icons/thumb-up.svg b/app/Resources/icons/thumb-up.svg new file mode 100644 index 0000000000000000000000000000000000000000..f0d667027247a582b36b6cd825ad79e6d053d04d --- /dev/null +++ b/app/Resources/icons/thumb-up.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <g> + <path fill="none" d="M0 0h24v24H0z"/> + <path d="M2 9h3v12H2a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1zm5.293-1.293l6.4-6.4a.5.5 0 0 1 .654-.047l.853.64a1.5 1.5 0 0 1 .553 1.57L14.6 8H21a2 2 0 0 1 2 2v2.104a2 2 0 0 1-.15.762l-3.095 7.515a1 1 0 0 1-.925.619H8a1 1 0 0 1-1-1V8.414a1 1 0 0 1 .293-.707z"/> + </g> +</svg> diff --git a/app/Resources/styles/index.css b/app/Resources/styles/index.css index 23780f3328d793bf1a235ae9599ad8d8176e4b4e..37bc69b1d44b4aa97721fd9ab7bf63320ebe73d7 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 e3d2b794ac65b1072e5045477d4195180c663013..966ec60663d6f5f5580a5f1b30576251fd612c19 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 f0f279b48b99b82e492ebc6ffc31bdbdeb2fb462..6fb8e78144a7247093d78b36345176881454aece 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 a78dae29e22d5263cb599510a3eeb4417c0acc6f..c12c39b06793b97f003851f7da698ce327ada2b8 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 a3c192a24c12f24e72828968fdf9f688b545de69..238d3b964d998d527b6f12b6e5b88f882b6e4d03 100644 --- a/app/Views/admin/episode/publish.php +++ b/app/Views/admin/episode/publish.php @@ -27,9 +27,9 @@ <label for="message" class="text-lg font-semibold"><?= lang( - 'Episode.publish_form.status', + 'Episode.publish_form.post', ) ?></label> -<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.status_hint') ?></small> +<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.post_hint') ?></small> <div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl"> <div class="flex px-4 py-3"> <img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast diff --git a/app/Views/admin/episode/publish_edit.php b/app/Views/admin/episode/publish_edit.php index 8d273db3f0665747b4eb03f30c410ec184357b1a..b83914a027714227bf140781488ef4723dd0b87d 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="message" class="text-lg font-semibold"><?= lang( - 'Episode.publish_form.status', + 'Episode.publish_form.post', ) ?></label> -<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.status_hint') ?></small> +<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.post_hint') ?></small> <div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl"> <div class="flex px-4 py-3"> <img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast->actor @@ -42,7 +42,7 @@ <span class="text-sm text-gray-500 truncate">@<?= $podcast ->actor->username ?></span> </p> - <?= relative_time($status->published_at, 'text-xs text-gray-500') ?> + <?= relative_time($post->published_at, 'text-xs text-gray-500') ?> </div> </div> <div class="px-4 mb-2"> @@ -54,7 +54,7 @@ 'placeholder' => 'Write your message...', 'autofocus' => '' ], - old('message', $status->message, false), + old('message', $post->message, false), ['rows' => 2], ) ?> </div> diff --git a/app/Views/podcast/_partials/comment.php b/app/Views/podcast/_partials/comment.php new file mode 100644 index 0000000000000000000000000000000000000000..587df06942531685ba09fde1cd304c4b066fc2de --- /dev/null +++ b/app/Views/podcast/_partials/comment.php @@ -0,0 +1,43 @@ +<article class="relative z-10 flex w-full px-4 py-2 rounded-2xl"> + <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> + <div class="flex-1"> + <header class="w-full mb-2"> + <a href="<?= $comment->actor + ->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local + ? '' + : 'target="_blank" rel="noopener noreferrer"' ?>> + <span class="mr-2 font-semibold truncate"><?= $comment->actor + ->display_name ?></span> + <span class="text-sm text-gray-500 truncate">@<?= $comment->actor + ->username . + ($comment->actor->is_local + ? '' + : '@' . $comment->actor->domain) ?></span> + <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?> + </a> + </header> + <div class="mb-2 post-content"><?= $comment->message_html ?></div> + <div class="inline-flex gap-x-4"> + <?= anchor_popup( + route_to('comment-remote-action', $podcast->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'), + ], + ) ?> + <?= anchor_popup( + route_to('comment-remote-action', $podcast->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'), + ], + ) ?> + </div> + </div> +</article> diff --git a/app/Views/podcast/_partials/episode_card.php b/app/Views/podcast/_partials/episode_card.php index 1aff9de6b664e8b8e9766715e7d6cd23a64730c8..01b5f412d452e406285b888d1819f54a0efe4eb6 100644 --- a/app/Views/podcast/_partials/episode_card.php +++ b/app/Views/podcast/_partials/episode_card.php @@ -28,12 +28,12 @@ <?= anchor( route_to('episode', $podcast->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 8714792f5364b5135fe2191668d5bbf27c97787e..85679ae9d4e0547c10befa96365d1268011f5e56 100644 --- a/app/Views/podcast/_partials/header.php +++ b/app/Views/podcast/_partials/header.php @@ -50,8 +50,8 @@ <a href="<?= route_to( 'podcast-activity', $podcast->handle, - ) ?>" class="hover:underline"><?= lang('Podcast.statuses', [ - 'numberOfStatuses' => $podcast->actor->statuses_count, + ) ?>" class="hover:underline"><?= lang('Podcast.posts', [ + 'numberOfPosts' => $podcast->actor->posts_count, ]) ?></a> </div> </div> diff --git a/app/Views/podcast/_partials/post.php b/app/Views/podcast/_partials/post.php new file mode 100644 index 0000000000000000000000000000000000000000..7ca262668c9135d3736cd14fcfd8d9cf8fdee0cd --- /dev/null +++ b/app/Views/podcast/_partials/post.php @@ -0,0 +1,36 @@ +<article class="relative z-10 w-full bg-white shadow rounded-2xl"> + <header class="flex px-6 py-4"> + <img src="<?= $post->actor + ->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> + <div class="flex flex-col min-w-0"> + <a href="<?= $post->actor + ->uri ?>" class="flex items-baseline hover:underline" <?= $post + ->actor->is_local + ? '' + : 'target="_blank" rel="noopener noreferrer"' ?>> + <span class="mr-2 font-semibold truncate"><?= $post->actor + ->display_name ?></span> + <span class="text-sm text-gray-500 truncate">@<?= $post->actor + ->username . + ($post->actor->is_local + ? '' + : '@' . $post->actor->domain) ?></span> + </a> + <a href="<?= route_to('post', $podcast->handle, $post->id) ?>" + class="text-xs text-gray-500"> + <?= relative_time($post->published_at) ?> + </a> + </div> + </header> + <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div> + <?php if ($post->episode_id): ?> + <?= view('podcast/_partials/episode_preview_card', [ + 'episode' => $post->episode, + ]) ?> + <?php elseif ($post->has_preview_card): ?> + <?= view('podcast/_partials/preview_card', [ + 'preview_card' => $post->preview_card, + ]) ?> + <?php endif; ?> + <?= $this->include('podcast/_partials/post_actions') ?> +</article> diff --git a/app/Views/podcast/_partials/post_actions.php b/app/Views/podcast/_partials/post_actions.php new file mode 100644 index 0000000000000000000000000000000000000000..2468b3c1b54e7eceb4c0d2f57ff8c3f92b004bab --- /dev/null +++ b/app/Views/podcast/_partials/post_actions.php @@ -0,0 +1,36 @@ +<footer class="flex justify-around px-6 py-3"> + <?= anchor( + route_to('post', $podcast->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, + ]), + ], + ) ?> + <?= anchor_popup( + route_to('post-remote-action', $podcast->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, + ]), + ], + ) ?> + <?= anchor_popup( + route_to('post-remote-action', $podcast->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, + ]), + ], + ) ?> +</footer> 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 9ee2cf6ef8f82529592e5843ca2705f4710deb4f..4cb02a32d49647b11c2ffbaba67462c1edb20ae7 100644 --- a/app/Views/podcast/_partials/status_actions_authenticated.php +++ b/app/Views/podcast/_partials/post_actions_authenticated.php @@ -1,87 +1,87 @@ <footer class="px-6 py-3"> <form action="<?= route_to( - 'status-attempt-action', + 'post-attempt-action', interact_as_actor()->username, - $status->id, + $post->id, ) ?>" method="POST" class="flex justify-around"> <?= csrf_field() ?> <?= anchor( - route_to('status', $podcast->handle, $status->id), - icon('chat', 'text-2xl mr-1 text-gray-400') . $status->replies_count, + route_to('post', $podcast->handle, $post->id), + icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count, [ 'class' => 'inline-flex items-center hover:underline', - 'title' => lang('Status.replies', [ - 'numberOfReplies' => $status->replies_count, + 'title' => lang('Post.replies', [ + 'numberOfReplies' => $post->replies_count, ]), ], ) ?> <button type="submit" name="action" value="reblog" class="inline-flex items-center hover:underline" title="<?= lang( - 'Status.reblogs', + 'Post.reblogs', [ - 'numberOfReblogs' => $status->reblogs_count, + 'numberOfReblogs' => $post->reblogs_count, ], ) ?>"><?= icon('repeat', 'text-2xl mr-1 text-gray-400') . - $status->reblogs_count ?></button> + $post->reblogs_count ?></button> <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline" title="<?= lang( - 'Status.favourites', + 'Post.favourites', [ - 'numberOfFavourites' => $status->favourites_count, + 'numberOfFavourites' => $post->favourites_count, ], ) ?>"><?= icon('heart', 'text-2xl mr-1 text-gray-400') . - $status->favourites_count ?></button> - <button id="<?= $status->id . - '-more-dropdown' ?>" type="button" class="px-2 py-1 text-2xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $status->id . + $post->favourites_count ?></button> + <button id="<?= $post->id . + '-more-dropdown' ?>" type="button" class="px-2 py-1 text-2xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $post->id . '-more-dropdown-menu' ?>" aria-label="<?= lang( 'Common.more', ) ?>" aria-haspopup="true" aria-expanded="false"><?= icon('more') ?> </button> </form> - <nav id="<?= $status->id . - '-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $status->id . + <nav id="<?= $post->id . + '-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $post->id . '-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom"> <?= anchor( - route_to('status', $podcast->handle, $status->id), - lang('Status.expand'), + route_to('post', $podcast->handle, $post->id), + lang('Post.expand'), [ 'class' => 'px-4 py-1 hover:bg-gray-100', ], ) ?> <form action="<?= route_to( - 'status-attempt-block-actor', + 'post-attempt-block-actor', interact_as_actor()->username, - $status->id, + $post->id, ) ?>" method="POST"> <?= csrf_field() ?> <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang( - 'Status.block_actor', + 'Post.block_actor', [ - 'actorUsername' => $status->actor->username, + 'actorUsername' => $post->actor->username, ], ) ?></button> </form> <form action="<?= route_to( - 'status-attempt-block-domain', + 'post-attempt-block-domain', interact_as_actor()->username, - $status->id, + $post->id, ) ?>" method="POST"> <?= csrf_field() ?> <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang( - 'Status.block_domain', + 'Post.block_domain', [ - 'actorDomain' => $status->actor->domain, + 'actorDomain' => $post->actor->domain, ], ) ?></button> </form> - <?php if ($status->actor->is_local): ?> + <?php if ($post->actor->is_local): ?> <hr class="my-2" /> <form action="<?= route_to( - 'status-attempt-delete', - $status->actor->username, - $status->id, + 'post-attempt-delete', + $post->actor->username, + $post->id, ) ?>" method="POST"> <?= csrf_field() ?> <button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang( - 'Status.delete', + 'Post.delete', ) ?></button> </form> <?php endif; ?> diff --git a/app/Views/podcast/_partials/post_authenticated.php b/app/Views/podcast/_partials/post_authenticated.php new file mode 100644 index 0000000000000000000000000000000000000000..1ec49dedef1710da6feafb1240908283bbc484e0 --- /dev/null +++ b/app/Views/podcast/_partials/post_authenticated.php @@ -0,0 +1,36 @@ +<article class="relative z-10 w-full bg-white shadow-md rounded-2xl"> + <header class="flex px-6 py-4"> + <img src="<?= $post->actor + ->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> + <div class="flex flex-col min-w-0"> + <a href="<?= $post->actor + ->uri ?>" class="flex items-baseline hover:underline" <?= $post + ->actor->is_local + ? '' + : 'target="_blank" rel="noopener noreferrer"' ?>> + <span class="mr-2 font-semibold truncate"><?= $post->actor + ->display_name ?></span> + <span class="text-sm text-gray-500 truncate">@<?= $post->actor + ->username . + ($post->actor->is_local + ? '' + : '@' . $post->actor->domain) ?></span> + </a> + <a href="<?= route_to('post', $podcast->handle, $post->id) ?>" + class="text-xs text-gray-500"> + <?= relative_time($post->published_at) ?> + </a> + </div> + </header> + <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div> + <?php if ($post->episode_id): ?> + <?= view('podcast/_partials/episode_preview_card', [ + 'episode' => $post->episode, + ]) ?> + <?php elseif ($post->has_preview_card): ?> + <?= view('podcast/_partials/preview_card', [ + 'preview_card' => $post->preview_card, + ]) ?> + <?php endif; ?> + <?= $this->include('podcast/_partials/post_actions_authenticated') ?> +</article> diff --git a/app/Views/podcast/_partials/status_with_replies.php b/app/Views/podcast/_partials/post_with_replies.php similarity index 53% rename from app/Views/podcast/_partials/status_with_replies.php rename to app/Views/podcast/_partials/post_with_replies.php index 5e10b01d03442aa942db17481a07beb1dd449e27..f002d786de82dd91fd5562cb484e0812a6cc8e26 100644 --- a/app/Views/podcast/_partials/status_with_replies.php +++ b/app/Views/podcast/_partials/post_with_replies.php @@ -1,10 +1,10 @@ -<?= $this->include('podcast/_partials/status') ?> -<div class="-mt-2 overflow-hidden border-b border-l border-r status-replies rounded-b-xl"> +<?= $this->include('podcast/_partials/post') ?> +<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl"> <div class="px-6 pt-8 pb-4 bg-gray-50"> <?= anchor_popup( - route_to('status-remote-action', $podcast->handle, $status->id, 'reply'), - lang('Status.reply_to', ['actorUsername' => $status->actor->username]), + route_to('post-remote-action', $podcast->handle, $post->id, 'reply'), + lang('Post.reply_to', ['actorUsername' => $post->actor->username]), [ 'class' => 'text-center justify-center font-semibold rounded-full shadow relative z-10 px-4 py-2 w-full bg-rose-600 text-white inline-flex items-center hover:bg-rose-700', @@ -15,8 +15,8 @@ </div> -<?php if ($status->has_replies): ?> - <?php foreach ($status->replies as $reply): ?> +<?php if ($post->has_replies): ?> + <?php foreach ($post->replies as $reply): ?> <?= view('podcast/_partials/reply', ['reply' => $reply]) ?> <?php endforeach; ?> <?php endif; ?> diff --git a/app/Views/podcast/_partials/status_with_replies_authenticated.php b/app/Views/podcast/_partials/post_with_replies_authenticated.php similarity index 65% rename from app/Views/podcast/_partials/status_with_replies_authenticated.php rename to app/Views/podcast/_partials/post_with_replies_authenticated.php index eb6d33081766f40c61fac859c5bb3c5487a1edb4..4b71fe45bb79fb4e830f3a081397cf34256a801e 100644 --- a/app/Views/podcast/_partials/status_with_replies_authenticated.php +++ b/app/Views/podcast/_partials/post_with_replies_authenticated.php @@ -1,7 +1,7 @@ -<?= $this->include('podcast/_partials/status_authenticated') ?> -<div class="-mt-2 overflow-hidden border-b border-l border-r status-replies rounded-b-xl"> +<?= $this->include('podcast/_partials/post_authenticated') ?> +<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl"> <?= form_open( - route_to('status-attempt-action', interact_as_actor()->username, $status->id), + route_to('post-attempt-action', interact_as_actor()->username, $post->id), [ 'class' => 'bg-gray-50 flex px-6 pt-8 pb-4', ], @@ -16,8 +16,8 @@ 'name' => 'message', 'class' => 'form-textarea mb-4 w-full', 'required' => 'required', - 'placeholder' => lang('Status.form.reply_to_placeholder', [ - 'actorUsername' => $status->actor->username, + 'placeholder' => lang('Post.form.reply_to_placeholder', [ + 'actorUsername' => $post->actor->username, ]), ], old('message', '', false), @@ -26,7 +26,7 @@ ], ) ?> <?= button( - lang('Status.form.submit_reply'), + lang('Post.form.submit_reply'), '', ['variant' => 'primary', 'size' => 'small'], [ @@ -39,8 +39,8 @@ </div> <?= form_close() ?> -<?php if ($status->has_replies): ?> - <?php foreach ($status->replies as $reply): ?> +<?php if ($post->has_replies): ?> + <?php foreach ($post->replies as $reply): ?> <?= view('podcast/_partials/reply_authenticated', [ 'reply' => $reply, ]) ?> diff --git a/app/Views/podcast/_partials/reblog.php b/app/Views/podcast/_partials/reblog.php index 2abc307e99b62a17b21cc2304b586adbb533cd86..84f3b782c5ac29a365dd064a8ae0ceab025e4a2c 100644 --- a/app/Views/podcast/_partials/reblog.php +++ b/app/Views/podcast/_partials/reblog.php @@ -1,43 +1,43 @@ -<article class="relative z-10 w-full bg-white shadow-md rounded-2xl"> +<article class="relative z-10 w-full bg-white shadow rounded-2xl"> <p class="inline-flex px-6 pt-4 text-xs text-gray-700"><?= icon( 'repeat', 'text-lg mr-2 text-gray-400', ) . - lang('Status.actor_shared', [ - 'actor' => $status->actor->display_name, + lang('Post.actor_shared', [ + 'actor' => $post->actor->display_name, ]) ?></p> <header class="flex px-6 py-4"> - <img src="<?= $status->actor - ->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> + <img src="<?= $post->actor + ->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> <div class="flex flex-col min-w-0"> - <a href="<?= $status->actor - ->uri ?>" class="flex items-baseline hover:underline" <?= $status + <a href="<?= $post->actor + ->uri ?>" class="flex items-baseline hover:underline" <?= $post ->actor->is_local ? '' : 'target="_blank" rel="noopener noreferrer"' ?>> - <span class="mr-2 font-semibold truncate"><?= $status->actor + <span class="mr-2 font-semibold truncate"><?= $post->actor ->display_name ?></span> - <span class="text-sm text-gray-500 truncate">@<?= $status->actor + <span class="text-sm text-gray-500 truncate">@<?= $post->actor ->username . - ($status->actor->is_local + ($post->actor->is_local ? '' - : '@' . $status->actor->domain) ?></span> + : '@' . $post->actor->domain) ?></span> </a> - <a href="<?= route_to('status', $podcast->handle, $status->id) ?>" + <a href="<?= route_to('post', $podcast->handle, $post->id) ?>" class="text-xs text-gray-500"> - <?= relative_time($status->published_at) ?> + <?= relative_time($post->published_at) ?> </a> </div> </header> - <div class="px-6 mb-4 status-content"><?= $status->message_html ?></div> - <?php if ($status->episode_id): ?> + <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div> + <?php if ($post->episode_id): ?> <?= view('podcast/_partials/episode_preview_card', [ - 'episode' => $status->episode, + 'episode' => $post->episode, ]) ?> - <?php elseif ($status->has_preview_card): ?> + <?php elseif ($post->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ - 'preview_card' => $status->preview_card, + 'preview_card' => $post->preview_card, ]) ?> <?php endif; ?> - <?= $this->include('podcast/_partials/status_actions') ?> + <?= $this->include('podcast/_partials/post_actions') ?> </article> diff --git a/app/Views/podcast/_partials/reblog_authenticated.php b/app/Views/podcast/_partials/reblog_authenticated.php index 11c5d3012e7931ee6b1cbfd83184aa81fccacfb1..9defed3cbebe0cba724c779167b7adc2569798a5 100644 --- a/app/Views/podcast/_partials/reblog_authenticated.php +++ b/app/Views/podcast/_partials/reblog_authenticated.php @@ -1,43 +1,43 @@ -<article class="relative z-10 w-full bg-white shadow-md rounded-2xl"> +<article class="relative z-10 w-full bg-white shadow rounded-2xl"> <p class="inline-flex px-6 pt-4 text-xs text-gray-700"><?= icon( 'repeat', 'text-lg mr-2 text-gray-400', ) . - lang('Status.actor_shared', [ - 'actor' => $status->actor->display_name, + lang('Post.actor_shared', [ + 'actor' => $post->actor->display_name, ]) ?></p> <header class="flex px-6 py-4"> - <img src="<?= $status->actor - ->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> + <img src="<?= $post->actor + ->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> <div class="flex flex-col min-w-0"> - <a href="<?= $status->actor - ->uri ?>" class="flex items-baseline hover:underline" <?= $status + <a href="<?= $post->actor + ->uri ?>" class="flex items-baseline hover:underline" <?= $post ->actor->is_local ? '' : 'target="_blank" rel="noopener noreferrer"' ?>> - <span class="mr-2 font-semibold truncate"><?= $status->actor + <span class="mr-2 font-semibold truncate"><?= $post->actor ->display_name ?></span> - <span class="text-sm text-gray-500 truncate">@<?= $status->actor + <span class="text-sm text-gray-500 truncate">@<?= $post->actor ->username . - ($status->actor->is_local + ($post->actor->is_local ? '' - : '@' . $status->actor->domain) ?></span> + : '@' . $post->actor->domain) ?></span> </a> - <a href="<?= route_to('status', $podcast->handle, $status->id) ?>" + <a href="<?= route_to('post', $podcast->handle, $post->id) ?>" class="text-xs text-gray-500"> - <?= relative_time($status->published_at) ?> + <?= relative_time($post->published_at) ?> </a> </div> </header> - <div class="px-6 mb-4 status-content"><?= $status->message_html ?></div> - <?php if ($status->episode_id): ?> + <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div> + <?php if ($post->episode_id): ?> <?= view('podcast/_partials/episode_preview_card', [ - 'episode' => $status->episode, + 'episode' => $post->episode, ]) ?> - <?php elseif ($status->has_preview_card): ?> + <?php elseif ($post->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ - 'preview_card' => $status->preview_card, + 'preview_card' => $post->preview_card, ]) ?> <?php endif; ?> - <?= $this->include('podcast/_partials/status_actions_authenticated') ?> + <?= $this->include('podcast/_partials/post_actions_authenticated') ?> </article> diff --git a/app/Views/podcast/_partials/reply.php b/app/Views/podcast/_partials/reply.php index 12977a3608ae68821ce6d8e972f3e1fcba34e71a..7d672725ed294032e6fdb6b0ad212798aa1ffcea 100644 --- a/app/Views/podcast/_partials/reply.php +++ b/app/Views/podcast/_partials/reply.php @@ -11,9 +11,9 @@ ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply ->actor->username . ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a> - <?= relative_time($status->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?> + <?= relative_time($post->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?> </header> - <p class="mb-2 status-content"><?= $reply->message_html ?></p> + <p class="mb-2 post-content"><?= $reply->message_html ?></p> <?php if ($reply->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $reply->preview_card, diff --git a/app/Views/podcast/_partials/reply_actions.php b/app/Views/podcast/_partials/reply_actions.php index 0445f454993c2555d603057117f62b2c2569ba32..6bdb11178b223f3a1c6527a1eaab9397ec0458da 100644 --- a/app/Views/podcast/_partials/reply_actions.php +++ b/app/Views/podcast/_partials/reply_actions.php @@ -1,34 +1,34 @@ <footer class="mt-2 space-x-6 text-sm"> <?= anchor( - route_to('status', $podcast->handle, $reply->id), + route_to('post', $podcast->handle, $reply->id), icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count, [ 'class' => 'inline-flex items-center hover:underline', - 'title' => lang('Status.replies', [ + 'title' => lang('Post.replies', [ 'numberOfReplies' => $reply->replies_count, ]), ], ) ?> <?= anchor_popup( - route_to('status-remote-action', $podcast->handle, $reply->id, 'reblog'), + route_to('post-remote-action', $podcast->handle, $reply->id, 'reblog'), icon('repeat', 'text-xl mr-1 text-gray-400') . $reply->reblogs_count, [ 'class' => 'inline-flex items-center hover:underline', 'width' => 420, 'height' => 620, - 'title' => lang('Status.reblogs', [ + 'title' => lang('Post.reblogs', [ 'numberOfReblogs' => $reply->reblogs_count, ]), ], ) ?> <?= anchor_popup( - route_to('status-remote-action', $podcast->handle, $reply->id, 'favourite'), + route_to('post-remote-action', $podcast->handle, $reply->id, 'favourite'), icon('heart', 'text-xl mr-1 text-gray-400') . $reply->favourites_count, [ 'class' => 'inline-flex items-center hover:underline', 'width' => 420, 'height' => 620, - 'title' => lang('Status.favourites', [ + 'title' => lang('Post.favourites', [ 'numberOfFavourites' => $reply->favourites_count, ]), ], diff --git a/app/Views/podcast/_partials/reply_actions_authenticated.php b/app/Views/podcast/_partials/reply_actions_authenticated.php index f876b85a96e127b4932fff40281840df9ca2789c..249eb34afe3e93497a376db723b5498f2cec5128 100644 --- a/app/Views/podcast/_partials/reply_actions_authenticated.php +++ b/app/Views/podcast/_partials/reply_actions_authenticated.php @@ -1,29 +1,29 @@ <footer class="mt-2 text-sm"> <form action="<?= route_to( - 'status-attempt-action', + 'post-attempt-action', interact_as_actor()->username, $reply->id, ) ?>" method="POST" class="flex items-start"> <?= csrf_field() ?> <?= anchor( - route_to('status', $podcast->handle, $reply->id), + route_to('post', $podcast->handle, $reply->id), icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count, [ 'class' => 'inline-flex items-center mr-6 hover:underline', - 'title' => lang('Status.replies', [ + 'title' => lang('Post.replies', [ 'numberOfReplies' => $reply->replies_count, ]), ], ) ?> <button type="submit" name="action" value="reblog" class="inline-flex items-center mr-6 hover:underline" title="<?= lang( - 'Status.reblogs', + 'Post.reblogs', [ 'numberOfReblogs' => $reply->reblogs_count, ], ) ?>"><?= icon('repeat', 'text-xl mr-1 text-gray-400') . $reply->reblogs_count ?></button> <button type="submit" name="action" value="favourite" class="inline-flex items-center mr-6 hover:underline" title="<?= lang( - 'Status.favourites', + 'Post.favourites', [ 'numberOfFavourites' => $reply->favourites_count, ], @@ -40,33 +40,33 @@ '-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $reply->id . '-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom"> <?= anchor( - route_to('status', $podcast->handle, $reply->id), - lang('Status.expand'), + route_to('post', $podcast->handle, $reply->id), + lang('Post.expand'), [ 'class' => 'px-4 py-1 hover:bg-gray-100', ], ) ?> <form action="<?= route_to( - 'status-attempt-block-actor', + 'post-attempt-block-actor', interact_as_actor()->username, $reply->id, ) ?>" method="POST"> <?= csrf_field() ?> <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang( - 'Status.block_actor', + 'Post.block_actor', [ 'actorUsername' => $reply->actor->username, ], ) ?></button> </form> <form action="<?= route_to( - 'status-attempt-block-domain', + 'post-attempt-block-domain', interact_as_actor()->username, $reply->id, ) ?>" method="POST"> <?= csrf_field() ?> <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang( - 'Status.block_domain', + 'Post.block_domain', [ 'actorDomain' => $reply->actor->domain, ], @@ -75,13 +75,13 @@ <?php if ($reply->actor->is_local): ?> <hr class="my-2" /> <form action="<?= route_to( - 'status-attempt-delete', + 'post-attempt-delete', $reply->actor->username, $reply->id, ) ?>" method="POST"> <?= csrf_field() ?> <button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang( - 'Status.delete', + 'Post.delete', ) ?></button> </form> <?php endif; ?> diff --git a/app/Views/podcast/_partials/reply_authenticated.php b/app/Views/podcast/_partials/reply_authenticated.php index 1d85461cc949be7cec42e70d7922765ee37a58d0..ff154acd54540059ecd17a85332b1d46fb49ba88 100644 --- a/app/Views/podcast/_partials/reply_authenticated.php +++ b/app/Views/podcast/_partials/reply_authenticated.php @@ -11,9 +11,9 @@ ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply ->actor->username . ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a> - <?= relative_time($status->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?> + <?= relative_time($post->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?> </header> - <p class="mb-2 status-content"><?= $reply->message_html ?></p> + <p class="mb-2 post-content"><?= $reply->message_html ?></p> <?php if ($reply->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $reply->preview_card, diff --git a/app/Views/podcast/_partials/status.php b/app/Views/podcast/_partials/status.php deleted file mode 100644 index 21d3d0807ce7399410d6bb66786405d786dc012d..0000000000000000000000000000000000000000 --- a/app/Views/podcast/_partials/status.php +++ /dev/null @@ -1,36 +0,0 @@ -<article class="relative z-10 w-full bg-white shadow rounded-2xl"> - <header class="flex px-6 py-4"> - <img src="<?= $status->actor - ->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> - <div class="flex flex-col min-w-0"> - <a href="<?= $status->actor - ->uri ?>" class="flex items-baseline hover:underline" <?= $status - ->actor->is_local - ? '' - : 'target="_blank" rel="noopener noreferrer"' ?>> - <span class="mr-2 font-semibold truncate"><?= $status->actor - ->display_name ?></span> - <span class="text-sm text-gray-500 truncate">@<?= $status->actor - ->username . - ($status->actor->is_local - ? '' - : '@' . $status->actor->domain) ?></span> - </a> - <a href="<?= route_to('status', $podcast->handle, $status->id) ?>" - class="text-xs text-gray-500"> - <?= relative_time($status->published_at) ?> - </a> - </div> - </header> - <div class="px-6 mb-4 status-content"><?= $status->message_html ?></div> - <?php if ($status->episode_id): ?> - <?= view('podcast/_partials/episode_preview_card', [ - 'episode' => $status->episode, - ]) ?> - <?php elseif ($status->has_preview_card): ?> - <?= view('podcast/_partials/preview_card', [ - 'preview_card' => $status->preview_card, - ]) ?> - <?php endif; ?> - <?= $this->include('podcast/_partials/status_actions') ?> -</article> diff --git a/app/Views/podcast/_partials/status_actions.php b/app/Views/podcast/_partials/status_actions.php deleted file mode 100644 index 36d7d9e7c2212d1028f598787b25ecd70bcb48ad..0000000000000000000000000000000000000000 --- a/app/Views/podcast/_partials/status_actions.php +++ /dev/null @@ -1,36 +0,0 @@ -<footer class="flex justify-around px-6 py-3"> - <?= anchor( - route_to('status', $podcast->handle, $status->id), - icon('chat', 'text-2xl mr-1 text-gray-400') . $status->replies_count, - [ - 'class' => 'inline-flex items-center hover:underline', - 'title' => lang('Status.replies', [ - 'numberOfReplies' => $status->replies_count, - ]), - ], - ) ?> - <?= anchor_popup( - route_to('status-remote-action', $podcast->handle, $status->id, 'reblog'), - icon('repeat', 'text-2xl mr-1 text-gray-400') . $status->reblogs_count, - [ - 'class' => 'inline-flex items-center hover:underline', - 'width' => 420, - 'height' => 620, - 'title' => lang('Status.reblogs', [ - 'numberOfReblogs' => $status->reblogs_count, - ]), - ], - ) ?> - <?= anchor_popup( - route_to('status-remote-action', $podcast->handle, $status->id, 'favourite'), - icon('heart', 'text-2xl mr-1 text-gray-400') . $status->favourites_count, - [ - 'class' => 'inline-flex items-center hover:underline', - 'width' => 420, - 'height' => 620, - 'title' => lang('Status.favourites', [ - 'numberOfFavourites' => $status->favourites_count, - ]), - ], - ) ?> -</footer> diff --git a/app/Views/podcast/_partials/status_authenticated.php b/app/Views/podcast/_partials/status_authenticated.php deleted file mode 100644 index 143be143fcc614e91ed46084ffd1cc53263bcd00..0000000000000000000000000000000000000000 --- a/app/Views/podcast/_partials/status_authenticated.php +++ /dev/null @@ -1,36 +0,0 @@ -<article class="relative z-10 w-full bg-white shadow-md rounded-2xl"> - <header class="flex px-6 py-4"> - <img src="<?= $status->actor - ->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> - <div class="flex flex-col min-w-0"> - <a href="<?= $status->actor - ->uri ?>" class="flex items-baseline hover:underline" <?= $status - ->actor->is_local - ? '' - : 'target="_blank" rel="noopener noreferrer"' ?>> - <span class="mr-2 font-semibold truncate"><?= $status->actor - ->display_name ?></span> - <span class="text-sm text-gray-500 truncate">@<?= $status->actor - ->username . - ($status->actor->is_local - ? '' - : '@' . $status->actor->domain) ?></span> - </a> - <a href="<?= route_to('status', $podcast->handle, $status->id) ?>" - class="text-xs text-gray-500"> - <?= relative_time($status->published_at) ?> - </a> - </div> - </header> - <div class="px-6 mb-4 status-content"><?= $status->message_html ?></div> - <?php if ($status->episode_id): ?> - <?= view('podcast/_partials/episode_preview_card', [ - 'episode' => $status->episode, - ]) ?> - <?php elseif ($status->has_preview_card): ?> - <?= view('podcast/_partials/preview_card', [ - 'preview_card' => $status->preview_card, - ]) ?> - <?php endif; ?> - <?= $this->include('podcast/_partials/status_actions_authenticated') ?> -</article> diff --git a/app/Views/podcast/activity.php b/app/Views/podcast/activity.php index 75bdbee563a001f60edda1ab2af9b44ff8eceba1..11959f914c656def98bed2baacdbba1353d06584 100644 --- a/app/Views/podcast/activity.php +++ b/app/Views/podcast/activity.php @@ -40,13 +40,13 @@ </nav> <section class="max-w-2xl px-6 py-8 mx-auto space-y-8"> -<?php foreach ($statuses as $status): ?> - <?php if ($status->reblog_of_id !== null): ?> +<?php foreach ($posts as $post): ?> + <?php if ($post->reblog_of_id !== null): ?> <?= view('podcast/_partials/reblog', [ - 'status' => $status->reblog_of_status, + 'post' => $post->reblog_of_post, ]) ?> <?php else: ?> - <?= view('podcast/_partials/status', ['status' => $status]) ?> + <?= view('podcast/_partials/post', ['post' => $post]) ?> <?php endif; ?> <?php endforeach; ?> </section> diff --git a/app/Views/podcast/activity_authenticated.php b/app/Views/podcast/activity_authenticated.php index d8404171a1c38636c19c26d86c02437c7013882e..20428570736d4571ea7e8301cf165ca903eae7a1 100644 --- a/app/Views/podcast/activity_authenticated.php +++ b/app/Views/podcast/activity_authenticated.php @@ -40,7 +40,7 @@ </nav> <section class="max-w-2xl px-6 py-8 mx-auto"> -<?= form_open(route_to('status-attempt-create', interact_as_actor()->username), [ +<?= form_open(route_to('post-attempt-create', interact_as_actor()->username), [ 'class' => 'flex p-4 bg-white shadow rounded-xl', ]) ?> <?= csrf_field() ?> @@ -57,7 +57,7 @@ 'name' => 'message', 'class' => 'form-textarea', 'required' => 'required', - 'placeholder' => lang('Status.form.message_placeholder'), + 'placeholder' => lang('Post.form.message_placeholder'), ], old('message', '', false), ['rows' => 2], @@ -67,7 +67,7 @@ 'name' => 'episode_url', 'class' => 'form-input mb-2', 'placeholder' => - lang('Status.form.episode_url_placeholder') . + lang('Post.form.episode_url_placeholder') . ' (' . lang('Common.optional') . ')', @@ -75,7 +75,7 @@ ]) ?> <?= button( - lang('Status.form.submit'), + lang('Post.form.submit'), '', ['variant' => 'primary', 'size' => 'small'], ['type' => 'submit', 'class' => 'self-end'], @@ -85,13 +85,13 @@ <hr class="my-4 border-2 border-pine-100"> <div class="space-y-8"> -<?php foreach ($statuses as $status): ?> - <?php if ($status->reblog_of_id !== null): ?> +<?php foreach ($posts as $post): ?> + <?php if ($post->reblog_of_id !== null): ?> <?= view('podcast/_partials/reblog_authenticated', [ - 'status' => $status->reblog_of_status, + 'post' => $post->reblog_of_post, ]) ?> <?php else: ?> - <?= view('podcast/_partials/status_authenticated', ['status' => $status]) ?> + <?= view('podcast/_partials/post_authenticated', ['post' => $post]) ?> <?php endif; ?> <?php endforeach; ?> </div> diff --git a/app/Views/podcast/episode.php b/app/Views/podcast/episode.php index 5d7fb5550ef248c235bbd6014c59b5baab53e848..b95c2249b406ee4356da44cc58a60f3826300859 100644 --- a/app/Views/podcast/episode.php +++ b/app/Views/podcast/episode.php @@ -65,46 +65,6 @@ <?= format_duration($episode->audio_file_duration) ?> </time> </div> - <div class="mb-2 space-x-4 text-sm"> - <?= anchor( - route_to('episode', $podcast->handle, $episode->slug), - icon('chat', 'text-xl mr-1 text-gray-400') . - $episode->statuses_total, - [ - 'class' => - 'inline-flex items-center hover:underline', - 'title' => lang('Episode.total_statuses', [ - 'numberOfTotalStatuses' => $episode->statuses_total, - ]), - ], - ) ?> - <?= anchor( - route_to('episode', $podcast->handle, $episode->slug), - icon('repeat', 'text-xl mr-1 text-gray-400') . - $episode->reblogs_total, - [ - 'class' => - 'inline-flex items-center hover:underline', - 'title' => lang('Episode.total_reblogs', [ - 'numberOfTotalReblogs' => - $episode->reblogs_total, - ]), - ], - ) ?> - <?= anchor( - route_to('episode', $podcast->handle, $episode->slug), - icon('heart', 'text-xl mr-1 text-gray-400') . - $episode->favourites_total, - [ - 'class' => - 'inline-flex items-center hover:underline', - 'title' => lang('Episode.total_favourites', [ - 'numberOfTotalFavourites' => - $episode->favourites_total, - ]), - ], - ) ?> - </div> <?= location_link($episode->location, 'text-sm mb-4') ?> <?= person_list($episode->persons) ?> <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?> @@ -113,27 +73,26 @@ </header> <div class="tabset"> - <?php if ($episode->statuses): ?> - - <input type="radio" name="tabset" id="activity" aria-controls="activity" checked="checked" /> - <label for="activity"><?= lang('Episode.activity') ?></label> - <?php endif; ?> + <input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" /> + <label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label> + + <input type="radio" name="tabset" id="activity" aria-controls="activity" /> + <label for="activity"><?= lang('Episode.activity') . ' (' . $episode->posts_count . ')' ?></label> - <input type="radio" name="tabset" id="description" aria-controls="description" <?= $episode->statuses - ? '' - : 'checked="checked"' ?> /> - <label for="description" class="<?= $episode->statuses - ? '' - : 'col-span-2' ?>"><?= lang('Episode.description') ?></label> + <input type="radio" name="tabset" id="description" aria-controls="description" /> + <label for="description"><?= lang('Episode.description') ?></label> <div class="tab-panels"> - <?php if ($episode->statuses): ?> - <section id="activity" class="space-y-8 tab-panel"> - <?php foreach ($episode->statuses as $status): ?> - <?= view('podcast/_partials/status', ['status' => $status]) ?> - <?php endforeach; ?> - </section> - <?php endif; ?> + <section id="comments" class="space-y-6 tab-panel"> + <?php foreach ($episode->comments as $comment): ?> + <?= view('podcast/_partials/comment', ['comment' => $comment]) ?> + <?php endforeach; ?> + </section> + <section id="activity" class="space-y-8 tab-panel"> + <?php foreach ($episode->posts as $post): ?> + <?= view('podcast/_partials/post', ['post' => $post]) ?> + <?php endforeach; ?> + </section> <section id="description" class="prose tab-panel"> <?= $episode->getDescriptionHtml('-+Website+-') ?> </section> diff --git a/app/Views/podcast/episode_authenticated.php b/app/Views/podcast/episode_authenticated.php index 743230cce4258cb9107cf33bb030a2de965f514b..b62bb0ada6a91ab98b060f0e9dfd7d2a275a2cf9 100644 --- a/app/Views/podcast/episode_authenticated.php +++ b/app/Views/podcast/episode_authenticated.php @@ -65,46 +65,6 @@ <?= format_duration($episode->audio_file_duration) ?> </time> </div> - <div class="mb-2 space-x-4 text-sm"> - <?= anchor( - route_to('episode', $podcast->handle, $episode->slug), - icon('chat', 'text-xl mr-1 text-gray-400') . - $episode->statuses_total, - [ - 'class' => - 'inline-flex items-center hover:underline', - 'title' => lang('Episode.total_statuses', [ - 'numberOfTotalStatuses' => $episode->statuses_total, - ]), - ], - ) ?> - <?= anchor( - route_to('episode', $podcast->handle, $episode->slug), - icon('repeat', 'text-xl mr-1 text-gray-400') . - $episode->reblogs_total, - [ - 'class' => - 'inline-flex items-center hover:underline', - 'title' => lang('Episode.total_reblogs', [ - 'numberOfTotalReblogs' => - $episode->reblogs_total, - ]), - ], - ) ?> - <?= anchor( - route_to('episode', $podcast->handle, $episode->slug), - icon('heart', 'text-xl mr-1 text-gray-400') . - $episode->favourites_total, - [ - 'class' => - 'inline-flex items-center hover:underline', - 'title' => lang('Episode.total_favourites', [ - 'numberOfTotalFavourites' => - $episode->favourites_total, - ]), - ], - ) ?> - </div> <?= location_link($episode->location, 'text-sm mb-4') ?> <?= person_list($episode->persons) ?> <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?> @@ -113,15 +73,58 @@ </header> <div class="tabset"> - <input type="radio" name="tabset" id="activity" aria-controls="activity" checked="checked" /> - <label for="activity"><?= lang('Episode.activity') ?></label> + <input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" /> + <label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label> + + <input type="radio" name="tabset" id="activity" aria-controls="activity" /> + <label for="activity"><?= lang('Episode.activity') . ' ('. $episode->posts_count .')' ?></label> <input type="radio" name="tabset" id="description" aria-controls="description" /> <label for="description"><?= lang('Episode.description') ?></label> <div class="tab-panels"> + <section id="comments" class="space-y-6 tab-panel"> + <?= form_open(route_to('comment-attempt-create', $podcast->id, $episode->id), [ + 'class' => 'flex p-4 bg-white shadow rounded-xl', + ]) ?> + <?= csrf_field() ?> + + <?= view('_message_block') ?> + + <img src="<?= interact_as_actor() + ->avatar_image_url ?>" alt="<?= interact_as_actor()->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> + <div class="flex flex-col flex-1 min-w-0"> + <?= form_textarea( + [ + 'id' => 'message', + 'name' => 'message', + 'class' => 'form-textarea mb-2', + 'required' => 'required', + 'placeholder' => lang( + 'Comment.form.episode_message_placeholder', + ), + ], + old('message', '', false), + [ + 'rows' => 2, + ], + ) ?> + + <?= button( + lang('Comment.form.submit'), + '', + ['variant' => 'primary', 'size' => 'small'], + ['type' => 'submit', 'class' => 'self-end'], + ) ?> + </div> + <?= form_close() ?> + <hr class="my-4 border border-pine-100"> + <?php foreach ($episode->comments as $comment): ?> + <?= view('podcast/_partials/comment', ['comment' => $comment]) ?> + <?php endforeach; ?> + </section> <section id="activity" class="space-y-8 tab-panel"> - <?= form_open(route_to('status-attempt-create', $podcast->handle), [ + <?= form_open(route_to('post-attempt-create', $podcast->handle), [ 'class' => 'flex p-4 bg-white shadow rounded-xl', ]) ?> <?= csrf_field() ?> @@ -129,8 +132,7 @@ <?= view('_message_block') ?> <img src="<?= interact_as_actor() - ->avatar_image_url ?>" alt="<?= interact_as_actor() - ->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> + ->avatar_image_url ?>" alt="<?= interact_as_actor()->display_name ?>" class="w-12 h-12 mr-4 rounded-full" /> <div class="flex flex-col flex-1 min-w-0"> <?= form_textarea( [ @@ -139,7 +141,7 @@ 'class' => 'form-textarea mb-2', 'required' => 'required', 'placeholder' => lang( - 'Status.form.episode_message_placeholder', + 'Post.form.episode_message_placeholder', ), ], old('message', '', false), @@ -154,7 +156,7 @@ 'type' => 'hidden', ]) ?> <?= button( - lang('Status.form.submit'), + lang('Post.form.submit'), '', ['variant' => 'primary', 'size' => 'small'], ['type' => 'submit', 'class' => 'self-end'], @@ -162,9 +164,9 @@ </div> <?= form_close() ?> <hr class="my-4 border border-pine-100"> - <?php foreach ($episode->statuses as $status): ?> - <?= view('podcast/_partials/status_authenticated', [ - 'status' => $status, + <?php foreach ($episode->posts as $post): ?> + <?= view('podcast/_partials/post_authenticated', [ + 'post' => $post, ]) ?> <?php endforeach; ?> </section> diff --git a/app/Views/podcast/post.php b/app/Views/podcast/post.php new file mode 100644 index 0000000000000000000000000000000000000000..45f75cc2bbce6a54088fcf5c0a365a44613e11db --- /dev/null +++ b/app/Views/podcast/post.php @@ -0,0 +1,38 @@ +<?= $this->extend('podcast/_layout') ?> + +<?= $this->section('meta-tags') ?> + <title><?= lang('Post.title', [ + 'actorDisplayName' => $post->actor->display_name, + ]) ?></title> + <meta name="description" content="<?= $post->message ?>"/> + <meta property="og:title" content="<?= lang('Post.title', [ + 'actorDisplayName' => $post->actor->display_name, + ]) ?>"/> + <meta property="og:locale" content="<?= service( + 'request', + )->getLocale() ?>" /> + <meta property="og:site_name" content="<?= $post->actor->display_name ?>" /> + <meta property="og:url" content="<?= current_url() ?>" /> + <meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" /> + <meta property="og:description" content="<?= $post->message ?>" /> +<?= $this->endSection() ?> + +<?= $this->section('content') ?> +<div class="max-w-2xl px-6 mx-auto"> + <nav class="py-3"> + <a href="<?= route_to('podcast-activity', $podcast->handle) ?>" + class="inline-flex items-center px-4 py-2 text-sm"><?= icon( + 'arrow-left', + 'mr-2 text-lg', + ) . + lang('Post.back_to_actor_posts', [ + 'actor' => $post->actor->display_name, + ]) ?></a> + </nav> + <div class="pb-12"> + <?= $this->include('podcast/_partials/post_with_replies') ?> + </div> +</div> + +<?= $this->endSection() +?> diff --git a/app/Views/podcast/status_authenticated.php b/app/Views/podcast/post_authenticated.php similarity index 51% rename from app/Views/podcast/status_authenticated.php rename to app/Views/podcast/post_authenticated.php index be5f41804887072c26d7ef015ee0068b0e4ae971..e2e3b9569697edff511a54354a22d52709a25fa1 100644 --- a/app/Views/podcast/status_authenticated.php +++ b/app/Views/podcast/post_authenticated.php @@ -1,20 +1,20 @@ <?= $this->extend('podcast/_layout_authenticated') ?> <?= $this->section('meta-tags') ?> - <title><?= lang('Status.title', [ - 'actorDisplayName' => $status->actor->display_name, + <title><?= lang('Post.title', [ + 'actorDisplayName' => $post->actor->display_name, ]) ?></title> - <meta name="description" content="<?= $status->message ?>"/> - <meta property="og:title" content="<?= lang('Status.title', [ - 'actorDisplayName' => $status->actor->display_name, + <meta name="description" content="<?= $post->message ?>"/> + <meta property="og:title" content="<?= lang('Post.title', [ + 'actorDisplayName' => $post->actor->display_name, ]) ?>"/> <meta property="og:locale" content="<?= service( 'request', )->getLocale() ?>" /> - <meta property="og:site_name" content="<?= $status->actor->display_name ?>" /> + <meta property="og:site_name" content="<?= $post->actor->display_name ?>" /> <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $status->actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $status->message ?>" /> + <meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" /> + <meta property="og:description" content="<?= $post->message ?>" /> <?= $this->endSection() ?> <?= $this->section('content') ?> @@ -25,13 +25,13 @@ 'arrow-left', 'mr-2 text-lg', ) . - lang('Status.back_to_actor_statuses', [ - 'actor' => $status->actor->display_name, + lang('Post.back_to_actor_posts', [ + 'actor' => $post->actor->display_name, ]) ?></a> </nav> <div class="pb-12"> <?= $this->include( - 'podcast/_partials/status_with_replies_authenticated', + 'podcast/_partials/post_with_replies_authenticated', ) ?> </div> </div> diff --git a/app/Views/podcast/status_remote_action.php b/app/Views/podcast/post_remote_action.php similarity index 76% rename from app/Views/podcast/status_remote_action.php rename to app/Views/podcast/post_remote_action.php index 5b3e5cd848bface758f6edc44b51d96fadecbc28..04a99bcc8b6527fa47642c975a86e9d8d7549c09 100644 --- a/app/Views/podcast/status_remote_action.php +++ b/app/Views/podcast/post_remote_action.php @@ -7,22 +7,22 @@ <link rel="shortcut icon" type="image/png" href="/favicon.ico" /> <title><?= lang('ActivityPub.' . $action . '.title', [ - 'actorDisplayName' => $status->actor->display_name, + 'actorDisplayName' => $post->actor->display_name, ]) ?></title> - <meta name="description" content="<?= $status->message ?>"/> + <meta name="description" content="<?= $post->message ?>"/> <meta property="og:title" content="<?= lang( 'ActivityPub.' . $action . '.title', [ - 'actorDisplayName' => $status->actor->display_name, + 'actorDisplayName' => $post->actor->display_name, ], ) ?>"/> <meta property="og:locale" content="<?= service( 'request', )->getLocale() ?>" /> - <meta property="og:site_name" content="<?= $status->actor->display_name ?>" /> + <meta property="og:site_name" content="<?= $post->actor->display_name ?>" /> <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $status->actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $status->message ?>" /> + <meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" /> + <meta property="og:description" content="<?= $post->message ?>" /> <?= service('vite')->asset('styles/index.css', 'css') ?> <?= service('vite')->asset('js/podcast.ts', 'js') ?> @@ -35,10 +35,10 @@ ) ?></h1> </header> <main class="flex-1 max-w-xl px-4 pb-8 mx-auto -mt-24"> - <?= $this->include('podcast/_partials/status') ?> + <?= $this->include('podcast/_partials/post') ?> <?= form_open( - route_to('status-attempt-remote-action', $status->id, $action), + route_to('post-attempt-remote-action', $post->id, $action), ['method' => 'post', 'class' => 'flex flex-col mt-8'], ) ?> <?= csrf_field() ?> diff --git a/app/Views/podcast/status.php b/app/Views/podcast/status.php deleted file mode 100644 index 61c8352664a6875eb92d0681c508732af73b1ec8..0000000000000000000000000000000000000000 --- a/app/Views/podcast/status.php +++ /dev/null @@ -1,38 +0,0 @@ -<?= $this->extend('podcast/_layout') ?> - -<?= $this->section('meta-tags') ?> - <title><?= lang('Status.title', [ - 'actorDisplayName' => $status->actor->display_name, - ]) ?></title> - <meta name="description" content="<?= $status->message ?>"/> - <meta property="og:title" content="<?= lang('Status.title', [ - 'actorDisplayName' => $status->actor->display_name, - ]) ?>"/> - <meta property="og:locale" content="<?= service( - 'request', - )->getLocale() ?>" /> - <meta property="og:site_name" content="<?= $status->actor->display_name ?>" /> - <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $status->actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $status->message ?>" /> -<?= $this->endSection() ?> - -<?= $this->section('content') ?> -<div class="max-w-2xl px-6 mx-auto"> - <nav class="py-3"> - <a href="<?= route_to('podcast-activity', $podcast->handle) ?>" - class="inline-flex items-center px-4 py-2 text-sm"><?= icon( - 'arrow-left', - 'mr-2 text-lg', - ) . - lang('Status.back_to_actor_statuses', [ - 'actor' => $status->actor->display_name, - ]) ?></a> - </nav> - <div class="pb-12"> - <?= $this->include('podcast/_partials/status_with_replies') ?> - </div> -</div> - -<?= $this->endSection() -?>