Recent::_recentPostsAll()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 4
dl 0
loc 10
rs 10
c 0
b 0
f 0
nc 1
nop 0
ccs 0
cts 4
cp 0
crap 12
1
<?php
2
3
/**
4
 * Find and retrieve information about recently posted topics, messages, and the like.
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
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte\Controller;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Exceptions\Exception;
21
use ElkArte\FrontpageInterface;
22
23
/**
24
 * Retrieve information about recent posts
25
 */
26
class Recent extends AbstractController implements FrontpageInterface
27
{
28
	/** @var \ElkArte\Recent The object that will retrieve the data */
29
	private $_grabber;
30
31
	/** @var int[] Sets range to query */
32
	private $_maxMsgID;
33
34
	/** @var string The url for the recent action */
35
	private $_base_url;
36
37
	/** @var int The number of posts found */
38
	private $_total_posts;
39
40
	/** @var int The starting place for pagination */
41
	private $_start;
42
43
	/** @var array The permissions own/any for use in the query */
44
	private $_permissions = [];
45
46
	/** @var bool Pass to pageindex, to use "url.page" instead of "url;start=page" */
47
	private $_flex_start = false;
48
49
	/** @var int Number of posts per page */
50
	private $_num_per_page = 10;
51
52
	/**
53
	 * {@inheritDoc}
54
	 */
55
	public static function frontPageHook(&$default_action)
56
	{
57
		add_integration_function('integrate_menu_buttons', '\\ElkArte\\Controller\\MessageIndex::addForumButton', '', false);
58
		add_integration_function('integrate_current_action', '\\ElkArte\\Controller\\MessageIndex::fixCurrentAction', '', false);
59
60
		$default_action = [
61
			'controller' => Recent::class,
62
			'function' => 'action_recent_front'
63
		];
64
	}
65
66
	/**
67
	 * {@inheritDoc}
68
	 */
69
	public static function frontPageOptions()
70
	{
71
		parent::frontPageOptions();
72
73
		theme()->addInlineJavascript('
74
			document.getElementById("front_page").addEventListener("change", function() {
75
			    let base = document.getElementById("recent_frontpage").parentNode;
76
			
77
			    if (this.value.endsWith("Recent")) 
78
			    {
79
			        base.fadeIn();
80
			        base.previousElementSibling.fadeIn();
81
			    }
82
			    else 
83
			    {
84
			        base.fadeOut();
85
			        base.previousElementSibling.fadeOut();
86
			    }
87
			});', true);
88
89
		return [['int', 'recent_frontpage']];
90
	}
91
92
	/**
93
	 * Called before any other action method in this class.
94
	 *
95
	 * - Allows for initializations, such as default values or
96
	 * loading templates or language files.
97
	 */
98
	public function pre_dispatch()
99
	{
100
		// Prefetching + lots of MySQL work = bad mojo.
101
		stop_prefetching();
102
103
		// Some common method dependencies
104
		require_once(SUBSDIR . '/Recent.subs.php');
105
		require_once(SUBSDIR . '/Boards.subs.php');
106
107
		// There might be - and are - different permissions between any and own.
108
		$this->_permissions = array(
109
			'own' => array(
110
				'post_reply_own' => 'can_reply',
111
				'delete_own' => 'can_delete',
112
			),
113
			'any' => array(
114
				'post_reply_any' => 'can_reply',
115
				'mark_any_notify' => 'can_mark_notify',
116
				'delete_any' => 'can_delete',
117
				'like_posts' => 'can_like'
118
			)
119
		);
120
	}
121
122
	/**
123
	 * Intended entry point for recent controller class.
124
	 *
125
	 * @see AbstractController::action_index
126
	 */
127
	public function action_index()
128
	{
129 2
		// Figure out what action to do, thinking, thinking ...
130
		$this->action_recent();
131
	}
132 2
133
	/**
134
	 * Find the ten most recent posts.
135 2
	 *
136 2
	 * Accessed by action=recent.
137
	 */
138
	public function action_recent()
139 2
	{
140
		global $txt, $context, $modSettings, $board;
141
142
		// Start up a new recent posts grabber
143
		$this->_grabber = new \ElkArte\Recent($this->user->id);
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...
144
145
		// Set or use a starting point for pagination
146
		$this->_start = $this->_req->getQuery('start', 'intval', 0);
147
148
		// Recent posts by category id's
149
		if (!empty($this->_req->query->c) && empty($board))
150
		{
151 2
			$categories = $this->_recentPostsCategory();
152
		}
153
		// Or recent posts by board id's?
154
		elseif (!empty($this->_req->query->boards))
155
		{
156
			$this->_recentPostsBoards();
157
		}
158
		// Or just the recent posts for a specific board
159
		elseif (!empty($board))
160
		{
161
			$this->_recentPostsBoard();
162
		}
163
		// All the recent posts across boards and categories it is then
164
		else
165
		{
166
			$this->_recentPostsAll();
167
		}
168
169 2
		if (!empty($this->_maxMsgID))
170
		{
171 2
			$this->_grabber->setEarliestMsg(max(0, $modSettings['maxMsgID'] - $this->_maxMsgID[0] - $this->_start * $this->_maxMsgID[1]));
172
		}
173
174 2
		// Set up the pageindex
175
		$context['page_index'] = constructPageIndex($this->_base_url, $this->_start, min(100, $this->_total_posts), $this->_num_per_page, !empty($this->_flex_start));
176
177 2
		// Rest of the items for the template
178
		theme()->getTemplates()->load('Recent');
179
		$context['page_title'] = $txt['recent_posts'];
180 2
		$context['sub_template'] = 'recent';
181
		$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
182 2
183
		// Breadcrumbs
184
		$context['breadcrumbs'][] = [
185
			'url' => getUrl('action', ['action' => 'recent'] + (empty($board) ? (empty($categories) ? [] : ['c' => implode(',', $categories)]) : ['board' => $board . '.0'])),
186
			'name' => $context['page_title']
187
		];
188
189
		// Nothing here... Or at least, nothing you can see...
190
		if (!$this->_grabber->findRecentMessages($this->_start, $this->_num_per_page))
191
		{
192
			$context['posts'] = array();
193
		}
194
		else
195
		{
196
			$context['posts'] = $this->_grabber->getRecentPosts($this->_start, $this->_permissions);
197
		}
198
199
		// Load any likes for the messages
200 2
		$context['likes'] = $this->_getLikes($context['posts']);
201
202
		foreach ($context['posts'] as $counter => $post)
203
		{
204
			// Some posts - the first posts - can't just be deleted.
205
			$context['posts'][$counter]['tests']['can_delete'] = $context['posts'][$counter]['tests']['can_delete'] && $context['posts'][$counter]['delete_possible'];
206 2
207
			// And some cannot be quoted...
208
			$context['posts'][$counter]['tests']['can_quote'] = $context['posts'][$counter]['tests']['can_reply'] && $quote_enabled;
209 2
210 2
			// Likes are always a bit particular
211 2
			$post['you_liked'] = !empty($context['likes'][$counter]['member'])
212 2
				&& isset($context['likes'][$counter]['member'][$this->user->id]);
213
			$post['use_likes'] = !empty($post['tests']['can_like']) && allowedTo('like_posts') && empty($context['is_locked'])
214
				&& ($post['poster']['id'] != $this->user->id || !empty($modSettings['likeAllowSelf']))
215 2
				&& (empty($modSettings['likeMinPosts']) || $modSettings['likeMinPosts'] <= $this->user->posts);
0 ignored issues
show
Bug Best Practice introduced by
The property posts does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
216 2
			$post['like_counter'] = empty($context['likes'][$counter]['count']) ? 0 : $context['likes'][$counter]['count'];
217 2
			$post['can_like'] = $post['use_likes'] && !$post['you_liked'];
218
			$post['can_unlike'] = $post['use_likes'] && $post['you_liked'];
219
			$post['likes_enabled'] = !empty($modSettings['likes_enabled']) && ($post['use_likes'] || ($post['like_counter'] != 0));
220
221 2
			// Let's add some buttons here!
222
			$context['posts'][$counter]['buttons'] = $this->_addButtons($post, $context['posts'][$counter]['tests']);
223
		}
224
	}
225
226
	/**
227 2
	 * Set up for getting recent posts on a category basis
228
	 */
229
	private function _recentPostsCategory()
230
	{
231 2
		global $modSettings, $context;
232
233 2
		$categories = array_map('intval', explode(',', $this->_req->query->c));
234
235
		if (count($categories) === 1)
236 2
		{
237
			require_once(SUBSDIR . '/Categories.subs.php');
238
			$name = categoryName($categories[0]);
239 2
240
			if (empty($name))
241
			{
242 2
				throw new Exception('no_access', false);
243 2
			}
244 2
245 2
			$context['breadcrumbs'][] = [
246 2
				'url' => getUrl('action', $modSettings['default_forum_action']) . '#c' . $categories[0],
247 2
				'name' => $name
248 2
			];
249 2
		}
250 2
251
		// Find the number of posts in these category's, exclude the recycle board.
252
		$boards_posts = boardsPosts(array(), $categories, false, false);
253 2
		$this->_total_posts = (int) array_sum($boards_posts);
254
		$boards = array_keys($boards_posts);
255 2
256
		if (empty($boards))
257
		{
258
			throw new Exception('error_no_boards_selected');
259
		}
260 2
261
		// The query for getting the messages
262 2
		$this->_grabber->setBoards($boards);
263
264 2
		// If this category has a significant number of posts in it...
265
		if ($this->_total_posts > 100 && $this->_total_posts > $modSettings['totalMessages'] / 15)
266 2
		{
267
			$this->_maxMsgID = array(400, 7);
268 2
		}
269 2
270
		$this->_base_url = '{scripturl}?action=recent;c=' . implode(',', $categories);
271 2
272
		return $categories;
273
	}
274
275
	/**
276 2
	 * Setup for finding recent posts based on a list of boards
277 2
	 */
278 2
	private function _recentPostsBoards()
279
	{
280
		global $modSettings;
281
282
		$this->_req->query->boards = array_map('intval', explode(',', $this->_req->query->boards));
283 2
284 2
		// Fetch the number of posts for the supplied board IDs
285 2
		$boards_posts = boardsPosts($this->_req->query->boards, array());
286
		$this->_total_posts = (int) array_sum($boards_posts);
287 2
		$boards = array_keys($boards_posts);
288
289
		// No boards, your request ends here
290
		if (empty($boards))
291
		{
292
			throw new Exception('error_no_boards_selected');
293 2
		}
294
295
		// Build the query for finding the messages
296 2
		$this->_grabber->setBoards($boards);
297
298
		// If these boards have a significant number of posts in them...
299
		if ($this->_total_posts > 100 && $this->_total_posts > $modSettings['totalMessages'] / 12)
300
		{
301 2
			$this->_maxMsgID = array(500, 9);
302
		}
303 2
304
		$this->_base_url = '{scripturl}?action=recent;boards=' . implode(',', $this->_req->query->boards);
305
	}
306
307
	/**
308
	 * Setup for finding recent posts for a single board
309
	 */
310
	private function _recentPostsBoard()
311
	{
312
		global $modSettings, $board;
313
314
		$board_data = fetchBoardsInfo(array('boards' => $board), array('selects' => 'posts'));
315
		$this->_total_posts = $board_data[(int) $board]['num_posts'];
316
317
		$this->_grabber->setBoards($board);
318
319
		// If this board has a significant number of posts in it...
320
		if ($this->_total_posts > 80 && $this->_total_posts > $modSettings['totalMessages'] / $this->_num_per_page)
321
		{
322
			$this->_maxMsgID = array(600, 10);
323
		}
324
325
		$this->_base_url = '{scripturl}?action=recent;board=' . $board . '.%1$d';
326
		$this->_flex_start = true;
327
	}
328
329
	/**
330
	 * Setup to find all the recent posts across all boards and categories
331
	 */
332
	private function _recentPostsAll()
333
	{
334
		global $modSettings;
335
336
		$this->_total_posts = sumRecentPosts();
337
338
		$this->_grabber->setVisibleBoards(max(0, $modSettings['maxMsgID'] - 100 - $this->_start * 6), !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? $modSettings['recycle_board'] : 0);
339
340
		// Set up the pageindex
341
		$this->_base_url = '{scripturl}?action=recent';
342
	}
343
344
	/**
345
	 * Loads the likes for the set of recent messages
346
	 *
347
	 * @param array $messages
348
	 *
349
	 * @return array|int[]
350
	 * @throws \Exception
351
	 */
352
	private function _getLikes($messages)
353
	{
354
		global $modSettings;
355
356
		$likes = array();
357
358
		// Load in the likes for this group of messages
359
		if (!empty($modSettings['likes_enabled']))
360
		{
361
			// Just the message id please
362
			$messages = array_column($messages, 'id');
363
364
			require_once(SUBSDIR . '/Likes.subs.php');
365
			$likes = loadLikes($messages, true);
366
367
			theme()->getLayers()->addBefore('load_likes_button', 'body');
368
		}
369
370
		return $likes;
371
	}
372
373
	/**
374
	 * Create the buttons array for this post
375
	 * Array is used by template_button_strip(), see that function for parameter details
376
	 *
377
	 * @param array $post Details of this post
378
	 * @param array $tests array holding true false values for various test keys, like can_quote;
379
	 * @return array
380
	 */
381
	private function _addButtons($post, $tests)
382
	{
383 2
		global $txt;
384
385 2
		$postButtons = [
386
			// How about... even... remove it entirely?!
387 2
			'remove' => [
388
				'url' => getUrl('action', ['action' => 'deletemsg', 'msg' => $post['id'], 'topic' => $post['topic'], 'recent', '{session_data}']),
389
				'text' => 'remove',
390 2
				'icon' => 'delete',
391
				'enabled' => $tests['can_delete'],
392
				'custom' => 'onclick="return confirm(' . JavaScriptEscape($txt['remove_message'] . '?') . ');"',
393 2
			],
394
			// Can we request notification of topics?
395 2
			'notify' => [
396 2
				'url' => getUrl('action', ['action' => 'notify', 'topic' => $post['topic'] . '.' . $post['start']]),
397
				'text' => 'notify',
398 2
				'icon' => 'comment',
399
				'enabled' => $tests['can_mark_notify'],
400
			],
401 2
			// If they *can* reply?
402
			'reply' => [
403
				'url' => getUrl('action', ['action' => 'post', 'topic' => $post['topic'] . '.' . $post['start']]),
404
				'text' => 'reply',
405
				'icon' => 'modify',
406
				'enabled' => $tests['can_reply'],
407 2
			],
408
			// If they *can* quote?
409 2
			'quote' => [
410
				'text' => 'quote',
411
				'url' => getUrl('action', ['action' => 'post', 'topic' => $post['topic'] . '.' . $post['start'], 'quote' => $post['id']]),
412 2
				'class' => 'quote_button last',
413 2
				'icon' => 'quote',
414 2
				'enabled' => $tests['can_quote'],
415
			],
416 2
			// If they *can* like / unlike / just see totals
417
			'react' => [
418
				'text' => 'like_post',
419 2
				'url' => 'javascript:void(0);',
420
				'custom' => 'onclick="likePosts.prototype.likeUnlikePosts(event,' . $post['id'] . ',' . $post['topic'] . '); return false;"',
421
				'linkclass' => 'react_button',
422
				'icon' => 'thumbsup',
423 2
				'enabled' => $post['likes_enabled'] && $post['can_like'],
424 2
				'counter' => $post['like_counter'] ?? 0,
425 2
			],
426
			'unreact' => [
427
				'text' => 'unlike_post',
428
				'url' => 'javascript:void(0);',
429
				'custom' => 'onclick="likePosts.prototype.likeUnlikePosts(event,' . $post['id'] . ',' . $post['topic'] . '); return false;"',
430
				'linkclass' => 'unreact_button',
431
				'icon' => 'thumbsdown',
432
				'enabled' => $post['likes_enabled'] && $post['can_unlike'],
433
				'counter' => $post['like_counter'] ?? 0,
434
			],
435
			'liked' => [
436 2
				'text' => 'likes',
437 2
				'url' => 'javascript:void(0);',
438
				'custom' => 'onclick="this.blur();"',
439
				'icon' => 'thumbsup',
440
				'enabled' => $post['likes_enabled'] && !$post['can_unlike'] && !$post['can_like'],
441
				'counter' => $post['like_counter'] ?? 0,
442
			],
443
		];
444
445 2
		// Drop all non-enabled ones
446
		return array_filter($postButtons, static fn($button) => !isset($button['enabled']) || $button['enabled'] !== false);
447 2
	}
448
449 2
	/**
450
	 * Intended entry point for recent controller class.
451
	 *
452 2
	 * @see Action_Controller::action_index()
453
	 */
454
	public function action_recent_front()
455 2
	{
456 2
		global $modSettings;
457 2
458
		if (isset($modSettings['recent_frontpage']))
459
		{
460
			$this->_num_per_page = $modSettings['recent_frontpage'];
461
		}
462 2
463
		// Figure out what action to do, thinking, thinking ...
464
		$this->action_recent();
465
	}
466
}
467