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; |
|
|
|
|
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) { |
|
|
|
|
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(), |
|
|
|
|
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()); |
|
|
|
|
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); |
|
|
|
|
396
|
|
View Code Duplication |
if ($newEntry) { |
|
|
|
|
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()); |
|
|
|
|
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) { |
|
|
|
|
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) { |
|
|
|
|
547
|
|
|
throw new NotFoundException(); |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
/* @var Entry */ |
551
|
|
|
$posting = $this->Entries->findById($sourceId)->first(); |
|
|
|
|
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)) { |
|
|
|
|
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 |
|
|
|
|
585
|
|
|
|| !$toggle |
|
|
|
|
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'); |
|
|
|
|
595
|
|
|
} else { |
596
|
|
|
$out['html'] = __d('nondynamic', $toggle . '_set_entry_link'); |
|
|
|
|
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
|
|
|
|
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.