Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark notifications as read when you read unread messages #3796

Merged
merged 2 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 60 additions & 27 deletions sources/ElkArte/Controller/Display.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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'];

Expand All @@ -175,14 +175,17 @@ 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))
{
// 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'],
Expand Down Expand Up @@ -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.
*
Expand All @@ -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);
}
}

/**
Expand Down
14 changes: 4 additions & 10 deletions sources/ElkArte/Mentions/Mentioning.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
118 changes: 113 additions & 5 deletions sources/subs/Mentions.subs.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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' => $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;
}
Loading