Passed
Push — master ( 3b518a...cd2a02 )
by Adam
11:23
created

TopicController::subscribe()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
nc 2
nop 1
dl 0
loc 17
rs 9.9666
c 1
b 0
f 0
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);
0 ignored issues
show
Bug introduced by
The method search() does not exist on Coyote\Repositories\Cont...opicRepositoryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Coyote\Repositories\Cont...opicRepositoryInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

157
            /** @scrutinizer ignore-call */ 
158
            $mlt = $this->topic->search($builder);
Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Coyote\Http\Resou...toArray($this->request) could return the type Illuminate\Contracts\Sup...ayable|JsonSerializable which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
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