[3.0] Keep Stringable arguments instead of dropping them - #9409
Merged
Sesquipedalian merged 2 commits intoAug 14, 2026
Merged
Conversation
formatMessage() hands its arguments to intl as
->format(array_filter($args, 'is_scalar'))
which throws away objects. That is right for an array or a plain object,
which intl cannot use, but a Stringable converts perfectly well, and dropping
it means the argument never arrives and ICU leaves the placeholder sitting in
the output.
The member list is where this shows: Admin -> Members lists an SMF\IP object
for member_ip, so the IP column rendered the literal text {member_ip}, inside
a link to ?action=trackip;searchip={member_ip}. It now reads 172.19.0.1 and
links to that address.
Stringables are converted before the MessageFormat escaping just above, so
one containing a brace or an apostrophe is protected the same as any other
string, and the non-intl fallback path gets the same value.
Signed-off-by: Mathias Papenbrock <mathiaspapealbert@hotmail.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Closed
Sesquipedalian
requested changes
Aug 14, 2026
Sesquipedalian
approved these changes
Aug 14, 2026
live627
pushed a commit
that referenced
this pull request
Aug 31, 2026
A second sweep of the bug fixes now on release-3.0, in the same spirit as #9511: ask of each one whether the suite can reach it, and write a test where it can. Everything merged since that sweep was looked at, along with the backlog that landed in one batch on the 29th and 30th. Most of it is templates, JavaScript, or PHP that wants Db::$db or User::$me. Four fixes do not. #9484 made SMF\Unicode\SpoofDetector::checkReservedName() split the admin's list on the two characters backslash and n as well as on a real newline. The installer writes the default list with the separators spelled out that way, so splitting on newlines alone gave one long name nobody would type and every reserved name was free to register. #9409 made SMF\Localization\MessageFormatter::formatMessage() flatten a \Stringable argument to its string value. The class skips any argument that is not already a string and hands the intl formatter only the scalar ones, so an object argument reached neither and the member was shown the placeholder. #9453 made SMF\PageIndex remember, across __toString(), that the start value it was handed was out of bounds. fixStart() records that as a side effect of clamping, and __toString() called it again on a value already clamped, so the verdict was always thrown away: page 1 came out as plain text rather than a link, with a "next page" link beside it. #9440 and #9442 both concern a gallery avatar, which is stored as a path under the avatars directory rather than as a URL. Read as a URL, it was worked back to a file from the URL's path, which lands outside the avatar directories; and on a forum at the root of its domain that path is null, so stripping the board URL off it threw a TypeError on every page the member appeared on. Each set was run against the code as it was before its fix, by checking out the single source file at the commit before the merge: - SpoofDetector.php before #9484: one failure, the installer's list. - MessageFormatter.php before #9409: three failures, all the \Stringable cases. The plain string, the number and the no-placeholder message pass either side. - PageIndex.php before #9453: two failures. The four tests covering an ordinary start pass either side, which is what makes them the control. - Avatar.php before #9440: five of six fail, the root-of-domain cases with the TypeError and the subdirectory ones by falling through to default.png. With #9440 but not #9442, four still fail: every gallery avatar becomes the default image. 202 tests, 295 assertions, still under a second. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Admin → Members prints the literal text
{member_ip}in the IP column, inside a link to?action=trackip;searchip={member_ip}.Localization\MessageFormatter::formatMessage()hands its arguments to intl like this:array_filter($args, 'is_scalar')throws away every object. That is the right call for an array or a plain object — intl fatals onstdClasswith "could not be converted to string" — but aStringableconverts perfectly well. Dropping it means the argument never arrives, and ICU leaves the placeholder in the output rather than substituting anything.Actions/Admin/Members.php::list_getMembers()puts anSMF\IPobject in the row:and
SMF\IP implements \Stringable. So the column'sformat_textnever got its value.Stringables are now converted to strings just before the MessageFormat escaping that already runs over string arguments, so one containing
{,}or'is protected the same as any other string, and the non-intl fallback path further down gets the same value.Checked
On the running forum, Admin → Members:
Ten pages re-rendered afterwards — board index, message index, stats, member list, recent, admin home, member list, error log, moderation centre, profile — no fatals and no other change.
The alternative
Members.phpcould instead declare the column as'member_ip' => true, which makesItemListdohtmlspecialchars((string) $value)before formatting. That fixes this one column and leaves the trap in place for the nextStringablesomeone passes, so I fixed the formatter. Happy to do it the other way if you would rather keepformatMessage()strict.Found by sweeping every rendered page of a stock forum for unsubstituted
{placeholders}, alongside #9407 and #9408.Issues References (Fixes|Related|Closes)
Related to #7933