3 namespace BookStack\Activity;
5 use BookStack\Activity\Models\Comment;
6 use BookStack\Entities\Models\Entity;
7 use BookStack\Entities\Models\Page;
8 use BookStack\Exceptions\NotifyException;
9 use BookStack\Facades\Activity as ActivityService;
10 use BookStack\Util\HtmlDescriptionFilter;
11 use Illuminate\Database\Eloquent\Builder;
16 * Get a comment by ID.
18 public function getById(int $id): Comment
20 return Comment::query()->findOrFail($id);
24 * Get a comment by ID, ensuring it is visible to the user based upon access to the page
25 * which the comment is attached to.
27 public function getVisibleById(int $id): Comment
29 return $this->getQueryForVisible()->findOrFail($id);
33 * Start a query for comments visible to the user.
34 * @return Builder<Comment>
36 public function getQueryForVisible(): Builder
38 return Comment::query()->scopes('visible');
42 * Create a new comment on an entity.
44 public function create(Entity $entity, string $html, ?int $parentId, string $contentRef): Comment
46 // Prevent comments being added to draft pages
47 if ($entity instanceof Page && $entity->draft) {
48 throw new \Exception(trans('errors.cannot_add_comment_to_draft'));
52 if ($parentId !== null) {
53 $parentCommentExists = Comment::query()
54 ->where('commentable_id', '=', $entity->id)
55 ->where('commentable_type', '=', $entity->getMorphClass())
56 ->where('local_id', '=', $parentId)
58 if (!$parentCommentExists) {
64 $comment = new Comment();
66 $comment->html = HtmlDescriptionFilter::filterFromString($html);
67 $comment->created_by = $userId;
68 $comment->updated_by = $userId;
69 $comment->local_id = $this->getNextLocalId($entity);
70 $comment->parent_id = $parentId;
71 $comment->content_ref = preg_match('/^bkmrk-(.*?):\d+:(\d*-\d*)?$/', $contentRef) === 1 ? $contentRef : '';
73 $entity->comments()->save($comment);
74 ActivityService::add(ActivityType::COMMENT_CREATE, $comment);
75 ActivityService::add(ActivityType::COMMENTED_ON, $entity);
77 $comment->refresh()->unsetRelations();
82 * Update an existing comment.
84 public function update(Comment $comment, string $html): Comment
86 $comment->updated_by = user()->id;
87 $comment->html = HtmlDescriptionFilter::filterFromString($html);
90 ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
97 * Archive an existing comment.
99 public function archive(Comment $comment, bool $log = true): Comment
101 if ($comment->parent_id) {
102 throw new NotifyException('Only top-level comments can be archived.', '/', 400);
105 $comment->archived = true;
109 ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
116 * Un-archive an existing comment.
118 public function unarchive(Comment $comment, bool $log = true): Comment
120 if ($comment->parent_id) {
121 throw new NotifyException('Only top-level comments can be un-archived.', '/', 400);
124 $comment->archived = false;
128 ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
135 * Delete a comment from the system.
137 public function delete(Comment $comment): void
141 ActivityService::add(ActivityType::COMMENT_DELETE, $comment);
145 * Get the next local ID relative to the linked entity.
147 protected function getNextLocalId(Entity $entity): int
149 $currentMaxId = $entity->comments()->max('local_id');
151 return $currentMaxId + 1;