Skip to content

[3.0] Removes the settings that updateModSettings() is asked to remove - #9532

Merged
Sesquipedalian merged 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/remove-modsettings
Aug 20, 2026
Merged

[3.0] Removes the settings that updateModSettings() is asked to remove#9532
Sesquipedalian merged 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/remove-modsettings

Conversation

@albertlast

@albertlast albertlast commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Description

Passing null as a value is how Config::updateModSettings() is asked to delete a setting, and it never deleted anything.

array_filter() keeps the keys, so $to_remove ends up as a list of names mapped to nulls, and {array_string:remove} binds the values. The statement that actually ran was:

DELETE FROM smf_settings WHERE variable IN ('')

It matches nothing, succeeds, and reports no error. The names are what the placeholder wants, and array_keys($change_array, null, true) finds them in one step.

Two other places had to lose the setting as well, or it comes straight back:

  • Config::$modSettings was never updated. Every other path through this method keeps that copy in step with the database; the removal path did not, so a setting stayed readable for the rest of the request after being deleted.
  • A removal on its own returned before the cache was cleared. Both other exits call CacheApi::put('modSettings', null, 90), but when $change_array holds nothing but nulls it is empty by the time the replace path is reached, so it returns at the empty($replace_array) check first. With caching on, the next request read the setting back out of the cache.

Where it came from, and 2.1

The deletion worked until 1a8e2b3ac ("Reduces redundant code in SMF\Config", 19 May 2026), which replaced the loop that collected the names with array_filter(). Same intent, tidier, but array_filter() preserves keys, so a list of names became a map of names to nulls while the placeholder went on binding the values.

2.1 is affected in a lighter form. updateSettings() there builds $toRemove[] = $k, so the delete itself is correct and this half of the bug is 3.0 only. The other two halves are shared: it does not unset the removed setting from $modSettings either, and it returns at if (empty($replaceArray)) return; before cache_put_data(), so a removal-only call leaves the cache holding the setting. Both are latent there — nothing in 2.1 core passes null to updateSettings(), so only a mod would reach them — which is why this is proposed against 3.0 alone.

Who this affects

Three migrations whose entire job is to drop a setting, plus one search API:

v2_1\SettingsUpdate 22 settings in $removedSettings
v3_0\RemoveCookieTime cookieTime
v3_0\LanguageDirectory removes the old name when renaming one
Search\APIs\Parsed clears search_parsed_status

None of them have ever worked. Upgrading a 2.1 forum leaves time_offset sitting in smf_settings afterwards, which is what led here. time_offset is the visible one only because a 2.1 install never writes the other 21 — they are 2.0-era settings, so a 2.0 → 3.0 upgrade is where the rest of them survive.

Testing

Ten checks against a fresh 3.0 install, on both engines, with file-based caching enabled so the cache half is exercised:

  ok    a setting can be written
  ok    and removed from the table
  ok    and from Config::$modSettings
  ok    three more written
  ok    two removed at once
  ok    the third one, not null, is untouched
  ok    and took its new value
  ok    the cached copy is dropped on a removal
  ok    an ordinary update still works
  ok    and that one is gone too

10 passed, 0 failed

MySQL 8.4 and PostgreSQL 17 both. The same ten against release-3.0 unchanged give 5 passed, 5 failed — every failure one of the three symptoms above, so none of the new checks are decoration.

php-cs-fixer clean.

Issues References (Fixes|Related|Closes)

Fixes #9530

Passing null as a value is how a setting is deleted, and it never deleted
anything. array_filter() keeps the keys, so $to_remove is a list of names
mapped to nulls, and {array_string:remove} binds the values -- so the
statement that ran was:

    DELETE FROM smf_settings WHERE variable IN ('')

which matches nothing and reports no error. The names are what the placeholder
wants, and array_keys($change_array, null, true) is a shorter way of finding
them than filtering and then taking the keys.

Two more places had to lose them or the setting comes straight back. The copy
in Config::$modSettings was never touched, so the removed setting was still
readable for the rest of the request, while every other path through this
method keeps that copy in step. And a call that only removes settings returns
before either of the existing cache invalidations, so with caching on the next
request read the setting back out of the cache.

The callers are three migrations whose entire job is to drop a setting --
v2_1\SettingsUpdate with 22 of them, v3_0\RemoveCookieTime, and
v3_0\LanguageDirectory renaming one -- plus Search\APIs\Parsed clearing its
status. None of them have ever worked. Upgrading a 2.1 forum leaves time_offset
behind in smf_settings, which is what led here.

Fixes SimpleMachines#9530

Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
@albertlast

Copy link
Copy Markdown
Collaborator Author

Confirmed end to end, rather than left as an inference from the unit-level checks.

A real 2.1 → 3.0 upgrade of the committed 2.1.7 baseline from #9330, run twice on MySQL 8.4 against the same dump — once with this branch and once with release-3.0 unchanged. Both runs reached "Upgrade complete", with the two DropTimeOffset defects from #9521 patched locally so they could get that far; that is unrelated to this and stops both runs identically.

Reading smf_settings afterwards:

release-3.0 this branch
time_offsetv2_1\SettingsUpdate still there gone
cookieTimev3_0\RemoveCookieTime still there gone
the other 21 in $removedSettings not present either way not present either way
search_parsed_statusSearch\APIs\Parsed not present either way not present either way

So two settings that a migration exists specifically to delete survive an upgrade today, and both go when this lands. cookieTime I had listed as affected without having watched it happen; now I have.

The rest are absent from both runs for the reason given in the description: a forum installed as 2.1 never writes them. They are 2.0-era settings, so the 2.0 → 3.0 path is where the remaining twenty would show, and I have no 2.0 baseline to run that against.

Nothing else moved. The schema comparison either side of the upgrade is identical between the two runs — 55 differences in the schema and 27 outside it, the same list both times — so this changes what is in settings and nothing more.

@Sesquipedalian
Sesquipedalian merged commit ba5b86e into SimpleMachines:release-3.0 Aug 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[3.0]: Config::updateModSettings() never removes a setting passed as null

3 participants