Completed
Push — patch_1-1-4 ( 3f780f...826343 )
by Emanuele
25:17 queued 11:40
created

Recent_Controller   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 472
Duplicated Lines 3.81 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 61.93%

Importance

Changes 0
Metric Value
dl 18
loc 472
rs 6.4799
c 0
b 0
f 0
ccs 122
cts 197
cp 0.6193
wmc 54
lcom 1
cbo 4

13 Methods

Rating   Name   Duplication   Size   Complexity  
A frontPageHook() 10 10 1
A frontPageOptions() 0 21 1
A pre_dispatch() 0 23 1
A action_index() 0 5 1
A action_recent_front() 0 12 2
F action_recent() 0 78 21
B _addButtons() 0 60 8
B _recentPostsCategory() 2 39 6
A _recentPostsBoards() 4 28 4
A _recentPostsBoard() 2 16 3
A _recentPostsAll() 0 11 3
A _getLikes() 0 22 2
A _likesJS() 0 31 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Recent_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Recent_Controller, and based on these observations, apply Extract Interface, too.

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