From 951d397a1011ec4084761214d612bdc56e308a2d Mon Sep 17 00:00:00 2001 From: albertlast Date: Wed, 29 Jul 2026 18:49:21 +0200 Subject: [PATCH 1/2] Stops selecting the dropped time_offset column in CreatePost_Notify The members table lost its time_offset column in 3.0, in the DropTimeOffset migration, and User::$time_offset became a virtual property derived from the member's time zone. User::setProperties() even lists time_offset among the "obsolete data" it ignores. CreatePost_Notify still asked the database for it: mem.smiley_set, mem.time_format, mem.time_offset, mem.timezone, so the query fails outright with "column mem.time_offset does not exist", and the task dies on the fetch_assoc() of a false result. The task is never marked done, so it is retried on later page loads and takes down whichever request happens to run it: SMF\Db\APIs\PostgreSQL::fetch_assoc(): Argument #1 ($result) must be of type object, false given Nobody watching a board or topic receives their notification either. Derives the offset from mem.timezone, which the query already selects, the same way User::$time_offset does. Also keys the parsed-message cache by time zone rather than by offset, and drops an (int) cast that truncated the offsets of half-hour and quarter-hour time zones. Co-Authored-By: Claude Opus 5 --- Sources/Tasks/CreatePost_Notify.php | 30 ++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Sources/Tasks/CreatePost_Notify.php b/Sources/Tasks/CreatePost_Notify.php index 060170f88a..e1a27d95a3 100644 --- a/Sources/Tasks/CreatePost_Notify.php +++ b/Sources/Tasks/CreatePost_Notify.php @@ -178,7 +178,7 @@ public function execute(): bool ln.id_member, ln.id_board, ln.id_topic, ln.sent, mem.email_address, mem.lngfile, mem.pm_ignore_list, mem.id_group, mem.id_post_group, mem.additional_groups, - mem.smiley_set, mem.time_format, mem.time_offset, mem.timezone, + mem.smiley_set, mem.time_format, mem.timezone, t.id_member_started, t.id_member_updated FROM {db_prefix}log_notify AS ln INNER JOIN {db_prefix}members AS mem ON (ln.id_member = mem.id_member) @@ -383,6 +383,30 @@ public function execute(): bool * Internal methods ******************/ + /** + * Gets a member's time offset from the forum's default time zone. + * + * The members table no longer has a time_offset column, so this derives the + * offset from the member's time zone, exactly as User::$time_offset does. + * + * @param string $timezone The member's time zone identifier. + * @return float The offset in hours. + */ + protected static function getTimeOffset(string $timezone): float + { + if ($timezone === '') { + return 0; + } + + $now = new \DateTime('now'); + $default = Config::$modSettings['default_timezone'] ?? date_default_timezone_get(); + + return ( + (new \DateTimeZone($timezone))->getOffset($now) + - (new \DateTimeZone($default))->getOffset($now) + ) / 3600; + } + /** * Update an alert if a message was updated since the alert was created. * @@ -574,11 +598,11 @@ protected function handleWatchedNotifications(): void } // Censor and parse BBC in the receiver's localization. Don't repeat unnecessarily. - $localization = implode('|', [$member_data['lngfile'], $member_data['time_offset'], $member_data['time_format']]); + $localization = implode('|', [$member_data['lngfile'], $member_data['timezone'], $member_data['time_format']]); if (empty($parsed_message[$localization])) { // Use the target member's localization settings. - Parser::$time_offset = (int) $member_data['time_offset']; + Parser::$time_offset = self::getTimeOffset($member_data['timezone']); Parser::$time_format = $member_data['time_format']; Parser::$smiley_set = $member_data['smiley_set']; Parser::$locale = Lang::getLocaleFromLanguageName($member_data['lngfile']) ?? Lang::getTxt('lang_locale', file: 'General', lang: $member_data['lngfile']); From 7dbe31fca75cac14f8c205f0c0281f55843ace09 Mon Sep 17 00:00:00 2001 From: albertlast Date: Wed, 29 Jul 2026 19:18:29 +0200 Subject: [PATCH 2/2] Moves getTimeOffset() into the internal static methods section PHP-CS-Fixer sorts class members, and a static method does not belong in the non-static "Internal methods" group. No change to the code itself. Co-Authored-By: Claude Opus 5 --- Sources/Tasks/CreatePost_Notify.php | 52 ++++++++++++++++------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/Sources/Tasks/CreatePost_Notify.php b/Sources/Tasks/CreatePost_Notify.php index e1a27d95a3..fd45628bf1 100644 --- a/Sources/Tasks/CreatePost_Notify.php +++ b/Sources/Tasks/CreatePost_Notify.php @@ -383,30 +383,6 @@ public function execute(): bool * Internal methods ******************/ - /** - * Gets a member's time offset from the forum's default time zone. - * - * The members table no longer has a time_offset column, so this derives the - * offset from the member's time zone, exactly as User::$time_offset does. - * - * @param string $timezone The member's time zone identifier. - * @return float The offset in hours. - */ - protected static function getTimeOffset(string $timezone): float - { - if ($timezone === '') { - return 0; - } - - $now = new \DateTime('now'); - $default = Config::$modSettings['default_timezone'] ?? date_default_timezone_get(); - - return ( - (new \DateTimeZone($timezone))->getOffset($now) - - (new \DateTimeZone($default))->getOffset($now) - ) / 3600; - } - /** * Update an alert if a message was updated since the alert was created. * @@ -833,4 +809,32 @@ protected function handleMentionedNotifications(): void $this->members['done'][] = $member_id; } } + + /************************* + * Internal static methods + *************************/ + + /** + * Gets a member's time offset from the forum's default time zone. + * + * The members table no longer has a time_offset column, so this derives the + * offset from the member's time zone, exactly as User::$time_offset does. + * + * @param string $timezone The member's time zone identifier. + * @return float The offset in hours. + */ + protected static function getTimeOffset(string $timezone): float + { + if ($timezone === '') { + return 0; + } + + $now = new \DateTime('now'); + $default = Config::$modSettings['default_timezone'] ?? date_default_timezone_get(); + + return ( + (new \DateTimeZone($timezone))->getOffset($now) + - (new \DateTimeZone($default))->getOffset($now) + ) / 3600; + } }