From 61ad7a262e9ec7a1d2bf390f7c14aca1d8acd806 Mon Sep 17 00:00:00 2001 From: Spuds Date: Mon, 29 Apr 2024 07:51:28 -0500 Subject: [PATCH 1/2] ! mark notifications as read when you read unread messages close #1451 --- sources/ElkArte/Controller/Display.php | 87 +++++++++++------ sources/ElkArte/Mentions/Mentioning.php | 14 +-- sources/subs/Mentions.subs.php | 118 +++++++++++++++++++++++- 3 files changed, 177 insertions(+), 42 deletions(-) diff --git a/sources/ElkArte/Controller/Display.php b/sources/ElkArte/Controller/Display.php index 378b533bd3..43110335bb 100644 --- a/sources/ElkArte/Controller/Display.php +++ b/sources/ElkArte/Controller/Display.php @@ -125,11 +125,11 @@ public function action_display() // The start isn't a number; it's information about what to do, where to go. $this->makeStartAdjustments($total_visible_posts); - // Censor the title... + // Censor the subject... $this->topicinfo['subject'] = censor($this->topicinfo['subject']); // Allow addons access to the topicinfo array - call_integration_hook('integrate_display_topic', array($this->topicinfo)); + call_integration_hook('integrate_display_topic', [$this->topicinfo]); // If all is set, figure out what needs to be done $can_show_all = $this->getCanShowAll($total_visible_posts); @@ -159,7 +159,7 @@ public function action_display() 'offset' => $limit, ]; - // Get each post and poster in this topic. + // Get each post and poster on this topic page. $topic_details = getTopicsPostsAndPoster($this->topicinfo['id_topic'], $limit_settings, $ascending); $messages = $topic_details['messages']; @@ -175,7 +175,7 @@ public function action_display() $context['first_message'] = 0; $context['first_new_message'] = false; - call_integration_hook('integrate_display_message_list', array(&$messages, &$all_posters)); + call_integration_hook('integrate_display_message_list', [&$messages, &$all_posters]); // If there _are_ messages here... (probably an error otherwise :!) if (!empty($messages)) @@ -183,6 +183,9 @@ public function action_display() // Mark the board as read or not ... calls updateReadNotificationsFor() sets $context['is_marked_notify'] $this->markRead($messages, $board); + // Mark notifications for this member and first seen messages + $this->markNotificationsRead($messages); + $msg_parameters = [ 'message_list' => $messages, 'new_from' => $this->topicinfo['new_from'], @@ -711,6 +714,34 @@ public function warnOldTopic() return false; } + /** + * Marks notifications read for specified messages + * + * @param array $messages An array of message ids + * @return void + */ + private function markNotificationsRead($messages) + { + global $modSettings; + + $mark_at_msg = max($messages); + if ($mark_at_msg >= $this->topicinfo['id_last_msg']) + { + $mark_at_msg = $modSettings['maxMsgID']; + } + + // If there are new messages "in view", lets mark any notification for them as read + if ($mark_at_msg >= $this->topicinfo['new_from']) + { + require_once(SUBSDIR . '/Mentions.subs.php'); + $filterMessages = array_filter($messages, static function ($element) use ($mark_at_msg) { + return $element >= $mark_at_msg; + }); + + markNotificationsRead($filterMessages); + } + } + /** * Keeps track of where the user is in reading this topic. * @@ -721,37 +752,39 @@ private function markRead($messages, $board) { global $modSettings; - // Guests can't mark topics read or for notifications, just can't sorry. - if ($this->user->is_guest === false && !empty($messages)) + // Guests can't mark topics read or for notifications, just can't, sorry. + if ($this->user->is_guest || empty($messages)) { - $boardseen = isset($this->_req->query->boardseen); - - $mark_at_msg = max($messages); - if ($mark_at_msg >= $this->topicinfo['id_last_msg']) - { - $mark_at_msg = $modSettings['maxMsgID']; - } + return; + } - if ($mark_at_msg >= $this->topicinfo['new_from']) - { - markTopicsRead(array($this->user->id, $this->topicinfo['id_topic'], $mark_at_msg, $this->topicinfo['unwatched']), $this->topicinfo['new_from'] !== 0); - $numNewTopics = getUnreadCountSince($board, empty($_SESSION['id_msg_last_visit']) ? 0 : $_SESSION['id_msg_last_visit']); + $boardseen = isset($this->_req->query->boardseen); - if (empty($numNewTopics)) - { - $boardseen = true; - } - } + $mark_at_msg = max($messages); + if ($mark_at_msg >= $this->topicinfo['id_last_msg']) + { + $mark_at_msg = $modSettings['maxMsgID']; + } - updateReadNotificationsFor($this->topicinfo['id_topic'], $board); + if ($mark_at_msg >= $this->topicinfo['new_from']) + { + markTopicsRead([$this->user->id, $this->topicinfo['id_topic'], $mark_at_msg, $this->topicinfo['unwatched']], $this->topicinfo['new_from'] !== 0); + $numNewTopics = getUnreadCountSince($board, empty($_SESSION['id_msg_last_visit']) ? 0 : $_SESSION['id_msg_last_visit']); - // Mark board as seen if we came using last post link from BoardIndex. (or other places...) - if ($boardseen) + if (empty($numNewTopics)) { - require_once(SUBSDIR . '/Boards.subs.php'); - markBoardsRead($board, false, false); + $boardseen = true; } } + + updateReadNotificationsFor($this->topicinfo['id_topic'], $board); + + // Mark board as seen if we came using last post link from BoardIndex. (or other places...) + if ($boardseen) + { + require_once(SUBSDIR . '/Boards.subs.php'); + markBoardsRead($board, false, false); + } } /** diff --git a/sources/ElkArte/Mentions/Mentioning.php b/sources/ElkArte/Mentions/Mentioning.php index fd607fbc6a..2e7fdee0f7 100644 --- a/sources/ElkArte/Mentions/Mentioning.php +++ b/sources/ElkArte/Mentions/Mentioning.php @@ -297,22 +297,16 @@ protected function _getAccessible($mention_ids, $action) * - note that delete is a "soft-delete" because otherwise anyway we have to remember * - when a user was already mentioned for a certain message (e.g. in case of editing) * - * @param int|int[] $id_mentions the mention id in the db + * @param int|int[] $id_mentions the mention(s) id in the db * @param string $status status to update, 'new', 'read', 'deleted', 'unapproved' * @return bool if successfully changed or not * @package Mentions */ protected function _changeStatus($id_mentions, $status = 'read') { - $success = $this->_db->query('', ' - UPDATE {db_prefix}log_mentions - SET status = {int:status} - WHERE id_mention IN ({array_int:id_mentions})', - [ - 'id_mentions' => (array) $id_mentions, - 'status' => $this->_known_status[$status], - ] - )->affected_rows() !== 0; + require_once(SUBSDIR . '/Mentions.subs.php'); + + $success = changeStatus($id_mentions, $this->user->id, $this->_known_status[$status], false); // Update the top level mentions count if ($success) diff --git a/sources/subs/Mentions.subs.php b/sources/subs/Mentions.subs.php index 96270585cf..d2c32633f8 100644 --- a/sources/subs/Mentions.subs.php +++ b/sources/subs/Mentions.subs.php @@ -187,7 +187,8 @@ function toggleMentionsApproval($msgs, $approved) // Update the mentions menu count for the members that have this message $status = $approved ? 0 : 3; $db->fetchQuery(' - SELECT id_member, status + SELECT + id_member, status FROM {db_prefix}log_mentions WHERE id_target IN ({array_int:messages})', array( @@ -437,11 +438,11 @@ function getNewMentions($id_member, $timestamp) * Get the available mention types for a user. * * @param int|null $user The user ID. If null, User::$info->id will be used. - * @param string $type The type of mentions. user will return only those that the user has enabled and set - * as notification. If not user, returns the system level enabled mentions. + * @param string $type The type of mentions. "user" will return only those that the user has enabled and set + * as on site notification. * - * By default, will filter out notification types with a method set to none, e.g. the user had disable that - * type of mention. Use type=all to return everything, or type=notification to return only those + * By default, will filter out notification types with a method set to none, e.g. the user has disabled that + * type of mention. Use type "system" to return everything, or type "user" to return only those * that they want on-site notifications. * * @return array The available mention types. @@ -487,3 +488,110 @@ function getMentionTypes($user, $type = 'user') sort($enabled); return $enabled; } + +/** + * Marks a set of notifications as read. + * + * Intended to be called when viewing a topic page. + * + * @param array $messages + */ +function markNotificationsRead($messages) +{ + // Guests can't mark notifications + if (User::$info->is_guest || empty($messages)) + { + return; + } + + // These are the types associated with messages (where the id_target is a msg_id) + $mentionTypes = ['mentionmem', 'likemsg', 'rlikemsg', 'quotedmem', 'watchedtopic', 'watchedboard']; + $messages = is_array($messages) ? $messages : [$messages]; + $changes = []; + + // Find unread notifications for this group of messages for this member + $db = database(); + $db->fetchQuery(' + SELECT + id_mention + FROM {db_prefix}log_mentions + WHERE status = {int:status} + AND id_member = {int:member} + AND id_target IN ({array_int:targets}) + AND mention_type IN ({array_string:mention_types})', + [ + 'status' => 0, + 'member' => User::$info->id, + 'targets' => is_array($messages) ? $messages : [$messages], + 'mention_types' => $mentionTypes, + ] + )->fetch_callback( + function ($row) use (&$changes) { + $changes[] = (int) $row['id_mention']; + }); + + if (!empty($changes)) + { + changeStatus(array_unique($changes), User::$info->id); + } +} + +/** + * Change the status of mentions + * + * Updates the status of mentions in the database. Also updates the mentions count for the member. + * + * - Can be used to mark as read, new, deleted, etc a group of mention id's + * - Note that delete is a "soft-delete" because otherwise anyway we have to remember + * - When a user was already mentioned for a certain message (e.g. in case of editing) + * + * @param int|array $id_mentions The id(s) of the mentions to update + * @param int $member_id The id of the member + * @param int $status The new status for the mentions (default: 1) + * @param bool $update Whether to update the mentions count (default: true) + * + * @return bool Returns true if the update was successful, false otherwise + */ +function changeStatus($id_mentions, $member_id, $status = 1, $update = true) +{ + $db = database(); + + $id_mentions = is_array($id_mentions) ? $id_mentions : [$id_mentions]; + $status = $status ?? 1; + + $success = $db->query('', ' + UPDATE {db_prefix}log_mentions + SET status = {int:status} + WHERE id_mention IN ({array_int:id_mentions})', + [ + 'id_mentions' => $id_mentions, + 'status' => $status, + ] + )->affected_rows() !== 0; + + // Update the mentions count + if ($success && $update) + { + $number = count($id_mentions); + require_once(SUBSDIR . '/Members.subs.php'); + + // Increase the count by 1 + if ($number === 1 && $status === 0) + { + updateMemberData($member_id, ['mentions' => '+']); + return true; + } + + // Mark as read we decrease the count by 1 + if ($number === 1 && $status === 1) + { + updateMemberData($member_id, ['mentions' => '-']); + return true; + } + + // A full recount is required + countUserMentions(false, '', $member_id); + } + + return $success; +} From 8e3750d6d9ce61df9a350fed1dd6f52b220a31b0 Mon Sep 17 00:00:00 2001 From: Spuds Date: Mon, 29 Apr 2024 08:12:33 -0500 Subject: [PATCH 2/2] ! moved to top of function --- sources/subs/Mentions.subs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/subs/Mentions.subs.php b/sources/subs/Mentions.subs.php index d2c32633f8..fb84b0440c 100644 --- a/sources/subs/Mentions.subs.php +++ b/sources/subs/Mentions.subs.php @@ -522,7 +522,7 @@ function markNotificationsRead($messages) [ 'status' => 0, 'member' => User::$info->id, - 'targets' => is_array($messages) ? $messages : [$messages], + 'targets' => $messages, 'mention_types' => $mentionTypes, ] )->fetch_callback(