Newer
Older
<?php
declare(strict_types=1);
/**
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Models;
use App\Entities\EpisodeComment;
use App\Libraries\CommentObject;
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\I18n\Time;
use Michalsn\Uuid\UuidModel;

Yassine Doghri
committed
use Modules\Fediverse\Activities\CreateActivity;
use Modules\Fediverse\Activities\DeleteActivity;
use Modules\Fediverse\Models\ActivityModel;
use Modules\Fediverse\Objects\TombstoneObject;
class EpisodeCommentModel extends UuidModel
{
/**
* @var string
*/
protected $returnType = EpisodeComment::class;
/**
* @var string
*/
protected $table = 'episode_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',
'replies_count',
'created_at',
'created_by',
];
/**
* @var string[]
*/
protected $beforeInsert = ['setCommentId'];
public function getCommentById(string $commentId): ?EpisodeComment
{
$cacheName = "comment#{$commentId}";
if (! ($found = cache($cacheName))) {
$found = $this->find($commentId);
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
public function addComment(EpisodeComment $comment, bool $registerActivity = true): bool|int|object|string
{
$this->db->transStart();
if (! ($newCommentId = $this->insert($comment, true))) {
$this->db->transRollback();
// Couldn't insert comment
return false;
}
if ($comment->in_reply_to_id === null) {
(new EpisodeModel())->builder()
->where('id', $comment->episode_id)
->increment('comments_count');
} else {
(new self())->builder()
->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes())
->increment('replies_count');
}
if ($registerActivity) {
// set post id and uri to construct NoteObject
$comment->id = $newCommentId;

Yassine Doghri
committed
$comment->uri = url_to(
'episode-comment',
esc($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::class, false)
->newActivity(
'Create',
$comment->actor_id,
null,
null,
$createActivity->toJSON(),
$comment->created_at,
'queued',
);

Yassine Doghri
committed
$createActivity->set('id', url_to('activity', esc($comment->actor->username), $activityId));
model(ActivityModel::class, false)
->update($activityId, [
'payload' => $createActivity->toJSON(),
]);
}
$this->db->transComplete();
$this->clearCache($comment);

Yassine Doghri
committed
return $newCommentId;
}
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
public function removeComment(EpisodeComment $comment, bool $registerActivity = true): BaseResult | bool
{
$this->db->transStart();
// remove all replies
foreach ($comment->replies as $reply) {
$this->removeComment($reply);
}
if ($registerActivity) {
$deleteActivity = new DeleteActivity();
$tombstoneObject = new TombstoneObject();
$tombstoneObject->set('id', $comment->uri);
$deleteActivity
->set('actor', $comment->actor->uri)
->set('object', $tombstoneObject);
$activityId = model(ActivityModel::class, false)
->newActivity(
'Delete',
$comment->actor_id,
null,
null,
$deleteActivity->toJSON(),
Time::now(),
'queued',
);
$deleteActivity->set('id', url_to('activity', esc($comment->actor->username), $activityId));
model(ActivityModel::class, false)
->update($activityId, [
'payload' => $deleteActivity->toJSON(),
]);
}
$result = model(self::class, false)
->delete($comment->id);
if ($comment->in_reply_to_id === null) {
model(EpisodeModel::class, false)->builder()
->where('id', $comment->episode_id)
->decrement('comments_count');
} else {
(new self())->builder()
->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes())
->decrement('replies_count');
}
$this->clearCache($comment);
$this->db->transComplete();
return $result;
}
/**
* Retrieves all published posts for a given episode ordered by publication date
*
* @return EpisodeComment[]
*
* @noRector ReturnTypeDeclarationRector
*/
public function getEpisodeComments(int $episodeId): array
{
// TODO: merge with replies from posts linked to episode linked
$episodeCommentsBuilder = $this->builder();
$episodeComments = $episodeCommentsBuilder->select('*, 0 as is_from_post')
->where([
'episode_id' => $episodeId,
'in_reply_to_id' => null,
])
->getCompiledSelect();
$postModel = new PostModel();
$episodePostsRepliesBuilder = $postModel->builder();
$episodePostsReplies = $episodePostsRepliesBuilder->select(
'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, replies_count, published_at as created_at, created_by, 1 as is_from_post'
)
->whereIn('in_reply_to_id', static function (BaseBuilder $builder) use (&$episodeId): BaseBuilder {
return $builder->select('id')

Yassine Doghri
committed
->from(config('Fediverse')->tablesPrefix . 'posts')
->where([
'episode_id' => $episodeId,
'in_reply_to_id' => null,
]);
})

Yassine Doghri
committed
->where('`created_at` <= UTC_TIMESTAMP()', null, false)
->getCompiledSelect();
/** @var BaseResult $allEpisodeComments */
$allEpisodeComments = $this->db->query(
$episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC'
);
// FIXME:?
// @phpstan-ignore-next-line
return $this->convertUuidFieldsToStrings(
$allEpisodeComments->getCustomResultObject($this->tempReturnType),
$this->tempReturnType
);
}
/**
* Retrieves all replies for a given comment
*
* @return EpisodeComment[]
*/
public function getCommentReplies(string $commentId): array
{
// TODO: get all replies for a given comment
return $this->where('in_reply_to_id', $this->uuid->fromString($commentId)->getBytes())
->orderBy('created_at', 'ASC')
->findAll();
}
public function resetLikesCount(): int | false
{
$commentsLikesCount = $this->db->table('likes')
->select('comment_id as id, COUNT(*) as `likes_count`')
->groupBy('id')
->get()
->getResultArray();
if ($commentsLikesCount !== []) {
$this->uuidUseBytes = false;
return $this->updateBatch($commentsLikesCount, 'id');
}

Yassine Doghri
committed
return 0;
}
public function resetRepliesCount(): int | false
{
$commentsRepliesCount = $this->builder()
->select('episode_comments.id, COUNT(*) as `replies_count`')
->join('episode_comments as c2', 'episode_comments.id = c2.in_reply_to_id')
->groupBy('episode_comments.id')
->get()
->getResultArray();
if ($commentsRepliesCount !== []) {
$this->uuidUseBytes = false;
return $this->updateBatch($commentsRepliesCount, 'id');
}
return 0;
}
/**
* @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::class, false)
->getActorById((int) $data['data']['actor_id']);
$episode = model(EpisodeModel::class, false)
->find((int) $data['data']['episode_id']);

Yassine Doghri
committed
$data['data']['uri'] = url_to('episode-comment', esc($actor->username), $episode->slug, $uuid4->toString());
}
return $data;
}
protected function clearCache(EpisodeComment $comment): void
{
cache()
->deleteMatching("comment#{$comment->id}*");
// delete podcast and episode pages cache
cache()
->deleteMatching("podcast-{$comment->episode->podcast->handle}*");
cache()
->deleteMatching('page_podcast#' . $comment->episode->podcast_id . '*');
cache()
->deleteMatching('page_episode#' . $comment->episode_id . '*');
}