Issues (1693)

sources/ElkArte/Controller/Markasread.php (3 issues)

1
<?php
2
3
/**
4
 * Handles all mark as read options, boards, topics, replies
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * @version 2.0 dev
11
 *
12
 */
13
14
namespace ElkArte\Controller;
15
16
use ElkArte\AbstractController;
17
use ElkArte\Action;
18
use ElkArte\Languages\Txt;
19
20
/**
21
 * This class handles a part of the actions to mark boards, topics, or replies,
22
 * as read/unread.
23
 */
24
class Markasread extends AbstractController
25
{
26
	/** @var array used to redirect user to the correct boards when marking unread */
27
	private $_querystring_board_limits;
28
29
	/** @var array used to remember user's sorting options when marking unread */
30
	private $_querystring_sort_limits;
31
32
	/** @var bool if this is an api call */
33
	private $api = false;
34
35
	/**
36
	 * This is the pre-dispatch function, actions common to all methods
37
	 */
38
	public function pre_dispatch()
39
	{
40
		$this->api = $this->getApi() === 'xml';
41
42
		// We will check these items in the ajax function
43
		if (!$this->api)
44
		{
45
			// Guests can't mark things.
46
			is_not_guest();
47
48
			checkSession('get');
49
		}
50
	}
51
52
	/**
53
	 * This is the main function for markasread file
54
	 *
55
	 * markasread;sa=topic;t=###;topic=###.0;session Mark a topic unread
56
	 * markasread;sa=board;board=#.0;session Mark a board (all its topics) as read
57
	 * markasread;sa=board;c=#;start=0;session Mark a category read
58
	 * markasread;sa=all;session everything is read
59
	 * markasread;sa=unreadreplies;topics=6056-4692-6026-5817;session
60
	 */
61
	public function action_index()
62
	{
63
		global $context;
64
65
		$subActions = [
66
			'all' => [$this, 'action_markboards'],
67
			'unreadreplies' => [$this, 'action_markreplies'],
68
			'topic' => [$this, 'action_marktopic_unread'],
69
			'markasread' => [$this, 'action_markasread']
70
		];
71
72
		$action = new Action('markasread');
73
		$subAction = $action->initialize($subActions, 'markasread');
74
		$context['sub_action'] = $subAction;
75
76
		if ($this->api)
77
		{
78
			$this->action_index_api($action, $subAction);
79
			return '';
80
		}
81
82
		$action->dispatch($subAction);
83
	}
84
85
	/**
86
	 * This is the controller when using APIs.
87
	 *
88
	 * @uses Xml template generic_xml_buttons sub template
89
	 */
90
	public function action_index_api($action, $subAction): ?string
91
	{
92
		global $context, $txt;
93
94
		// Setup for an Ajax response
95
		theme()->getTemplates()->load('Xml');
96
		theme()->getLayers()->removeAll();
97
		$context['sub_template'] = 'generic_xml_buttons';
98
99
		// Guests can't mark things.
100
		if ($this->user->is_guest)
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
101
		{
102
			Txt::load('Errors');
103
			$context['xml_data'] = [
104
				'error' => 1,
105
				'text' => $txt['not_guests']
106
			];
107
108
			return '';
109
		}
110
111
		// Best have a valid session
112
		if (checkSession('get', '', false))
113
		{
114
			// Again, this is a special case, someone will deal with the others later :P
115
			if ($this->_req->getQuery('sa') === 'all')
116
			{
117
				Txt::load('Errors');
118
				$context['xml_data'] = [
119
					'error' => 1,
120
					'url' => getUrl('action', ['action' => 'markasread', 'sa' => 'all', '{session_data}']),
121
				];
122
123
				return '';
124
			}
125
126
			obExit(false);
127
		}
128
129
		// Dispatch to the right method
130
		$action->dispatch($subAction);
131
132
		// For the time being this is a special case, but in BoardIndex no, we don't want it
133
		if ($this->_req->getQuery('sa') === 'all' || ($this->_req->getQuery('sa') === 'board' && !isset($this->_req->query->bi)))
134
		{
135
			$url_params = ['action' => 'unread', 'all', '{session_data}'];
136
			if (!empty($this->_querystring_board_limits))
137
			{
138
				$url_params += $this->_querystring_board_limits;
139
				$url_params['start'] = 0;
140
			}
141
142
			if (!empty($this->_querystring_sort_limits))
143
			{
144
				$url_params += $this->_querystring_sort_limits;
145
			}
146
147
			$context['xml_data'] = [
148
				'text' => $txt['topic_alert_none'],
149
				'body' => str_replace('{unread_all_url}', getUrl('action', $url_params), $txt['unread_topics_visit_none']),
150
			];
151
152
			return '';
153
		}
154
155
		// No need to output anything, just return to the button
156
		obExit(false);
157
		return null;
158
	}
159
160
	/**
161
	 * Marks boards as read (or unread)
162
	 *
163
	 * - Accessed by action=markasread;sa=all
164
	 */
165
	public function action_markboards(): ?string
166
	{
167
		global $modSettings;
168
169
		require_once(SUBSDIR . '/Boards.subs.php');
170
171
		// Find all the boards this user can see.
172
		$boards = accessibleBoards();
173
174
		// Mark boards as read
175
		if (!empty($boards))
176
		{
177
			markBoardsRead($boards, isset($this->_req->query->unread), true);
178
		}
179
180
		$_SESSION['id_msg_last_visit'] = $modSettings['maxMsgID'];
181
		$redirectAction = '';
182
		if (!empty($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'action=unread') !== false)
183
		{
184
			$redirectAction = 'action=unread';
185
		}
186
187
		if (isset($_SESSION['topicseen_cache']))
188
		{
189
			$_SESSION['topicseen_cache'] = [];
190
		}
191
192
		if (!empty($modSettings['default_forum_action']) && $redirectAction === '')
193
		{
194
			$redirectAction = getUrlQuery('action', $modSettings['default_forum_action']);
195
		}
196
197
		if ($this->api)
198
		{
199
			return '';
200
		}
201
202
		redirectexit($redirectAction);
203
		return null;
204
	}
205
206
	/**
207
	 * Marks the selected topics as read.
208
	 *
209
	 * - Accessed by action=markasread;sa=unreadreplies
210
	 */
211
	public function action_markreplies(): ?string
212
	{
213
		global $modSettings;
214
215
		// Make sure all the topics are integers!
216
		$topics = array_map('intval', explode('-', $this->_req->query->topics));
217
218
		require_once(SUBSDIR . '/Topic.subs.php');
219
		$logged_topics = getLoggedTopics($this->user->id, $topics);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
220
221
		$markRead = [];
222
		foreach ($topics as $id_topic)
223
		{
224
			$markRead[] = [$this->user->id, (int) $id_topic, $modSettings['maxMsgID'], (int) !empty($logged_topics[$id_topic])];
225
		}
226
227
		markTopicsRead($markRead, true);
228
229
		if (isset($_SESSION['topicseen_cache']))
230
		{
231
			$_SESSION['topicseen_cache'] = [];
232
		}
233
234
		if ($this->api)
235
		{
236
			return '';
237
		}
238
239
		return redirectexit('action=unreadreplies');
240
	}
241
242
	/**
243
	 * Mark a single topic as unread, returning to the board topic listing
244
	 *
245
	 * - Accessed by action=markasread;sa=topic;topic=123;t=123
246
	 * - Button URL set in Display.php Controller
247
	 */
248
	public function action_marktopic_unread(): ?string
249
	{
250
		global $board, $topic;
251
252
		require_once(SUBSDIR . '/Topic.subs.php');
253
		require_once(SUBSDIR . '/Messages.subs.php');
254
255
		// First, let's figure out what the latest message is.
256
		$topicinfo = getTopicInfo($topic, 'all');
257
		$topic_msg_id = $this->_req->getQuery('t', 'intval');
258
		if (!empty($topic_msg_id))
259
		{
260
			// If they read the whole topic, go back to the beginning.
261
			if ($topic_msg_id >= $topicinfo['id_last_msg'])
262
			{
263
				$earlyMsg = 0;
264
			}
265
			// If they want to mark the whole thing read, same.
266
			elseif ($topic_msg_id <= $topicinfo['id_first_msg'])
267
			{
268
				$earlyMsg = 0;
269
			}
270
			// Otherwise, get the latest message before the named one.
271
			else
272
			{
273
				$earlyMsg = previousMessage($topic_msg_id, $topic);
274
			}
275
		}
276
		// Marking read from first page?  That's the whole topic.
277
		elseif ($this->_req->query->start == 0)
278
		{
279
			$earlyMsg = 0;
280
		}
281
		else
282
		{
283
			[$earlyMsg] = messageAt((int) $this->_req->query->start, $topic);
284
			$earlyMsg--;
285
		}
286
287
		// Blam, unread!
288
		markTopicsRead([$this->user->id, $topic, $earlyMsg, $topicinfo['unwatched']], true);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
289
290
		if ($this->api)
291
		{
292
			return '';
293
		}
294
295
		return redirectexit('board=' . $board . '.0');
296
	}
297
298
	/**
299
	 * Mark as read: boards, topics, unread replies.
300
	 *
301
	 * - Accessed by action=markasread;sa=board;board=1.0;session
302
	 * - Subactions: sa=topic, sa=all, sa=unreadreplies, sa=board
303
	 */
304
	public function action_markasread(): ?string
305
	{
306
		global $board, $board_info;
307
308
		require_once(SUBSDIR . '/Boards.subs.php');
309
310
		$categories = [];
311
		$boards = [];
312
313
		if (isset($this->_req->query->c))
314
		{
315
			$categories = array_map('intval', explode(',', $this->_req->query->c));
316
		}
317
318
		if (isset($this->_req->query->boards))
319
		{
320
			$boards = array_map('intval', explode(',', $this->_req->query->boards));
321
		}
322
323
		if (!empty($board))
324
		{
325
			$boards[] = (int) $board;
326
		}
327
328
		if (isset($this->_req->query->children) && !empty($boards))
329
		{
330
			// Mark all children of the boards we got (selected by the user).
331
			$boards = addChildBoards($boards);
332
		}
333
334
		$boards = array_keys(boardsPosts($boards, $categories));
335
336
		if (empty($boards))
337
		{
338
			if ($this->api)
339
			{
340
				return '';
341
			}
342
343
			redirectexit();
344
			return null;
345
		}
346
347
		// Mark boards as read.
348
		markBoardsRead($boards, isset($this->_req->query->unread), true);
349
350
		foreach ($boards as $b)
351
		{
352
			if (isset($_SESSION['topicseen_cache'][$b]))
353
			{
354
				$_SESSION['topicseen_cache'][$b] = [];
355
			}
356
		}
357
358
		$this->_querystring_board_limits = $this->_req->getQuery('sa') === 'board' ? ['boards' => implode(',', $boards), 'start' => '%d'] : [];
359
360
		$this->_setQuerystringSortLimits();
361
362
		$this->_markAsRead($boards);
363
364
		if (empty($board_info['parent']) && !$this->api)
365
		{
366
			redirectexit();
367
			return null;
368
		}
369
370
		if ($this->api)
371
		{
372
			return '';
373
		}
374
375
		redirectexit('board=' . $board_info['parent'] . '.0');
376
		return null;
377
	}
378
379
	/**
380
	 * Sets the sorting parameters
381
	 */
382
	private function _setQuerystringSortLimits(): void
383
	{
384
		$sort_methods = [
385
			'subject',
386
			'starter',
387
			'replies',
388
			'views',
389
			'first_post',
390
			'last_post'
391
		];
392
393
		// The default is the most logical: newest first.
394
		if (!isset($this->_req->query->sort) || !in_array($this->_req->query->sort, $sort_methods))
395
		{
396
			$this->_querystring_sort_limits = isset($this->_req->query->asc) ? ['asc'] : [];
397
		}
398
		// But, for other methods the default sort is ascending.
399
		else
400
		{
401
			$this->_querystring_sort_limits = ['sort' => $this->_req->query->sort, isset($this->_req->query->desc) ? 'desc' : ''];
402
		}
403
	}
404
405
	/**
406
	 * Mark a group of boards as read
407
	 *
408
	 * @param array $boards
409
	 */
410
	private function _markAsRead($boards): ?string
411
	{
412
		global $board;
413
414
		// Want to mark as unread, nothing to do here
415
		if (isset($this->_req->query->unread))
416
		{
417
			return '';
418
		}
419
420
		// Find all boards with the parents in the board list
421
		$boards_to_add = accessibleBoards(null, $boards);
422
		if (!empty($boards_to_add))
423
		{
424
			markBoardsRead($boards_to_add);
425
		}
426
427
		$redirectAction = 'board=' . $board . '.0';
428
		if (empty($board))
429
		{
430
			$redirectAction = '';
431
		}
432
433
		if ($this->api)
434
		{
435
			return '';
436
		}
437
438
		redirectexit($redirectAction);
439
		return null;
440
	}
441
}
442