1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Coyote\Http\Controllers\Forum; |
4
|
|
|
|
5
|
|
|
use Coyote\Events\TopicSaved; |
6
|
|
|
use Coyote\Forum; |
7
|
|
|
use Coyote\Forum\Reason; |
8
|
|
|
use Coyote\Http\Factories\CacheFactory; |
9
|
|
|
use Coyote\Http\Resources\FlagResource; |
10
|
|
|
use Coyote\Http\Resources\PollResource; |
11
|
|
|
use Coyote\Http\Resources\PostCollection; |
12
|
|
|
use Coyote\Http\Resources\TopicResource; |
13
|
|
|
use Coyote\Repositories\Criteria\Forum\OnlyThoseWithAccess; |
14
|
|
|
use Coyote\Repositories\Criteria\Post\WithSubscribers; |
15
|
|
|
use Coyote\Repositories\Criteria\WithTrashed; |
16
|
|
|
use Coyote\Repositories\Criteria\Post\WithTrashedInfo; |
17
|
|
|
use Coyote\Services\Elasticsearch\Builders\Forum\MoreLikeThisBuilder; |
18
|
|
|
use Coyote\Services\Flags; |
19
|
|
|
use Coyote\Services\Forum\Tracker; |
20
|
|
|
use Coyote\Services\Forum\TreeBuilder\Builder; |
21
|
|
|
use Coyote\Services\Forum\TreeBuilder\JsonDecorator; |
22
|
|
|
use Coyote\Services\Forum\TreeBuilder\ListDecorator; |
23
|
|
|
use Coyote\Topic; |
24
|
|
|
use Illuminate\Http\Request; |
25
|
|
|
|
26
|
|
|
class TopicController extends BaseController |
27
|
|
|
{ |
28
|
|
|
use CacheFactory; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var \Illuminate\Contracts\Auth\Access\Gate |
32
|
|
|
*/ |
33
|
|
|
private $gate; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @param Request $request |
37
|
|
|
* @param $forum |
38
|
|
|
* @param $topic |
39
|
|
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Support\Collection|\Illuminate\View\View |
40
|
|
|
*/ |
41
|
|
|
public function index(Request $request, $forum, $topic) |
42
|
|
|
{ |
43
|
|
|
$this->breadcrumb->push($topic->title, route('forum.topic', [$forum->slug, $topic->id, $topic->slug])); |
44
|
|
|
|
45
|
|
|
// get the topic (and forum) mark time value from middleware |
46
|
|
|
// @see \Coyote\Http\Middleware\ScrollToPost |
47
|
|
|
$markTime = $request->attributes->get('mark_time'); |
48
|
|
|
|
49
|
|
|
$this->gate = $this->getGateFactory(); |
50
|
|
|
|
51
|
|
|
// current page... |
52
|
|
|
$page = (int) $request->get('page'); |
53
|
|
|
// number of posts per one page |
54
|
|
|
$perPage = $this->postsPerPage($request); |
55
|
|
|
|
56
|
|
|
// user with forum-update ability WILL see every post |
57
|
|
|
// NOTE: criteria MUST BE pushed before calling getPage() method! |
58
|
|
|
if ($this->gate->allows('delete', $forum)) { |
59
|
|
|
$this->post->pushCriteria(new WithTrashed()); |
60
|
|
|
$this->post->pushCriteria(new WithTrashedInfo()); |
61
|
|
|
|
62
|
|
|
// user is able to see real number of posts in this topic |
63
|
|
|
$topic->replies = $topic->replies_real; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
// user wants to show certain post. we need to calculate page number based on post id. |
67
|
|
|
if ($request->filled('p')) { |
68
|
|
|
$page = $this->post->getPage(min(2147483647, (int) $request->get('p')), $topic->id, $perPage); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
// show posts of last page if page parameter is higher than pages count |
72
|
|
|
$lastPage = max((int) ceil(($topic->replies + 1) / $perPage), 1); |
73
|
|
|
if ($page > $lastPage) { |
74
|
|
|
$page = $lastPage; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
$this->post->pushCriteria(new WithSubscribers($this->userId)); |
78
|
|
|
|
79
|
|
|
// magic happens here. get posts for given topic |
80
|
|
|
/* @var \Illuminate\Support\Collection $posts */ |
81
|
|
|
$paginate = $this->post->lengthAwarePagination($topic, $page, $perPage); |
82
|
|
|
|
83
|
|
|
$this->pushForumCriteria(true); |
84
|
|
|
|
85
|
|
|
// create forum list for current user (according to user's privileges) |
86
|
|
|
$treeBuilder = new Builder($this->forum->list()); |
87
|
|
|
$treeDecorator = new ListDecorator($treeBuilder); |
88
|
|
|
|
89
|
|
|
$userForums = $treeDecorator->build(); |
90
|
|
|
|
91
|
|
|
// important: load topic owner so we can highlight user's login |
92
|
|
|
$page === 1 ? $topic->setRelation('firstPost', $paginate->first()) : $topic->load('firstPost'); |
93
|
|
|
|
94
|
|
|
$tracker = Tracker::make($topic); |
95
|
|
|
|
96
|
|
|
$allForums = []; |
97
|
|
|
$reasons = null; |
98
|
|
|
|
99
|
|
|
if ($this->gate->allows('delete', $forum) || $this->gate->allows('move', $forum)) { |
100
|
|
|
$reasons = Reason::pluck('name', 'id')->toArray(); |
101
|
|
|
|
102
|
|
|
$this->forum->resetCriteria(); |
103
|
|
|
$this->pushForumCriteria(false); |
104
|
|
|
|
105
|
|
|
// forum list only for moderators |
106
|
|
|
$treeBuilder->setForums($this->forum->list()); |
107
|
|
|
|
108
|
|
|
$allForums = (new JsonDecorator($treeBuilder))->build(); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$resource = (new PostCollection($paginate)) |
112
|
|
|
->setRelations($topic, $forum) |
113
|
|
|
->setTracker($tracker); |
114
|
|
|
|
115
|
|
|
$dateTime = $paginate->last()->created_at; |
116
|
|
|
// first, build array of posts with info which posts have been read |
117
|
|
|
// assign array ot posts variable. this is our skeleton! do not remove |
118
|
|
|
$posts = $resource->toResponse($this->request)->getData(true); |
119
|
|
|
|
120
|
|
|
// ..then, mark topic as read |
121
|
|
|
if ($markTime < $dateTime) { |
122
|
|
|
$tracker->asRead($dateTime); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
if ($request->wantsJson()) { |
126
|
|
|
return $posts; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
$topic->load('tags'); |
130
|
|
|
|
131
|
|
|
return $this->view('forum.topic', compact('posts', 'forum', 'paginate', 'reasons'))->with([ |
132
|
|
|
'mlt' => $this->moreLikeThis($topic), |
133
|
|
|
'model' => $topic, // we need eloquent model in twig to show information about locked/moved topic |
134
|
|
|
'topic' => (new TopicResource($tracker))->resolve($request), |
135
|
|
|
'poll' => $topic->poll ? (new PollResource($topic->poll))->resolve($request) : null, |
136
|
|
|
'is_writeable' => $this->gate->allows('write', $forum) && $this->gate->allows('write', $topic), |
137
|
|
|
'all_forums' => $allForums, |
138
|
|
|
'user_forums' => $userForums, |
139
|
|
|
'description' => excerpt(array_first($posts['data'])['text'], 100), |
140
|
|
|
'flags' => $this->flags($forum) |
141
|
|
|
]); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
private function moreLikeThis(Topic $topic) |
145
|
|
|
{ |
146
|
|
|
// build "more like this" block. it's important to send elasticsearch query before |
147
|
|
|
// send SQL query to database because search() method exists only in Model and not Builder class. |
148
|
|
|
return $this->getCacheFactory()->remember('mlt-post:' . $topic->id, now()->addDay(), function () use ($topic) { |
149
|
|
|
// it's important to reset criteria for the further queries |
150
|
|
|
$this->forum->resetCriteria(); |
151
|
|
|
|
152
|
|
|
$this->forum->pushCriteria(new OnlyThoseWithAccess()); |
153
|
|
|
|
154
|
|
|
$builder = new MoreLikeThisBuilder($topic, $this->forum->pluck('id')); |
155
|
|
|
|
156
|
|
|
// search related topics |
157
|
|
|
$mlt = $this->topic->search($builder); |
|
|
|
|
158
|
|
|
|
159
|
|
|
return $mlt; |
160
|
|
|
}); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
private function flags(Forum $forum): array |
164
|
|
|
{ |
165
|
|
|
$flags = resolve(Flags::class)->fromModels([Topic::class])->permission('delete', [$forum])->get(); |
166
|
|
|
|
167
|
|
|
return FlagResource::collection($flags)->toArray($this->request); |
|
|
|
|
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @param \Coyote\Topic $topic |
172
|
|
|
*/ |
173
|
|
|
public function mark($topic) |
174
|
|
|
{ |
175
|
|
|
$tracker = Tracker::make($topic); |
176
|
|
|
|
177
|
|
|
$tracker->asRead($topic->last_post_created_at); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|