Skip to content

[2.1.8]: Birthday alerts: unread counter not updated for most recipients, and clicking the alert never marks it as read #9792

Open

Description

@smf-prdx

Basic Information

Birthday alerts have two independent bugs. The first one is already fixed on release-3.0; the second one is still present there.

1. Unread alert counter only updated for the last language group (2.1 only)

In Sources/tasks/Birthday-Notify.php, members whose birthday is today are grouped by lngfile and processed in foreach ($birthdays as $lang => $members). The counter update runs after that loop:

if (!empty($alert_rows))
{
        $smcFunc['db_insert'](/* ... */ $alert_rows /* ... */);

        updateMemberData(array_keys($members), array('alerts' => '+'));
}

At that point $members only holds the members of the last language group processed. As a result:

  • Members in any other language group get the alert row, but members.alerts is not incremented. The alert is invisible, since the alerts menu shows no count. It only appears when another alert arrives and the member opens the dropdown, possibly days later.
  • Every member in the last language group gets alerts + 1, even those who received no alert (birthday preference set to email only or disabled).

On release-3.0 this is already fixed: Tasks/Birthday_Notify.php passes the rows to Alert::createBatch(), which recounts unread alerts for each member who got one. A fix is still needed for 2.1, for example collecting the IDs of members who actually got an alert and incrementing only those.

2. Alerts without a target link are never marked as read when clicked (2.1 and 3.0)

Clicking an alert in the dropdown opens ?action=profile;area=showalerts;alert=<id>. shle-View.php redirects to the alerts list before marking the alert as read when thereis no target_href:

// In case it failed to determine this alert's link
if (empty($alert['target_href']))
        redirectexit('action=profile;area=showalerts');

// Mark the alert as read while we're at it.
alert_mark($memID, $alert_id, 1);

Birthday alerts always take this path, because they are inserted with content_id = 0 an them sends the user to their alerts list and leaves the alert unread, however many timesthey click. The only ways to clear it are the per-alert "Mark read" button in Profile → Alerts or "Mark all as read". The same applies to any third-party alert type that doesn't set content_link.

The same logic is still present on release-3.0 in Sources/Actions/Profile/ShowAlerts.php (around line 59). Suggested fix: call alert_mark() / Alert::mark() before the fallback redirect. Optionally,
give birthday alerts a content_link, for example the member's own profile.

Environment: SMF 2.1.8, PHP 8.3, MySQL 8.0. Observed on a production forum: a member withrence saw their birthday alert a week late, and could not dismiss it by clicking it.

Steps to reproduce

Bug 1 (counter, 2.1 only):

  1. Create three members whose birthday is today, each with a different language (lngfile), e.g. english, spanish_es and empty (forum default).
  2. Set the "When it's my birthday" notification to "alert" for the spanish_es member only. Leave the other two on the default (email only).
  3. Run the birthdayemails scheduled task.
  4. Check smf_user_alerts and smf_members.alerts:
    • The spanish_es member has a new content_type = 'birthday' alert but alerts = 0, so the alerts menu shows no count.
    • The member in the last language group processed (here the empty-lngfile one) has alerts = 1 and no alert row.

Bug 2 (mark as read, 2.1 and 3.0):

  1. Set a member's "When it's my birthday" notification to "alert" and make today their birthday.
  2. Run the birthdayemails scheduled task.
  3. Log in as that member, open the alerts dropdown and click the birthday alert.
  4. You are redirected to Profile → Alerts, and the birthday alert is still unread. Repeating the click changes nothing.

Expected result

Bug 1: After the birthday task runs, members.alerts is incremented exactly for the members who received a birthday alert, whatever their language. The alerts menu shows the new alert's count right away. Members who received no alert (email-only or disabled preference) keep their counter unchanged.

Bug 2: Clicking a birthday alert in the alerts dropdown marks it as read, even though it has no target link. It no longer shows as unread in the dropdown or in Profile → Alerts.

Actual result

Bug 1: Only the members in the last language group processed get alerts + 1, including those who received no alert. Members in any other language group get the alert row but their counter stays at 0, so the alerts menu shows nothing. The alert only becomes visible when an unrelated alert arrives and the member opens the dropdown, possibly days later (a week, in the case we observed).

Bug 2: Clicking the birthday alert redirects to Profile → Alerts without marking it as read. It stays unread in the dropdown however many times it is clicked. The only ways to clear it are the per-alert "Mark read" button in Profile → Alerts or "Mark all as read".

Version/Git revision

2.1.8

Database Engine

MySQL

Database Version

8.0.46-0ubuntu0.24.04.4

PHP Version

8.3.6-0ubuntu0.24.04.13

Logs

Neither bug writes anything to the SMF error log or the PHP error log. Database state observed in production for the affected member (IDs anonymised):

smf_user_alerts (birthday alert, created by the scheduled task on the member's birthday):
  id_alert | alert_time          | content_type | content_id | content_action | is_read
  194843   | 2026-10-15 01:20:01 | birthday     | 0          | msg            | 0   (until the member marked it read manually on 2026-10-22)

smf_members.alerts for that member stayed at 0 from 2026-10-15 to 2026-10-22, while the alert above was unread.

Members with a birthday that day, grouped by lngfile: `english` (1), `spanish_es` (4, including the only one with an alert preference), `''` (1). The affected member was in the `spanish_es` group, which was not the last group processed.

Additional Information

  • Checked against current branches: bug 1 is fixed on release-3.0 (Alert::createBatch() recounts per member); bug 2 is still present on release-3.0 (Sources/Actions/Profile/ShowAlerts.php).
  • Workaround for users: "Mark all as read" in the alerts dropdown, or the per-alert "Mark read" button in Profile → Alerts.

Activity

  1. changed the title [-][2.1.8]: Els c[/-] [+][2.1.8]: Birthday alerts: unread counter not updated for most recipients, and clicking the alert never marks it as read[/+] on Oct 22, 2026
  2. smf-prdx commented on Oct 27, 2026

    @smf-prdx
    Author

    Only if it helps, this is the fix I came up with for both bugs, against a vanilla 2.1.8 copy. I checked that both patched files pass php -l and that the diff applies cleanly with patch --dry-run.

    --- a/Sources/tasks/Birthday-Notify.php
    +++ b/Sources/tasks/Birthday-Notify.php
    @@ -71,6 +71,9 @@
                            // We need this for sendmail and AddMailQueue
                            require_once($sourcedir . '/Subs-Post.php');
    
    +                       $alert_rows = array();
    +                       $alerted_members = array();
    +
                            // Send out the greetings!
                            foreach ($birthdays as $lang => $members)
                            {
    @@ -106,6 +109,7 @@
                                                            'is_read' => 0,
                                                            'extra' => $smcFunc['json_encode'](array('happy_birthday' => $alertdata['body'])),
                                                    );
    +                                               $alerted_members[] = $member_id;
                                            }
    
                                            if ($pref & self::RECEIVE_NOTIFY_EMAIL)
    @@ -132,7 +136,7 @@
                                            array()
                                    );
    
    -                               updateMemberData(array_keys($members), array('alerts' => '+'));
    +                               updateMemberData($alerted_members, array('alerts' => '+'));
                            }
                    }
    
    --- a/Sources/Profile-View.php
    +++ b/Sources/Profile-View.php
    @@ -682,13 +682,13 @@
                     * the value for $alert['target_href'].
                     */
    
    +               // Mark the alert as read while we're at it.
    +               alert_mark($memID, $alert_id, 1);
    +
                    // In case it failed to determine this alert's link
                    if (empty($alert['target_href']))
                            redirectexit('action=profile;area=showalerts');
    
    -               // Mark the alert as read while we're at it.
    -               alert_mark($memID, $alert_id, 1);
    -
                    // Take the user to the content
                    redirectexit($alert['target_href']);
            }

    What the changes do:

    • Bug 1, Sources/tasks/Birthday-Notify.php: while building $alert_rows, the member IDs that actually get an alert are collected in $alerted_members. Only those get alerts + 1, whatever their language group. Before, the counter was updated with array_keys($members) after the loop, so only the last language group was updated, including members who had no alert. $alert_rows is now also initialised before the loop instead of being created implicitly on first use.
    • Bug 2, Sources/Profile-View.php: alert_mark() now runs before the "no target link" fallback redirect, so clicking any alert marks it as read, even if it has nowhere to go (birthday alerts always have content_id = 0 and no content_link). alert_mark() filters by id_member, so an invalid ID or another member's alert ID still changes nothing.
    • release-3.0: bug 1 needs no change there, because Alert::createBatch() already recounts unread alerts per member. Bug 2 would need the same reordering in Sources/Actions/Profile/ShowAlerts.php: call Alert::mark() before the empty($alert->target_href) redirect.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions