Completed
Pull Request — development (#3620)
by Emanuele
07:38 queued 07:38
created

Recent::_likesJS()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

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