Repository navigation
Description
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.alertsis 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):
- Create three members whose birthday is today, each with a different language (
lngfile), e.g.english,spanish_esand empty (forum default). - Set the "When it's my birthday" notification to "alert" for the
spanish_esmember only. Leave the other two on the default (email only). - Run the
birthdayemailsscheduled task. - Check
smf_user_alertsandsmf_members.alerts:- The
spanish_esmember has a newcontent_type = 'birthday'alert butalerts = 0, so the alerts menu shows no count. - The member in the last language group processed (here the empty-
lngfileone) hasalerts = 1and no alert row.
- The
Bug 2 (mark as read, 2.1 and 3.0):
- Set a member's "When it's my birthday" notification to "alert" and make today their birthday.
- Run the
birthdayemailsscheduled task. - Log in as that member, open the alerts dropdown and click the birthday alert.
- 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 onrelease-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
[-][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[/+]smf-prdx commentedon Oct 27, 2026
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 -land that the diff applies cleanly withpatch --dry-run.What the changes do:
Sources/tasks/Birthday-Notify.php: while building$alert_rows, the member IDs that actually get an alert are collected in$alerted_members. Only those getalerts + 1, whatever their language group. Before, the counter was updated witharray_keys($members)after the loop, so only the last language group was updated, including members who had no alert.$alert_rowsis now also initialised before the loop instead of being created implicitly on first use.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 havecontent_id = 0and nocontent_link).alert_mark()filters byid_member, so an invalid ID or another member's alert ID still changes nothing.release-3.0: bug 1 needs no change there, becauseAlert::createBatch()already recounts unread alerts per member. Bug 2 would need the same reordering inSources/Actions/Profile/ShowAlerts.php: callAlert::mark()before theempty($alert->target_href)redirect.