elkarte /
Elkarte
| 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 = [ |
||
| 109 | 'own' => [ |
||
| 110 | 'post_reply_own' => 'can_reply', |
||
| 111 | 'delete_own' => 'can_delete', |
||
| 112 | ], |
||
| 113 | 'any' => [ |
||
| 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(): void |
||
| 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
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'] = []; |
||
| 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.
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(): array |
||
| 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([], $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 = [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(): void |
|
| 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, []); |
|
| 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 = [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(): void |
||
| 311 | { |
||
| 312 | global $modSettings, $board; |
||
| 313 | |||
| 314 | $board_data = fetchBoardsInfo(['boards' => $board], ['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 = [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(): void |
||
| 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): array |
||
| 353 | { |
||
| 354 | global $modSettings; |
||
| 355 | |||
| 356 | $likes = []; |
||
| 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): array |
||
| 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(): void |
||
| 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 |