Completed
Branch #338-Save_posting_before_movin... (2366dc)
by Schlaefer
02:58
created

EntriesController::solve()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 11
nop 1
dl 0
loc 32
rs 8.7857
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\Entity\Entry;
20
use App\Model\Table\EntriesTable;
21
use Cake\Core\Configure;
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\Posting;
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
    public $actionAuthConfig = [
50
        'ajaxToggle' => 'mod',
51
        'merge' => 'mod',
52
        'delete' => 'mod'
53
    ];
54
55
    /**
56
     * {@inheritDoc}
57
     */
58
    public function initialize()
59
    {
60
        parent::initialize();
61
62
        $this->loadComponent('MarkAsRead');
63
        $this->loadComponent('Referer');
64
        $this->loadComponent('Threads');
65
    }
66
67
    /**
68
     * posting index
69
     *
70
     * @return void|\Cake\Network\Response
71
     */
72
    public function index()
73
    {
74
        Stopwatch::start('Entries->index()');
75
76
        //= determine user sort order
77
        $sortKey = 'last_answer';
78
        if (!$this->CurrentUser->get('user_sort_last_answer')) {
79
            $sortKey = 'time';
80
        }
81
        $order = ['fixed' => 'DESC', $sortKey => 'DESC'];
82
83
        //= get threads
84
        $threads = $this->Threads->paginate($order);
85
        $this->set('entries', $threads);
86
87
        $currentPage = (int)$this->request->getQuery('page') ?: 1;
88
        if ($currentPage > 1) {
89
            $this->set('titleForLayout', __('page') . ' ' . $currentPage);
90
        }
91
        if ($currentPage === 1) {
92
            if ($this->MarkAsRead->refresh()) {
93
                return $this->redirect(['action' => 'index']);
94
            }
95
            $this->MarkAsRead->next();
96
        }
97
98
        // @bogus
99
        $this->request->getSession()->write('paginator.lastPage', $currentPage);
100
        $this->set('showDisclaimer', true);
101
        $this->set('showBottomNavigation', true);
102
        $this->set('allowThreadCollapse', true);
103
        $this->Slidetabs->show();
104
105
        $this->_setupCategoryChooser($this->CurrentUser);
106
107
        /** @var AutoReloadComponent */
108
        $autoReload = $this->loadComponent('AutoReload');
109
        $autoReload->after($this->CurrentUser);
110
111
        Stopwatch::stop('Entries->index()');
112
    }
113
114
    /**
115
     * Mix view
116
     *
117
     * @param string $tid thread-ID
118
     * @return void|Response
119
     * @throws NotFoundException
120
     */
121
    public function mix($tid)
122
    {
123
        $tid = (int)$tid;
124
        if ($tid <= 0) {
125
            throw new BadRequestException();
126
        }
127
128
        $postings = $this->Entries->treeForNode(
129
            $tid,
130
            ['root' => true, 'complete' => true]
131
        );
132
133
        /// redirect sub-posting to mix view of thread
134
        if (!$postings) {
135
            $post = $this->Entries->find()
136
                ->select(['tid'])
137
                ->where(['id' => $tid])
138
                ->first();
139
            if (!empty($post)) {
140
                return $this->redirect([$post->get('tid'), '#' => $tid], 301);
141
            }
142
            throw new NotFoundException;
143
        }
144
145
        // check if anonymous tries to access internal categories
146
        $root = $postings;
147
        if (!$this->CurrentUser->getCategories()->permission('read', $root->get('category'))) {
148
            return $this->_requireAuth();
149
        }
150
151
        $this->_setRootEntry($root);
152
        $this->Title->setFromPosting($root, __('view.type.mix'));
153
154
        $this->set('showBottomNavigation', true);
155
        $this->set('entries', $postings);
156
157
        $this->_showAnsweringPanel();
158
159
        $this->Threads->incrementViews($root, 'thread');
160
        $this->MarkAsRead->thread($postings);
161
    }
162
163
    /**
164
     * load front page force all entries mark-as-read
165
     *
166
     * @return void
167
     */
168
    public function update()
169
    {
170
        $this->autoRender = false;
171
        $this->CurrentUser->getLastRefresh()->set();
172
        $this->redirect('/entries/index');
173
    }
174
175
    /**
176
     * Outputs raw markup of an posting $id
177
     *
178
     * @param string $id posting-ID
179
     * @return void
180
     */
181
    public function source($id = null)
182
    {
183
        $this->viewBuilder()->enableAutoLayout(false);
184
        $this->view($id);
185
    }
186
187
    /**
188
     * View posting.
189
     *
190
     * @param string $id posting-ID
191
     * @return \Cake\Network\Response|void
192
     */
193
    public function view($id = null)
194
    {
195
        Stopwatch::start('Entries->view()');
196
197
        // redirect if no id is given
198
        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...
199
            $this->Flash->set(__('Invalid post'), ['element' => 'error']);
200
201
            return $this->redirect(['action' => 'index']);
202
        }
203
204
        $entry = $this->Entries->get($id);
205
206
        // redirect if posting doesn't exists
207
        if ($entry == false) {
208
            $this->Flash->set(__('Invalid post'));
209
210
            return $this->redirect('/');
211
        }
212
213
        if (!$this->CurrentUser->getCategories()->permission('read', $entry->get('category'))) {
214
            return $this->_requireAuth();
215
        }
216
217
        $this->set('entry', $entry);
218
        $this->Threads->incrementViews($entry);
219
        $this->_setRootEntry($entry);
220
        $this->_showAnsweringPanel();
221
222
        $this->MarkAsRead->posting($entry);
223
224
        // inline open
225
        if ($this->request->is('ajax')) {
226
            return $this->render('/Element/entry/view_posting');
227
        }
228
229
        // full page request
230
        $this->set(
231
            'tree',
232
            $this->Entries->treeForNode($entry->get('tid'), ['root' => true])
233
        );
234
        $this->Title->setFromPosting($entry);
235
236
        Stopwatch::stop('Entries->view()');
237
    }
238
239
    /**
240
     * Add new posting.
241
     *
242
     * @param null|string $id parent-ID if is answer
243
     * @return void|\Cake\Network\Response
244
     * @throws ForbiddenException
245
     */
246
    public function add($id = null)
247
    {
248
        $titleForPage = __('Write a New Posting');
249
250
        if (!empty($this->request->getData())) {
251
            //= insert new posting
252
            $posting = $this->Entries->createPosting(
253
                $this->request->getData(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getData() targeting Cake\Http\ServerRequest::getData() can also be of type string; however, App\Model\Table\EntriesTable::createPosting() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
254
                $this->CurrentUser
255
            );
256
257
            // inserting new posting was successful
258
            if ($posting && !count($posting->getErrors())) {
259
                $id = $posting->get('id');
260
                $pid = $posting->get('pid');
261
                $tid = $posting->get('tid');
262
263
                if ($this->request->is('ajax')) {
264
                    if ($this->Referer->wasAction('index')) {
265
                        //= inline answer
266
                        $json = json_encode(
267
                            ['id' => $id, 'pid' => $pid, 'tid' => $tid]
268
                        );
269
                        $this->response = $this->response->withType('json');
270
                        $this->response = $this->response->withStringBody($json);
271
                    }
272
273
                    return $this->response;
274
                } else {
275
                    //= answering through POST request
276
                    $url = ['controller' => 'entries'];
277
                    if ($this->Referer->wasAction('mix')) {
278
                        //= answer came from mix-view
279
                        $url += ['action' => 'mix', $tid, '#' => $id];
280
                    } else {
281
                        //= normal posting from entries/add or entries/view
282
                        $url += ['action' => 'view', $posting->get('id')];
283
                    }
284
285
                    return $this->redirect($url);
286
                }
287
            } else {
288
                //= Error while trying to save a post
289
                /** @var Entry */
290
                $posting = $this->Entries->newEntity($this->request->getData());
0 ignored issues
show
Bug introduced by
It seems like $this->request->getData() targeting Cake\Http\ServerRequest::getData() can also be of type string; however, Cake\ORM\Table::newEntity() does only seem to accept array|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
291
292
                if (count($posting->getErrors()) === 0) {
293
                    //= Error isn't displayed as form validation error.
294
                    $this->Flash->set(
295
                        __(
296
                            'Something clogged the tubes. Could not save entry. Try again.'
297
                        ),
298
                        ['element' => 'error']
299
                    );
300
                }
301
            }
302
        } else {
303
            //= show form
304
            $posting = $this->Entries->newEntity();
305
            $isAnswer = $id !== null;
306
307
            if ($isAnswer) {
308
                //= answer to existing posting
309
                if ($this->request->is('ajax') === false) {
310
                    return $this->redirect($this->referer());
311
                }
312
313
                $parent = $this->Entries->get($id);
314
315
                if ($parent->isAnsweringForbidden()) {
316
                    throw new ForbiddenException;
317
                }
318
319
                $this->set('citeSubject', $parent->get('subject'));
320
                $this->set('citeText', $parent->get('text'));
321
322
                /** @var Entry */
323
                $posting = $this->Entries->patchEntity(
324
                    $posting,
325
                    ['pid' => $id]
326
                );
327
328
                // set Subnav
329
                $headerSubnavLeftTitle = __(
330
                    'back_to_posting_from_linkname',
331
                    $parent->get('user')->get('username')
332
                );
333
                $this->set('headerSubnavLeftTitle', $headerSubnavLeftTitle);
334
                $titleForPage = __('Write a Reply');
335
            } else {
336
                // new posting which creates new thread
337
                /** @var Entry */
338
                $posting = $this->Entries->patchEntity(
339
                    $posting,
340
                    ['pid' => 0, 'tid' => 0]
341
                );
342
            }
343
        }
344
345
        $isInline = $isAnswer = !$posting->isRoot();
346
        $formId = $posting->get('pid');
347
348
        $this->set(
349
            compact('isAnswer', 'isInline', 'formId', 'posting', 'titleForPage')
350
        );
351
        $this->_setAddViewVars($isAnswer);
352
    }
353
354
    /**
355
     * Get thread-line to insert after an inline-answer
356
     *
357
     * @param string $id posting-ID
358
     * @return void|\Cake\Network\Response
359
     */
360
    public function threadLine($id = null)
361
    {
362
        $posting = $this->Entries->get($id);
363
        if (!$this->CurrentUser->getCategories()->permission('read', $posting->get('category'))) {
364
            return $this->_requireAuth();
365
        }
366
367
        $this->set('entrySub', $posting);
368
        // ajax requests so far are always answers
369
        $this->response = $this->response->withType('json');
370
        $this->set('level', '1');
371
    }
372
373
    /**
374
     * Edit posting
375
     *
376
     * @param string $id posting-ID
377
     * @return void|\Cake\Network\Response
378
     * @throws NotFoundException
379
     * @throws BadRequestException
380
     */
381
    public function edit($id = null)
382
    {
383
        if (empty($id)) {
384
            throw new BadRequestException;
385
        }
386
387
        $posting = $this->Entries->get($id, ['return' => 'Entity']);
388
        if (!$posting) {
389
            throw new NotFoundException;
390
        }
391
392
        // try to save edit
393
        $data = $this->request->getData();
394
        if (!empty($data)) {
395
            $newEntry = $this->Entries->updatePosting($posting, $data, $this->CurrentUser);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->request->getData() on line 393 can also be of type string; however, App\Model\Table\EntriesTable::updatePosting() does only seem to accept array, 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...
396 View Code Duplication
            if ($newEntry) {
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...
397
                return $this->redirect(['action' => 'view', $id]);
398
            } else {
399
                $this->Flash->set(
400
                    __(
401
                        'Something clogged the tubes. Could not save entry. Try again.'
402
                    ),
403
                    ['element' => 'warning']
404
                );
405
            }
406
        }
407
408
        // show editing form
409
        if (!$posting->toPosting()->isEditingAsUserAllowed()) {
410
            $this->Flash->set(
411
                __('notice_you_are_editing_as_mod'),
412
                ['element' => 'warning']
413
            );
414
        }
415
416
        $this->Entries->patchEntity($posting, $this->request->getData());
0 ignored issues
show
Bug introduced by
It seems like $this->request->getData() targeting Cake\Http\ServerRequest::getData() can also be of type null or string; however, Cake\ORM\Table::patchEntity() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
417
418
        // get text of parent entry for citation
419
        $parentEntryId = $posting->get('pid');
420
        if ($parentEntryId > 0) {
421
            $parentEntry = $this->Entries->get($parentEntryId);
422
            $this->set('citeText', $parentEntry->get('text'));
423
        }
424
425
        $isAnswer = !$posting;
426
        $isInline = false;
427
        $formId = $posting->get('pid');
428
429
        $this->set(compact('isAnswer', 'isInline', 'formId', 'posting'));
430
431
        // set headers
432
        $this->set(
433
            'headerSubnavLeftTitle',
434
            __(
435
                'back_to_posting_from_linkname',
436
                $posting->get('user')->get('username')
437
            )
438
        );
439
        $this->set('headerSubnavLeftUrl', ['action' => 'view', $id]);
440
        $this->set('form_title', __('edit_linkname'));
441
        $this->_setAddViewVars($isAnswer);
442
        $this->render('/Entries/add');
443
    }
444
445
    /**
446
     * Delete posting
447
     *
448
     * @param string $id posting-ID
449
     * @return void
450
     * @throws NotFoundException
451
     * @throws MethodNotAllowedException
452
     */
453
    public function delete($id = null)
454
    {
455
        //$this->request->allowMethod(['post', 'delete']);
456
        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...
457
            throw new NotFoundException;
458
        }
459
        /* @var Entry $posting */
460
        $posting = $this->Entries->get($id);
461
        if (!$posting) {
462
            throw new NotFoundException;
463
        }
464
465
        $success = $this->Entries->treeDeleteNode($id);
466
467
        if ($success) {
468
            $flashType = 'success';
469
            if ($posting->isRoot()) {
470
                $message = __('delete_tree_success');
471
                $redirect = '/';
472
            } else {
473
                $message = __('delete_subtree_success');
474
                $redirect = '/entries/view/' . $posting->get('pid');
475
            }
476
        } else {
477
            $flashType = 'error';
478
            $message = __('delete_tree_error');
479
            $redirect = $this->referer();
480
        }
481
        $this->Flash->set($message, ['element' => $flashType]);
482
        $this->redirect($redirect);
483
    }
484
485
    /**
486
     * Empty function for benchmarking
487
     *
488
     * @return void
489
     */
490
    public function e()
491
    {
492
        Stopwatch::start('Entries->e()');
493
        Stopwatch::stop('Entries->e()');
494
    }
495
496
    /**
497
     * Marks sub-entry $id as solution to its current root-entry
498
     *
499
     * @param string $id posting-ID
500
     * @return void
501
     * @throws BadRequestException
502
     */
503
    public function solve($id)
504
    {
505
        $this->autoRender = false;
506
        try {
507
            $posting = $this->Entries->get($id, ['return' => 'Entity']);
508
509
            if (empty($posting)) {
510
                throw new \InvalidArgumentException('Posting to mark solved not found.');
511
            }
512
513
            if ($posting->isRoot()) {
514
                throw new \InvalidArgumentException('Root postings cannot mark themself solved.');
515
            }
516
517
            $rootId = $posting->get('tid');
518
            $rootPosting = $this->Entries->get($rootId);
519
            if ($rootPosting->get('user_id') !== $this->CurrentUser->getId()) {
520
                throw new SaitoForbiddenException(
521
                    sprintf('Attempt to mark posting %s as solution.', $posting->get('id')),
522
                    ['CurrentUser' => $this->CurrentUser]
523
                );
524
            }
525
526
            $success = $this->Entries->toggleSolve($posting);
527
528
            if (!$success) {
529
                throw new BadRequestException;
530
            }
531
        } catch (\Exception $e) {
532
            throw new BadRequestException();
533
        }
534
    }
535
536
    /**
537
     * Merge threads.
538
     *
539
     * @param string $sourceId posting-ID of thread to be merged
540
     * @return void
541
     * @throws NotFoundException
542
     * @td put into admin entries controller
543
     */
544
    public function merge($sourceId = null)
545
    {
546
        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...
547
            throw new NotFoundException();
548
        }
549
550
        /* @var Entry */
551
        $posting = $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...
552
553
        if (!$posting || !$posting->isRoot()) {
554
            throw new NotFoundException();
555
        }
556
557
        // perform move operation
558
        $targetId = $this->request->getData('targetId');
559
        if (!empty($targetId)) {
560 View Code Duplication
            if ($this->Entries->threadMerge($sourceId, $targetId)) {
0 ignored issues
show
Documentation introduced by
$targetId is of type string|array, 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...
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...
561
                $this->redirect('/entries/view/' . $sourceId);
562
563
                return;
564
            } else {
565
                $this->Flash->set(__('Error'), ['element' => 'error']);
566
            }
567
        }
568
569
        $this->viewBuilder()->setLayout('Admin.admin');
570
        $this->set(compact('posting'));
571
    }
572
573
    /**
574
     * Toggle posting property via ajax request.
575
     *
576
     * @param string $id posting-ID
577
     * @param string $toggle property
578
     *
579
     * @return \Cake\Network\Response
580
     */
581
    public function ajaxToggle($id = null, $toggle = null)
582
    {
583
        $allowed = ['fixed', 'locked'];
584
        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...
585
            || !$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...
586
            || !$this->request->is('ajax')
587
            || !in_array($toggle, $allowed)
588
        ) {
589
            throw new BadRequestException;
590
        }
591
592
        $current = $this->Entries->toggle((int)$id, $toggle);
593
        if ($current) {
594
            $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...
595
        } else {
596
            $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...
597
        }
598
599
        $this->response = $this->response->withType('json');
600
        $this->response = $this->response->withStringBody(json_encode($out));
601
602
        return $this->response;
603
    }
604
605
    /**
606
     * {@inheritDoc}
607
     */
608
    public function beforeFilter(Event $event)
609
    {
610
        parent::beforeFilter($event);
611
        Stopwatch::start('Entries->beforeFilter()');
612
613
        $this->Security->setConfig(
614
            'unlockedActions',
615
            ['solve', 'view']
616
        );
617
        $this->Auth->allow(['index', 'view', 'mix', 'update']);
618
619
        Stopwatch::stop('Entries->beforeFilter()');
620
    }
621
622
    /**
623
     * set view vars for category chooser
624
     *
625
     * @param CurrentUserInterface $User CurrentUser
626
     * @return void
627
     */
628
    protected function _setupCategoryChooser(CurrentUserInterface $User)
629
    {
630
        if (!$User->isLoggedIn()) {
631
            return;
632
        }
633
        $globalActivation = Configure::read(
634
            'Saito.Settings.category_chooser_global'
635
        );
636
        if (!$globalActivation) {
637
            if (!Configure::read(
638
                'Saito.Settings.category_chooser_user_override'
639
            )
640
            ) {
641
                return;
642
            }
643
            if (!$User->get('user_category_override')) {
644
                return;
645
            }
646
        }
647
648
        $this->set(
649
            'categoryChooserChecked',
650
            $User->getCategories()->getCustom('read')
651
        );
652
        switch ($User->getCategories()->getType()) {
653
            case 'single':
654
                $title = $User->get('user_category_active');
655
                break;
656
            case 'custom':
657
                $title = __('Custom');
658
                break;
659
            default:
660
                $title = __('All Categories');
661
        }
662
        $this->set('categoryChooserTitleId', $title);
663
        $this->set(
664
            'categoryChooser',
665
            $User->getCategories()->getAll('read', 'list')
666
        );
667
    }
668
669
    /**
670
     * set additional vars for creating a new posting
671
     *
672
     * @param bool $isAnswer is new posting answer or root
673
     * @return void
674
     */
675
    protected function _setAddViewVars($isAnswer)
676
    {
677
        //= categories for dropdown
678
        $action = $isAnswer ? 'answer' : 'thread';
679
        $categories = $this->CurrentUser->getCategories()->getAll($action, 'list');
680
        $this->set('categories', $categories);
681
    }
682
683
    /**
684
     * Decide if an answering panel is show when rendering a posting
685
     *
686
     * @return void
687
     */
688
    protected function _showAnsweringPanel()
689
    {
690
        $showAnsweringPanel = false;
691
692
        if ($this->CurrentUser->isLoggedIn()) {
693
            // Only logged in users see the answering buttons if they …
694
            if (// … directly on entries/view but not inline
695
                ($this->request->getParam('action') === 'view' && !$this->request->is('ajax'))
696
                // … directly in entries/mix
697
                || $this->request->getParam('action') === 'mix'
698
                // … inline viewing … on entries/index.
699
                || ($this->Referer->wasController('entries')
700
                    && $this->Referer->wasAction('index'))
701
            ) {
702
                $showAnsweringPanel = true;
703
            }
704
        }
705
        $this->set('showAnsweringPanel', $showAnsweringPanel);
706
    }
707
708
    /**
709
     * makes root posting of $posting avaiable in view
710
     *
711
     * @param Posting $posting posting for root entry
712
     * @return void
713
     */
714
    protected function _setRootEntry(Posting $posting)
715
    {
716
        if (!$posting->isRoot()) {
717
            $root = $this->Entries->find()
718
                ->select(['user_id'])
719
                ->where(['id' => $posting->get('tid')])
720
                ->first();
721
        } else {
722
            $root = $posting;
723
        }
724
        $this->set('rootEntry', $root);
725
    }
726
}
727