Basic Information
Config::updateModSettings() never removes a setting. Passing null as the value is the documented way to delete one, and every caller that does so silently leaves the row in place.
Sources/Config.php:1293-1304:
// Go check if there is any setting to be removed.
$to_remove = array_filter($change_array, fn($setting) => $setting === null);
$change_array = array_diff_key($change_array, $to_remove);
// Proceed with the deletion.
if (!empty($to_remove)) {
Db\DatabaseApi::$db->query(
'DELETE FROM {db_prefix}settings
WHERE variable IN ({array_string:remove})',
[
'remove' => $to_remove,
],
);
}
array_filter() keeps the keys, so $to_remove is ['some_setting' => null, ...] — the names are the keys and every value is null. {array_string:remove} binds the values, so the statement that reaches the database is:
DELETE FROM smf_settings WHERE variable IN ('')
It matches nothing, and nothing is deleted. array_keys($to_remove) is what the placeholder wants.
The removal half of $change_array is filtered out at line 1294, too, so the setting is not written either — a caller passing null gets neither the delete it asked for nor an update, and no error.
Where it shows. v2_1\SettingsUpdate is the largest caller: it lists 22 settings in $removedSettings, sets each to null, and hands them to Config::updateModSettings(). None of them are removed. Upgrading a 2.1 forum that carries time_offset leaves that setting in smf_settings afterwards, which is how I noticed.
time_offset is the visible one because a 2.1 install writes none of the other 21 — they are 2.0-era settings, so a 2.0 → 3.0 upgrade is where the rest of them survive.
Steps to reproduce
- From any SMF 3.0 install, write a setting and then ask for it back:
Config::updateModSettings(['probe_removeme' => 'x']);
Config::updateModSettings(['probe_removeme' => null]);
SELECT value FROM smf_settings WHERE variable = 'probe_removeme';
Or, on the upgrade path: upgrade a 2.1 database whose smf_settings contains time_offset, then look for time_offset in smf_settings afterwards.
Expected result
The row is gone.
Actual result
The row is still there, with its original value.
after writing it: present ('x')
after nulling it: present ('x')
the bound array: array (
'probe_removeme' => NULL,
)
the statement: DELETE FROM `smf`.smf_settings WHERE variable IN ('')
Version/Git revision
3.0 Alpha 4, release-3.0 at 7a934c8
Database Engine
All
Database Version
Reproduced on MySQL 8.4. The defect is in Config.php, which is shared, so nothing about it is engine-specific.
PHP Version
8.4.24
Logs
Nothing is logged. The query is valid SQL and succeeds, having matched no rows.
Additional Information
Found while widening the time zone coverage of the 2.1 upgrade baseline in #9330, which is what put time_offset in front of that migration in the first place.
Basic Information
Config::updateModSettings()never removes a setting. Passingnullas the value is the documented way to delete one, and every caller that does so silently leaves the row in place.Sources/Config.php:1293-1304:array_filter()keeps the keys, so$to_removeis['some_setting' => null, ...]— the names are the keys and every value isnull.{array_string:remove}binds the values, so the statement that reaches the database is:It matches nothing, and nothing is deleted.
array_keys($to_remove)is what the placeholder wants.The removal half of
$change_arrayis filtered out at line 1294, too, so the setting is not written either — a caller passingnullgets neither the delete it asked for nor an update, and no error.Where it shows.
v2_1\SettingsUpdateis the largest caller: it lists 22 settings in$removedSettings, sets each tonull, and hands them toConfig::updateModSettings(). None of them are removed. Upgrading a 2.1 forum that carriestime_offsetleaves that setting insmf_settingsafterwards, which is how I noticed.time_offsetis the visible one because a 2.1 install writes none of the other 21 — they are 2.0-era settings, so a 2.0 → 3.0 upgrade is where the rest of them survive.Steps to reproduce
SELECT value FROM smf_settings WHERE variable = 'probe_removeme';Or, on the upgrade path: upgrade a 2.1 database whose
smf_settingscontainstime_offset, then look fortime_offsetinsmf_settingsafterwards.Expected result
The row is gone.
Actual result
The row is still there, with its original value.
Version/Git revision
3.0 Alpha 4,
release-3.0at 7a934c8Database Engine
All
Database Version
Reproduced on MySQL 8.4. The defect is in
Config.php, which is shared, so nothing about it is engine-specific.PHP Version
8.4.24
Logs
Nothing is logged. The query is valid SQL and succeeds, having matched no rows.
Additional Information
Found while widening the time zone coverage of the 2.1 upgrade baseline in #9330, which is what put
time_offsetin front of that migration in the first place.