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
![]() |
|||
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
The property
posts does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||
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 |