Completed
Branch feature/currentUserRefactoring (c13c1d)
by Schlaefer
04:13
created

EntriesController::delete()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 10
nop 1
dl 0
loc 39
rs 8.3626
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Saito - The Threaded Web Forum
7
 *
8
 * @copyright Copyright (c) the Saito Project Developers
9
 * @link https://github.com/Schlaefer/Saito
10
 * @license http://opensource.org/licenses/MIT
11
 */
12
13
namespace App\Controller;
14
15
use App\Controller\Component\AutoReloadComponent;
16
use App\Controller\Component\MarkAsReadComponent;
17
use App\Controller\Component\RefererComponent;
18
use App\Controller\Component\ThreadsComponent;
19
use App\Model\Table\EntriesTable;
20
use Cake\Core\Configure;
21
use Cake\Datasource\Exception\RecordNotFoundException;
22
use Cake\Event\Event;
23
use Cake\Http\Exception\BadRequestException;
24
use Cake\Http\Exception\ForbiddenException;
25
use Cake\Http\Exception\MethodNotAllowedException;
26
use Cake\Http\Exception\NotFoundException;
27
use Cake\Http\Response;
28
use Cake\Routing\RequestActionTrait;
29
use Saito\Exception\SaitoForbiddenException;
30
use Saito\Posting\Basic\BasicPostingInterface;
31
use Saito\User\CurrentUser\CurrentUserInterface;
32
use Stopwatch\Lib\Stopwatch;
33
34
/**
35
 * Class EntriesController
36
 *
37
 * @property CurrentUserInterface $CurrentUser
38
 * @property EntriesTable $Entries
39
 * @property MarkAsReadComponent $MarkAsRead
40
 * @property RefererComponent $Referer
41
 * @property ThreadsComponent $Threads
42
 */
43
class EntriesController extends AppController
44
{
45
    use RequestActionTrait;
0 ignored issues
show
Deprecated Code introduced by
The trait Cake\Routing\RequestActionTrait has been deprecated with message: 3.3.0 Use view cells instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
46
47
    public $helpers = ['Posting', 'Text'];
48
49
    /**
50
     * {@inheritDoc}
51
     */
52
    public function initialize()
53
    {
54
        parent::initialize();
55
56
        $this->loadComponent('MarkAsRead');
57
        $this->loadComponent('Referer');
58
        $this->loadComponent('Threads', ['table' => $this->Entries]);
59
    }
60
61
    /**
62
     * posting index
63
     *
64
     * @return void|\Cake\Network\Response
65
     */
66
    public function index()
67
    {
68
        Stopwatch::start('Entries->index()');
69
70
        //= determine user sort order
71
        $sortKey = 'last_answer';
72
        if (!$this->CurrentUser->get('user_sort_last_answer')) {
73
            $sortKey = 'time';
74
        }
75
        $order = ['fixed' => 'DESC', $sortKey => 'DESC'];
76
77
        //= get threads
78
        $threads = $this->Threads->paginate($order, $this->CurrentUser);
79
        $this->set('entries', $threads);
80
81
        $currentPage = (int)$this->request->getQuery('page') ?: 1;
82
        if ($currentPage > 1) {
83
            $this->set('titleForLayout', __('page') . ' ' . $currentPage);
84
        }
85
        if ($currentPage === 1) {
86
            if ($this->MarkAsRead->refresh()) {
87
                return $this->redirect(['action' => 'index']);
88
            }
89
            $this->MarkAsRead->next();
90
        }
91
92
        // @bogus
93
        $this->request->getSession()->write('paginator.lastPage', $currentPage);
94
        $this->set('showDisclaimer', true);
95
        $this->set('showBottomNavigation', true);
96
        $this->set('allowThreadCollapse', true);
97
        $this->Slidetabs->show();
98
99
        $this->_setupCategoryChooser($this->CurrentUser);
100
101
        /** @var AutoReloadComponent */
102
        $autoReload = $this->loadComponent('AutoReload');
103
        $autoReload->after($this->CurrentUser);
104
105
        Stopwatch::stop('Entries->index()');
106
    }
107
108
    /**
109
     * Mix view
110
     *
111
     * @param string $tid thread-ID
112
     * @return void|Response
113
     * @throws NotFoundException
114
     */
115
    public function mix($tid)
116
    {
117
        $tid = (int)$tid;
118
        if ($tid <= 0) {
119
            throw new BadRequestException();
120
        }
121
122
        try {
123
            $postings = $this->Entries->postingsForThread($tid, true, $this->CurrentUser);
124
        } catch (RecordNotFoundException $e) {
125
            /// redirect sub-posting to mix view of thread
126
            $actualTid = $this->Entries->getThreadId($tid);
127
128
            return $this->redirect([$actualTid, '#' => $tid], 301);
129
        }
130
131
        // check if anonymous tries to access internal categories
132
        $root = $postings;
133
        if (!$this->CurrentUser->getCategories()->permission('read', $root->get('category'))) {
134
            return $this->_requireAuth();
135
        }
136
137
        $this->_setRootEntry($root);
138
        $this->Title->setFromPosting($root, __('view.type.mix'));
0 ignored issues
show
Compatibility introduced by
$root of type object<Saito\Posting\PostingInterface> is not a sub-type of object<Saito\Posting\Posting>. It seems like you assume a concrete implementation of the interface Saito\Posting\PostingInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
139
140
        $this->set('showBottomNavigation', true);
141
        $this->set('entries', $postings);
142
143
        $this->_showAnsweringPanel();
144
145
        $this->Threads->incrementViewsForThread($root, $this->CurrentUser);
146
        $this->MarkAsRead->thread($postings);
147
    }
148
149
    /**
150
     * load front page force all entries mark-as-read
151
     *
152
     * @return void
153
     */
154
    public function update()
155
    {
156
        $this->autoRender = false;
157
        $this->CurrentUser->getLastRefresh()->set();
158
        $this->redirect('/entries/index');
159
    }
160
161
    /**
162
     * Outputs raw markup of an posting $id
163
     *
164
     * @param string $id posting-ID
165
     * @return void
166
     */
167
    public function source($id = null)
168
    {
169
        $this->viewBuilder()->enableAutoLayout(false);
170
        $this->view($id);
171
    }
172
173
    /**
174
     * View posting.
175
     *
176
     * @param string $id posting-ID
177
     * @return \Cake\Network\Response|void
178
     */
179
    public function view($id = null)
180
    {
181
        Stopwatch::start('Entries->view()');
182
183
        // redirect if no id is given
184
        if (!$id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
185
            $this->Flash->set(__('Invalid post'), ['element' => 'error']);
186
187
            return $this->redirect(['action' => 'index']);
188
        }
189
190
        $entry = $this->Entries->get($id);
191
192
        // redirect if posting doesn't exists
193
        if ($entry == false) {
194
            $this->Flash->set(__('Invalid post'));
195
196
            return $this->redirect('/');
197
        }
198
199
        $posting = $entry->toPosting()->withCurrentUser($this->CurrentUser);
200
201
        if (!$this->CurrentUser->getCategories()->permission('read', $posting->get('category'))) {
202
            return $this->_requireAuth();
203
        }
204
205
        $this->set('entry', $posting);
206
        $this->Threads->incrementViewsForPosting($posting, $this->CurrentUser);
207
        $this->_setRootEntry($posting);
208
        $this->_showAnsweringPanel();
209
210
        $this->MarkAsRead->posting($posting);
211
212
        // inline open
213
        if ($this->request->is('ajax')) {
214
            return $this->render('/Element/posting/view_posting');
215
        }
216
217
        // full page request
218
        $this->set(
219
            'tree',
220
            $this->Entries->postingsForThread($posting->get('tid'), false, $this->CurrentUser)
221
        );
222
        $this->Title->setFromPosting($posting);
223
224
        Stopwatch::stop('Entries->view()');
225
    }
226
227
    /**
228
     * Add new posting.
229
     *
230
     * @return void|\Cake\Network\Response
231
     */
232
    public function add()
233
    {
234
        $titleForPage = __('Write a New Posting');
235
        $this->set(compact('titleForPage'));
236
    }
237
238
    /**
239
     * Edit posting
240
     *
241
     * @param string $id posting-ID
242
     * @return void|\Cake\Network\Response
243
     * @throws NotFoundException
244
     * @throws BadRequestException
245
     */
246
    public function edit($id = null)
247
    {
248
        if (empty($id)) {
249
            throw new BadRequestException;
250
        }
251
252
        $entry = $this->Entries->get($id);
253
        if (empty($entry)) {
254
            throw new NotFoundException;
255
        }
256
        $posting = $entry->toPosting()->withCurrentUser($this->CurrentUser);
257
258
        if (!$posting->isEditingAllowed()) {
259
            throw new SaitoForbiddenException(
260
                'Access to posting in EntriesController:edit() forbidden.',
261
                ['CurrentUser' => $this->CurrentUser]
262
            );
263
        }
264
265
        // show editing form
266
        if (!$posting->isEditingAsUserAllowed()) {
267
            $this->Flash->set(
268
                __('notice_you_are_editing_as_mod'),
269
                ['element' => 'warning']
270
            );
271
        }
272
273
        $this->set(compact('posting'));
274
275
        // set headers
276
        $this->set(
277
            'headerSubnavLeftTitle',
278
            __('back_to_posting_from_linkname', $posting->get('name'))
279
        );
280
        $this->set('headerSubnavLeftUrl', ['action' => 'view', $id]);
281
        $this->set('form_title', __('edit_linkname'));
282
        $this->render('/Entries/add');
283
    }
284
285
    /**
286
     * Get thread-line to insert after an inline-answer
287
     *
288
     * @param string $id posting-ID
289
     * @return void|\Cake\Network\Response
290
     */
291
    public function threadLine($id = null)
292
    {
293
        $posting = $this->Entries->get($id)->toPosting()->withCurrentUser($this->CurrentUser);
294
        if (!$this->CurrentUser->getCategories()->permission('read', $posting->get('category'))) {
295
            return $this->_requireAuth();
296
        }
297
298
        $this->set('entrySub', $posting);
299
        // ajax requests so far are always answers
300
        $this->response = $this->response->withType('json');
301
        $this->set('level', '1');
302
    }
303
304
    /**
305
     * Delete posting
306
     *
307
     * @param string $id posting-ID
308
     * @return void
309
     * @throws NotFoundException
310
     * @throws MethodNotAllowedException
311
     */
312
    public function delete(string $id)
313
    {
314
        //$this->request->allowMethod(['post', 'delete']);
315
        $id = (int)$id;
316
        if (!$id) {
317
            throw new NotFoundException;
318
        }
319
        /* @var Entry $posting */
320
        $posting = $this->Entries->get($id);
321
        if (!$posting) {
322
            throw new NotFoundException;
323
        }
324
325
        $action = $posting->isRoot() ? 'thread' : 'answer';
326
        $allowed = $this->CurrentUser->getCategories()
327
            ->permission($action, $posting->get('category_id'));
328
        if (!$allowed) {
329
            throw new ForbiddenException(null, 1571309481);
330
        }
331
332
        $success = $this->Entries->deletePosting($id);
333
334
        if ($success) {
335
            $flashType = 'success';
336
            if ($posting->isRoot()) {
337
                $message = __('delete_tree_success');
338
                $redirect = '/';
339
            } else {
340
                $message = __('delete_subtree_success');
341
                $redirect = '/entries/view/' . $posting->get('pid');
342
            }
343
        } else {
344
            $flashType = 'error';
345
            $message = __('delete_tree_error');
346
            $redirect = $this->referer();
347
        }
348
        $this->Flash->set($message, ['element' => $flashType]);
349
        $this->redirect($redirect);
350
    }
351
352
    /**
353
     * Empty function for benchmarking
354
     *
355
     * @return void
356
     */
357
    public function e()
358
    {
359
        Stopwatch::start('Entries->e()');
360
        Stopwatch::stop('Entries->e()');
361
    }
362
363
    /**
364
     * Marks sub-entry $id as solution to its current root-entry
365
     *
366
     * @param string $id posting-ID
367
     * @return void
368
     * @throws BadRequestException
369
     */
370
    public function solve($id)
371
    {
372
        $this->autoRender = false;
373
        try {
374
            $posting = $this->Entries->get($id);
375
376
            if (empty($posting)) {
377
                throw new \InvalidArgumentException('Posting to mark solved not found.');
378
            }
379
380
            if ($posting->isRoot()) {
381
                throw new \InvalidArgumentException('Root postings cannot mark themself solved.');
382
            }
383
384
            $rootId = $posting->get('tid');
385
            $rootPosting = $this->Entries->get($rootId);
386
            if ($rootPosting->get('user_id') !== $this->CurrentUser->getId()) {
387
                throw new SaitoForbiddenException(
388
                    sprintf('Attempt to mark posting %s as solution.', $posting->get('id')),
389
                    ['CurrentUser' => $this->CurrentUser]
390
                );
391
            }
392
393
            $success = $this->Entries->toggleSolve($posting);
0 ignored issues
show
Bug introduced by
It seems like $posting defined by $this->Entries->get($id) on line 374 can also be of type array; however, App\Model\Table\EntriesTable::toggleSolve() does only seem to accept object<App\Model\Entity\Entry>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
394
395
            if (!$success) {
396
                throw new BadRequestException;
397
            }
398
        } catch (\Exception $e) {
399
            throw new BadRequestException();
400
        }
401
    }
402
403
    /**
404
     * Merge threads.
405
     *
406
     * @param string $sourceId posting-ID of thread to be merged
407
     * @return void
408
     * @throws NotFoundException
409
     * @td put into admin entries controller
410
     */
411
    public function merge($sourceId = null)
412
    {
413
        if (!$sourceId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceId of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
414
            throw new NotFoundException();
415
        }
416
417
        /* @var Entry */
418
        $entry = $this->Entries->findById($sourceId)->first();
0 ignored issues
show
Documentation Bug introduced by
The method findById does not exist on object<App\Model\Table\EntriesTable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
419
420
        if (!$entry || !$entry->isRoot()) {
421
            throw new NotFoundException();
422
        }
423
424
        // perform move operation
425
        $targetId = $this->request->getData('targetId');
426 View Code Duplication
        if (!empty($targetId)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
            if ($this->Entries->threadMerge($sourceId, $targetId)) {
0 ignored issues
show
Documentation introduced by
$targetId is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
428
                $this->redirect('/entries/view/' . $sourceId);
429
430
                return;
431
            } else {
432
                $this->Flash->set(__('Error'), ['element' => 'error']);
433
            }
434
        }
435
436
        $this->viewBuilder()->setLayout('Admin.admin');
437
        $this->set('posting', $entry);
438
    }
439
440
    /**
441
     * Toggle posting property via ajax request.
442
     *
443
     * @param string $id posting-ID
444
     * @param string $toggle property
445
     *
446
     * @return \Cake\Network\Response
447
     */
448
    public function ajaxToggle($id = null, $toggle = null)
449
    {
450
        $allowed = ['fixed', 'locked'];
451
        if (!$id
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
452
            || !$toggle
0 ignored issues
show
Bug Best Practice introduced by
The expression $toggle of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
453
            || !$this->request->is('ajax')
454
            || !in_array($toggle, $allowed)
455
        ) {
456
            throw new BadRequestException;
457
        }
458
459
        $current = $this->Entries->toggle((int)$id, $toggle);
460
        if ($current) {
461
            $out['html'] = __d('nondynamic', $toggle . '_unset_entry_link');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$out was never initialized. Although not strictly required by PHP, it is generally a good practice to add $out = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
462
        } else {
463
            $out['html'] = __d('nondynamic', $toggle . '_set_entry_link');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$out was never initialized. Although not strictly required by PHP, it is generally a good practice to add $out = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
464
        }
465
466
        $this->response = $this->response->withType('json');
467
        $this->response = $this->response->withStringBody(json_encode($out));
468
469
        return $this->response;
470
    }
471
472
    /**
473
     * {@inheritDoc}
474
     */
475
    public function beforeFilter(Event $event)
476
    {
477
        parent::beforeFilter($event);
478
        Stopwatch::start('Entries->beforeFilter()');
479
480
        $this->Security->setConfig(
481
            'unlockedActions',
482
            ['solve', 'view']
483
        );
484
        $this->Authentication->allowUnauthenticated(['index', 'view', 'mix', 'update']);
485
486
        $this->AuthUser->authorizeAction('ajaxToggle', 'saito.core.posting.pinAndLock');
487
        $this->AuthUser->authorizeAction('merge', 'saito.core.posting.merge');
488
        $this->AuthUser->authorizeAction('delete', 'saito.core.posting.delete');
489
490
        Stopwatch::stop('Entries->beforeFilter()');
491
    }
492
493
    /**
494
     * set view vars for category chooser
495
     *
496
     * @param CurrentUserInterface $User CurrentUser
497
     * @return void
498
     */
499
    protected function _setupCategoryChooser(CurrentUserInterface $User)
500
    {
501
        if (!$User->isLoggedIn()) {
502
            return;
503
        }
504
        $globalActivation = Configure::read(
505
            'Saito.Settings.category_chooser_global'
506
        );
507
        if (!$globalActivation) {
508
            if (!Configure::read(
509
                'Saito.Settings.category_chooser_user_override'
510
            )
511
            ) {
512
                return;
513
            }
514
            if (!$User->get('user_category_override')) {
515
                return;
516
            }
517
        }
518
519
        $this->set(
520
            'categoryChooserChecked',
521
            $User->getCategories()->getCustom('read')
522
        );
523
        switch ($User->getCategories()->getType()) {
524
            case 'single':
525
                $title = $User->get('user_category_active');
526
                break;
527
            case 'custom':
528
                $title = __('Custom');
529
                break;
530
            default:
531
                $title = __('All Categories');
532
        }
533
        $this->set('categoryChooserTitleId', $title);
534
        $this->set(
535
            'categoryChooser',
536
            $User->getCategories()->getAll('read', 'select')
537
        );
538
    }
539
540
    /**
541
     * Decide if an answering panel is show when rendering a posting
542
     *
543
     * @return void
544
     */
545
    protected function _showAnsweringPanel()
546
    {
547
        $showAnsweringPanel = false;
548
549
        if ($this->CurrentUser->isLoggedIn()) {
550
            // Only logged in users see the answering buttons if they …
551
            if (// … directly on entries/view but not inline
552
                ($this->request->getParam('action') === 'view' && !$this->request->is('ajax'))
553
                // … directly in entries/mix
554
                || $this->request->getParam('action') === 'mix'
555
                // … inline viewing … on entries/index.
556
                || ($this->Referer->wasController('entries')
557
                    && $this->Referer->wasAction('index'))
558
            ) {
559
                $showAnsweringPanel = true;
560
            }
561
        }
562
        $this->set('showAnsweringPanel', $showAnsweringPanel);
563
    }
564
565
    /**
566
     * makes root posting of $posting avaiable in view
567
     *
568
     * @param BasicPostingInterface $posting posting for root entry
569
     * @return void
570
     */
571
    protected function _setRootEntry(BasicPostingInterface $posting)
572
    {
573
        if (!$posting->isRoot()) {
574
            $root = $this->Entries->find()
575
                ->select(['user_id'])
576
                ->where(['id' => $posting->get('tid')])
577
                ->first();
578
        } else {
579
            $root = $posting;
580
        }
581
        $this->set('rootEntry', $root);
582
    }
583
}
584