1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6declare(strict_types=1);
7
8namespace App\Libraries\BeatmapsetDiscussion;
9
10use App\Exceptions\InvariantException;
11use App\Jobs\Notifications\BeatmapsetDiscussionPostNew;
12use App\Libraries\BeatmapsetDiscussion\Traits\HandlesProblem;
13use App\Models\BeatmapDiscussion;
14use App\Models\BeatmapDiscussionPost;
15use App\Models\BeatmapsetEvent;
16use App\Models\User;
17
18class Reply
19{
20 use HandlesProblem;
21
22 private array $posts = [];
23
24 public function __construct(private User $user, private BeatmapDiscussion $discussion, private ?string $message, private ?bool $resolve = null)
25 {
26 priv_check_user($this->user, 'BeatmapsetDiscussionReply', $discussion->beatmapset)->ensureCan();
27
28 if (!$discussion->exists) {
29 throw new InvariantException('Cannot reply to a new discussion.');
30 }
31
32 // Treat resolving to the same state as not changing.
33 // Maybe throw instead?
34 if ($resolve === $discussion->resolved) {
35 $this->resolve = null;
36 }
37
38 if ($this->resolve !== null) {
39 if (!$discussion->canBeResolved()) {
40 throw new InvariantException("{$discussion->message_type} does not support resolving.");
41 }
42
43 if ($discussion->resolved !== $resolve) {
44 $priv = $resolve ? 'BeatmapDiscussionResolve' : 'BeatmapDiscussionReopen';
45 priv_check_user($user, $priv, $discussion)->ensureCan();
46 }
47 }
48
49 $this->maybeSetProblemDiscussion($discussion, false);
50 }
51
52 /**
53 * @return BeatmapDiscussionPost[]
54 */
55 public function handle(): array
56 {
57 $newPost = $this->discussion->getConnection()->transaction(function () {
58 $post = $this->discussion->beatmapDiscussionPosts()->make(['message' => $this->message]);
59 $post->user()->associate($this->user);
60
61 $post->saveOrExplode();
62 $this->posts[] = $post;
63
64 $this->handleResolvedChange($post);
65 $this->handleProblemDiscussion();
66
67 return $post;
68 });
69
70 // TODO: make transactional
71
72 (new BeatmapsetDiscussionPostNew($newPost, $this->user))->dispatch();
73
74 return $this->posts;
75 }
76
77 private function handleResolvedChange(BeatmapDiscussionPost $post)
78 {
79 if ($this->resolve === null) {
80 return;
81 }
82
83 // if a resolved state change was requested, check if someone else got to it first.
84 $discussion = $this->discussion->lockSelf();
85 if ($discussion->resolved !== $this->discussion->resolved) {
86 throw new InvariantException('resolved state of the discussion has changed');
87 }
88
89 $this->discussion->resolved = $this->resolve;
90 $event = $this->discussion->resolved ? BeatmapsetEvent::ISSUE_RESOLVE : BeatmapsetEvent::ISSUE_REOPEN;
91
92 $systemPost = BeatmapDiscussionPost::generateLogResolveChange($this->user, $this->discussion->resolved);
93 $systemPost->beatmap_discussion_id = $this->discussion->getKey();
94 $systemPost->saveOrExplode();
95 BeatmapsetEvent::log($event, $this->user, $post)->saveOrExplode();
96
97 $this->posts[] = $systemPost;
98
99 $this->discussion->saveOrExplode();
100 }
101}