From 2bf5ae7d4edc3995c43db170ac07a8956f4ce08c Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Wed, 25 Feb 2026 23:04:54 -0700 Subject: [PATCH 01/45] Fixes some calls to JavaScript function doAutoSubmit() Signed-off-by: Jon Stovell --- Themes/default/Admin.template.php | 2 +- Themes/default/ManageNews.template.php | 2 +- Themes/default/ManageSearch.template.php | 2 +- Themes/default/Packages.template.php | 4 ++-- Themes/default/Post.template.php | 3 +-- Themes/default/UpgradeTemplate.php | 12 +++--------- 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Themes/default/Admin.template.php b/Themes/default/Admin.template.php index 2be9b2150e3..bfc0c72d497 100644 --- a/Themes/default/Admin.template.php +++ b/Themes/default/Admin.template.php @@ -1557,7 +1557,7 @@ function template_repair_boards() if (!empty(Utils::$context['redirect_to_recount'])) { echo ' '; } } diff --git a/Themes/default/ManageNews.template.php b/Themes/default/ManageNews.template.php index aa39082a84a..2b629a8e44b 100644 --- a/Themes/default/ManageNews.template.php +++ b/Themes/default/ManageNews.template.php @@ -414,7 +414,7 @@ function template_email_members_send() '; } diff --git a/Themes/default/ManageSearch.template.php b/Themes/default/ManageSearch.template.php index 2925b001021..7997c5e0f92 100644 --- a/Themes/default/ManageSearch.template.php +++ b/Themes/default/ManageSearch.template.php @@ -331,7 +331,7 @@ function template_create_index_progress() '; } diff --git a/Themes/default/Packages.template.php b/Themes/default/Packages.template.php index 6beda4e4d7d..4f0b3ec5581 100644 --- a/Themes/default/Packages.template.php +++ b/Themes/default/Packages.template.php @@ -1776,7 +1776,7 @@ function template_action_permissions() $countDown = 3; echo ' -
+

', Lang::getTxt('package_file_perms_applying', file: 'Packages'), '

'; @@ -1878,6 +1878,6 @@ function template_action_permissions() // Just the countdown stuff echo ' '; } diff --git a/Themes/default/Post.template.php b/Themes/default/Post.template.php index d168725fc12..5ff3312b062 100644 --- a/Themes/default/Post.template.php +++ b/Themes/default/Post.template.php @@ -872,8 +872,7 @@ function template_announcement_send()
'; } diff --git a/Themes/default/UpgradeTemplate.php b/Themes/default/UpgradeTemplate.php index 12fd0b60a12..96cec3ffb1c 100644 --- a/Themes/default/UpgradeTemplate.php +++ b/Themes/default/UpgradeTemplate.php @@ -91,17 +91,11 @@ public static function lower(): void echo '
'; - echo ' - '; - // Are we on a pause? - // !!! TODO: Why? - if (!empty(Maintenance::$context['pause'])) { + if (!empty(Maintenance::$context['pause']) && !Maintenance::$tool->isDebug()) { echo ' - '; } } From ec7afb09e08bbbfbe90b65794278362f10e38f9f Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Wed, 25 Feb 2026 23:50:53 -0700 Subject: [PATCH 02/45] More reliable way to get theme dir in Maintenance::__construct() Signed-off-by: Jon Stovell --- Sources/Maintenance/Maintenance.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Maintenance/Maintenance.php b/Sources/Maintenance/Maintenance.php index 2043f292640..9b1ab1c4ceb 100644 --- a/Sources/Maintenance/Maintenance.php +++ b/Sources/Maintenance/Maintenance.php @@ -228,7 +228,7 @@ class Maintenance public function __construct() { Security::frameOptionsHeader('SAMEORIGIN'); - self::$theme_dir = \dirname(SMF_SETTINGS_FILE) . '/Themes/default'; + self::$theme_dir = self::getBaseDir() . '/Themes/default'; // This might be overwritten by the tool, but we need a default value. self::$context['started'] = (int) TIME_START; From 21191a0ce2ff9de942a707065ec46c18df6566f4 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 1 Mar 2026 12:46:02 -0700 Subject: [PATCH 03/45] Don't mess with redirects or canonical URLs during install or upgrade Signed-off-by: Jon Stovell --- Sources/QueryString.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/QueryString.php b/Sources/QueryString.php index c92a0a3ed52..554130c65cc 100644 --- a/Sources/QueryString.php +++ b/Sources/QueryString.php @@ -639,6 +639,7 @@ protected static function sslRedirect(): void && !Sapi::httpsOn() && str_starts_with($_SERVER['REQUEST_URL'] ?? '', 'http://') && SMF != 'SSI' + && !\defined('SMF_INSTALLING') ) { if (isset($_GET['sslRedirect'])) { ErrorHandler::fatalLang('login_ssl_required', false); @@ -654,7 +655,7 @@ protected static function sslRedirect(): void */ protected static function wwwRedirect(): void { - if (SMF == 'SSI') { + if (SMF == 'SSI' || \defined('SMF_INSTALLING')) { return; } @@ -678,7 +679,7 @@ protected static function wwwRedirect(): void */ protected static function fixUrl(): void { - if (SMF == 'SSI') { + if (SMF == 'SSI' || \defined('SMF_INSTALLING')) { return; } From 6f6fcac859d8dbedfcd8fe4d7b66e18218472ea4 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Wed, 4 Mar 2026 11:20:06 -0700 Subject: [PATCH 04/45] Removes unnecessary param for SMF\Db\Schema\Table::getInitializers() Signed-off-by: Jon Stovell --- Sources/Db/Schema/Table.php | 12 +++++----- Sources/Db/Schema/v3_0/Initialize/Base.php | 22 ++++++++++++++++++- .../Migration/v3_0/PostgreSqlFunctions.php | 2 +- Sources/Maintenance/Tools/Install.php | 2 +- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index 7bd02392126..b45a081a8b5 100644 --- a/Sources/Db/Schema/Table.php +++ b/Sources/Db/Schema/Table.php @@ -636,23 +636,21 @@ final public static function find(string $table_name, string $schema_version): ? } /** - * Gets all known table schemas. + * Gets database initializer queries for the indicated SMF version. * + * @param string $schema_version E.g. 'v3_0'. * @return array All known table schemas. */ - final public static function getInitializers(string $schema_version, string $title): array + final public static function getInitializers(string $schema_version): array { - if (file_exists(__DIR__ . '/' . $schema_version . '/Initialize/' . $title . '.php')) { + if (file_exists(__DIR__ . '/' . $schema_version . '/Initialize/' . Db::$db->title . '.php')) { - $fully_qualified_class_name = __NAMESPACE__ . '\\' . $schema_version . '\\Initialize\\' . $title; + $fully_qualified_class_name = __NAMESPACE__ . '\\' . $schema_version . '\\Initialize\\' . Db::$db->title; if (!class_exists($fully_qualified_class_name)) { return []; } - /** - * @var \SMF\Db\Schema\v3_0\Initialize\Base - */ $intializer = new $fully_qualified_class_name(Db::$db->get_version()); return $intializer->getAll(); diff --git a/Sources/Db/Schema/v3_0/Initialize/Base.php b/Sources/Db/Schema/v3_0/Initialize/Base.php index 498b91235d5..94cc1aed0a7 100644 --- a/Sources/Db/Schema/v3_0/Initialize/Base.php +++ b/Sources/Db/Schema/v3_0/Initialize/Base.php @@ -31,21 +31,41 @@ class Base * Public methods ****************/ - public function __construct(?string $version) + /** + * Constructor. + * + * @param string $version Version of the database engine. + */ + public function __construct(string $version) { $this->version = $version; } + /** + * Gets queries that create custom SQL functions and operators. + * + * @return array SQL queries. + */ public function getAll(): array { return $this->functions() + $this->operators(); } + /** + * Gets queries that create custom SQL functions. + * + * @return array SQL queries. + */ public function functions(): array { return []; } + /** + * Gets queries that create custom SQL operators. + * + * @return array SQL queries. + */ public function operators(): array { return []; diff --git a/Sources/Maintenance/Migration/v3_0/PostgreSqlFunctions.php b/Sources/Maintenance/Migration/v3_0/PostgreSqlFunctions.php index 493a23bf267..bf3ace41f8f 100644 --- a/Sources/Maintenance/Migration/v3_0/PostgreSqlFunctions.php +++ b/Sources/Maintenance/Migration/v3_0/PostgreSqlFunctions.php @@ -49,7 +49,7 @@ public function execute(): bool { $schema_version = substr(__NAMESPACE__, strrpos(__NAMESPACE__, '\\', -1) + 1); - $queries = Table::getInitializers($schema_version, POSTGRE_TITLE); + $queries = Table::getInitializers($schema_version); foreach ($queries as $query) { // Use the upgrade query handler. diff --git a/Sources/Maintenance/Tools/Install.php b/Sources/Maintenance/Tools/Install.php index 29a50f32add..72b77c54b61 100644 --- a/Sources/Maintenance/Tools/Install.php +++ b/Sources/Maintenance/Tools/Install.php @@ -742,7 +742,7 @@ public function databasePopulation(): bool // Some initialization may exist. Db::$db->disableQueryCheck = true; - foreach (Table::getInitializers($this->schema_version, Db::$db->title) as $query) { + foreach (Table::getInitializers($this->schema_version) as $query) { Db::$db->query($query, [ 'security_override' => true, ]); From c535f9e1a9294ad7fafdaddb615f17ad349f1e9d Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Wed, 4 Mar 2026 11:40:21 -0700 Subject: [PATCH 05/45] Removes old comments when rebuilding Settings.php during upgrade Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/ToolsBase.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php index abe6ee09fa6..2b63693f399 100644 --- a/Sources/Maintenance/Tools/ToolsBase.php +++ b/Sources/Maintenance/Tools/ToolsBase.php @@ -585,6 +585,15 @@ public function updateSettingsFile(array $config_vars, ?bool $keep_quotes = null $this->logProgress(Lang::getTxt('log_settings_file_save', ['setting_names' => Lang::sentenceList(array_keys($config_vars))], file: 'Maintenance'), true); } + if ($rebuild) { + // Remove all the existing comments to make the rebuild nice and clean. + Config::safeFileWrite( + file: SMF_SETTINGS_FILE, + data: Config::stripPhpComments(file_get_contents(SMF_SETTINGS_FILE)), + mtime: time(), + ); + } + if (!Config::updateSettingsFile($config_vars, $keep_quotes, $rebuild)) { $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => Lang::getTxt('settings_error', file: 'Maintenance')], file: 'Maintenance')); From 002a872af6055cbc5e5700431efe3177dfa8d4ef Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Thu, 5 Mar 2026 13:32:18 -0700 Subject: [PATCH 06/45] Fixes type error when setting Config::$db_port in upgrader Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Upgrade.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index 5af6b9bb821..3c7aa6af8ed 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -979,18 +979,15 @@ public function upgradeOptions(): bool // If they have a "host:port" setup for the host, split that into separate values // You should never have a : in the hostname if you're not on MySQL, but better safe than sorry if (strpos(Config::$db_server, ':') !== false) { - list(Config::$db_server, Config::$db_port) = explode(':', Config::$db_server); + list(Config::$db_server, $db_port) = explode(':', Config::$db_server); + Config::$db_port = (int) $db_port; $file_settings['db_server'] = Config::$db_server; - - // Only set this if we're not using the default port - if (Config::$db_port != Db::$db->getDefaultPort()) { - $file_settings['db_port'] = (int) Config::$db_port; - } + $file_settings['db_port'] = Config::$db_port; } // If db_port is set and is the same as the default, set it to 0. - if (!empty(Config::$db_port) && Config::$db_port != Db::$db->getDefaultPort()) { + if (!empty(Config::$db_port) && Config::$db_port == Db::$db->getDefaultPort()) { $file_settings['db_port'] = 0; } From 6c880ef0d01dcdf0394fbde9330410748e862550 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Thu, 5 Mar 2026 22:29:37 -0700 Subject: [PATCH 07/45] Fixes some type casting inside Config::set() Signed-off-by: Jon Stovell --- Sources/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Config.php b/Sources/Config.php index e25aa754927..f4aea7f9a7f 100644 --- a/Sources/Config.php +++ b/Sources/Config.php @@ -998,7 +998,7 @@ public static function set(array $settings): void } // Make sure the paths are correct... at least try to fix them. - if (empty(self::$boarddir) || !is_dir(realpath(self::$boarddir))) { + if (empty(self::$boarddir) || !is_dir((string) realpath(self::$boarddir))) { self::$boarddir = !empty($_SERVER['SCRIPT_FILENAME']) ? \dirname(realpath($_SERVER['SCRIPT_FILENAME'])) : \dirname(__DIR__); } From 60d17ac7b4366893fb649b437fb9fac1ad9b233c Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 8 Mar 2026 16:26:42 -0600 Subject: [PATCH 08/45] Loads minimal user data for User::$me in upgrader Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Upgrade.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index 3c7aa6af8ed..8d47037797a 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -1220,10 +1220,11 @@ public function finalize(): bool // Log what we've done. if (!isset(User::$me)) { - User::load(); + User::load(dataset: 'minimal'); } if (empty(User::$me->id) && !empty($this->user['id'])) { + User::load($this->user['id'], dataset: 'minimal'); User::setMe($this->user['id']); } @@ -1608,6 +1609,7 @@ private function performSubsteps(array $substeps): bool // Load up the current user safely. if (!isset(User::$me)) { + User::load($this->user['id'], dataset: 'minimal'); User::setMe($this->user['id']); if ($this->user['id'] === 0 && $this->user['name'] === 'Database Admin') { From e224d83f37149096f3eae3170d465bec72c29dca Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 8 Mar 2026 19:36:08 -0600 Subject: [PATCH 09/45] Only show box for detailed info about migration steps when requested Signed-off-by: Jon Stovell --- Themes/default/MaintenanceTemplate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Themes/default/MaintenanceTemplate.php b/Themes/default/MaintenanceTemplate.php index e95ae46dbfe..e83679bf98f 100644 --- a/Themes/default/MaintenanceTemplate.php +++ b/Themes/default/MaintenanceTemplate.php @@ -311,7 +311,7 @@ protected static function showStepWithSubSteps(string $type, string $done_param)

', Lang::getTxt('upgrade_performing_substeps', ['type' => $type], file: 'Maintenance'), '

', Lang::getTxt('upgrade_please_be_patient', file: 'Maintenance'), '

-
'; +
'; echo '

', From fb77f07dbea5f5a1dd3388f506519d008641dc03 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 8 Mar 2026 19:23:53 -0600 Subject: [PATCH 10/45] Uses JavaScript to update "time elapsed" counter in maintenance tools Signed-off-by: Jon Stovell --- Languages/en_US/Maintenance.php | 8 +------- Sources/Maintenance/Maintenance.php | 15 ++++++++++----- Themes/default/MaintenanceTemplate.php | 20 ++++++++++++++++++-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Languages/en_US/Maintenance.php b/Languages/en_US/Maintenance.php index 38b817e2426..3f3f4ee4477 100644 --- a/Languages/en_US/Maintenance.php +++ b/Languages/en_US/Maintenance.php @@ -65,13 +65,7 @@ $txt['maintenance_step'] = 'Step'; $txt['maintenance_overall_progress'] = 'Overall Progress'; $txt['maintenance_substep_progress'] = 'Step Progress'; -$txt['maintenance_time_elasped_ms'] = 'Time Elapsed {m, plural, - one {# minute} - other {# minutes} -} and {s, plural, - one {# second} - other {# seconds} -}'; +$txt['maintenance_time_elapsed'] = 'Time Elapsed: '; // File Permissions. $txt['chmod_linux_info'] = 'If you have a shell account, the following command can automatically correct permissions on these files'; diff --git a/Sources/Maintenance/Maintenance.php b/Sources/Maintenance/Maintenance.php index 9b1ab1c4ceb..5c9997a6396 100644 --- a/Sources/Maintenance/Maintenance.php +++ b/Sources/Maintenance/Maintenance.php @@ -744,12 +744,17 @@ public static function loginWithDatabasePassword( */ public static function getTimeElapsed(): string { - // How long have we been running this? - $elapsed = time() - (int) self::$context['started']; - $mins = (int) ($elapsed / 60); - $seconds = $elapsed - $mins * 60; + $duration = (new \DateTime('@' . self::$context['started']))->diff(new \DateTime()); - return Lang::getTxt('maintenance_time_elasped_ms', ['m' => $mins, 's' => $seconds]); + if ((int) $duration->format('%a') > 0) { + return \strval((int) $duration->format('%h') + ((int) $duration->format('%a') * 24)) . $duration->format(':%I:%S'); + } + + if ((int) $duration->format('%h') > 0) { + return $duration->format('%h:%I:%S'); + } + + return $duration->format('%i:%S'); } /** diff --git a/Themes/default/MaintenanceTemplate.php b/Themes/default/MaintenanceTemplate.php index e83679bf98f..fd6006d9964 100644 --- a/Themes/default/MaintenanceTemplate.php +++ b/Themes/default/MaintenanceTemplate.php @@ -46,6 +46,7 @@ public static function header(): void const smf_charset = \'UTF-8\'; const allow_xhjr_credentials = true; const isDebug = ', Maintenance::$tool->isDebug() ? 'true' : 'false', '; + const timeStarted = ', Maintenance::$context['started'], '; let startPercent = ', Maintenance::$overall_percent, '; @@ -124,8 +125,9 @@ public static function header(): void ', Maintenance::getItemsProgress(), '% -
- ', Maintenance::getTimeElapsed(), ' +
+ ', Lang::getTxt('maintenance_time_elapsed', file: 'Maintenance'), ' +
@@ -183,6 +185,20 @@ function updateItemsProgress(current, max) setInnerHTML(document.getElementById("item_text"), width + "%"); } } + + // This function dynamically updates the "time elapsed" counter. + function updateTimeElapsed() + { + const elapsed = (Date.now() / 1000) - timeStarted; + + const s = elapsed % 60; + const m = Math.floor(elapsed / 60) % 60; + const h = Math.floor(elapsed / 3600); + + if (document.getElementById("time_elapsed")) { + document.getElementById("time_elapsed").textContent = ((h > 0) ? ((h < 10) ? "0" + h.toString() : h.toString()) + ":" : "") + ((m < 10) ? "0" + m.toString() : m.toString()) + ":" + ((s < 10) ? "0" + s.toString() : s.toString()); + } + } '; From f28b56512f3f8aaac6c131254900e18272d1135e Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Thu, 12 Mar 2026 22:21:50 -0600 Subject: [PATCH 11/45] Fixes bizarre embedded template in critical error message Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/ToolsBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php index 2b63693f399..8442c464b03 100644 --- a/Sources/Maintenance/Tools/ToolsBase.php +++ b/Sources/Maintenance/Tools/ToolsBase.php @@ -559,7 +559,7 @@ public function checkAndHandleTimeout(): void Maintenance::setQueryString(); } - Maintenance::exit(); + Maintenance::exit(Maintenance::isJson()); throw new \Exception('Zombies!'); } From c451bb5bd28bdff8e526eb8a416e3b6e6590aca4 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Wed, 18 Mar 2026 22:44:21 -0600 Subject: [PATCH 12/45] Handles generated columns correctly in Db\APIs\MySQL::backup_table() Signed-off-by: Jon Stovell --- Sources/Db/APIs/MySQL.php | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index c1c5ed6c66c..4c991fbc6d8 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -938,19 +938,29 @@ public function backup_table(string $table, string $backup_table): object|bool ], ); - // If this failed, we go old school. if ($result) { + $columns = []; + + // Do we have any generated columns to deal with? + foreach ($this->list_columns($table, true) as $column) { + // Skip generated columns in the insert statement. + if (empty($column['generation_expression'])) { + $columns[] = $column['name']; + } + } + $request = $this->query( 'INSERT INTO {raw:backup_table} - SELECT * + ({raw:columns}) + SELECT {raw:columns} FROM {raw:table}', [ 'backup_table' => $backup_table, 'table' => $table, + 'columns' => implode(', ', $columns), ], ); - // Old school or no school? if ($request) { return $request; } @@ -1047,6 +1057,13 @@ public function backup_table(string $table, string $backup_table): object|bool ); } + // Restore the generation expressions on any generated columns. + foreach ($this->list_columns($table, true) as $column) { + if (!empty($column['generation_expression'])) { + $this->change_column($backup_table, $column['name'], $column); + } + } + return $request; } @@ -2136,7 +2153,7 @@ public function list_columns(string $table_name, bool $detail = false, array $pa } if (str_contains($row['Extra'], 'GENERATED')) { - $columns[$row['Field']]['generation_expression'] = $row['generation_expression']; + $columns[$row['Field']]['generation_expression'] = $this->unescape_string($row['generation_expression']); $columns[$row['Field']]['stored'] = str_contains($row['Extra'], 'STORED'); } } From f8c09f2b9a13255e866854ec1fa00ba535234516 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Thu, 19 Mar 2026 18:32:20 -0600 Subject: [PATCH 13/45] =?UTF-8?q?Maintenance\Cleanup\v3=5F0\OldFiles=20?= =?UTF-8?q?=E2=86=92=20Maintenance\Cleanup\OldFilesBase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jon Stovell --- Sources/Maintenance/Cleanup/CleanupBase.php | 2 -- .../Cleanup/{v3_0/OldFiles.php => OldFilesBase.php} | 13 +++++++------ Sources/Maintenance/Cleanup/v2_1/OldFiles.php | 4 ++-- Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php | 2 -- Sources/Maintenance/Tools/Upgrade.php | 1 - 5 files changed, 9 insertions(+), 13 deletions(-) rename Sources/Maintenance/Cleanup/{v3_0/OldFiles.php => OldFilesBase.php} (91%) diff --git a/Sources/Maintenance/Cleanup/CleanupBase.php b/Sources/Maintenance/Cleanup/CleanupBase.php index 4f37911097c..7bdce49640d 100644 --- a/Sources/Maintenance/Cleanup/CleanupBase.php +++ b/Sources/Maintenance/Cleanup/CleanupBase.php @@ -38,9 +38,7 @@ abstract class CleanupBase implements SubStepInterface ****************/ /** - * Check if the task should be performed or not. * - * @return bool True if this task needs to be run, false otherwise. */ public function isCandidate(): bool { diff --git a/Sources/Maintenance/Cleanup/v3_0/OldFiles.php b/Sources/Maintenance/Cleanup/OldFilesBase.php similarity index 91% rename from Sources/Maintenance/Cleanup/v3_0/OldFiles.php rename to Sources/Maintenance/Cleanup/OldFilesBase.php index 1f04f87e288..7d376fea788 100644 --- a/Sources/Maintenance/Cleanup/v3_0/OldFiles.php +++ b/Sources/Maintenance/Cleanup/OldFilesBase.php @@ -13,13 +13,16 @@ declare(strict_types=1); -namespace SMF\Maintenance\Cleanup\v3_0; +namespace SMF\Maintenance\Cleanup; use SMF\Config; -use SMF\Maintenance\Cleanup\CleanupBase; use SMF\Utils; -class OldFiles extends CleanupBase +/** + * Base class for cleanup tasks that delete files that have been removed in a + * new version of SMF. + */ +abstract class OldFilesBase extends CleanupBase { /******************* * Public properties @@ -37,7 +40,7 @@ class OldFiles extends CleanupBase /** * @var array * - * List of files removed in SMF 3.0. + * List of files removed in the relevant version of SMF. */ protected array $removed = [ // Files in the Themes directory. @@ -57,9 +60,7 @@ class OldFiles extends CleanupBase ****************/ /** - * Check if the task should be performed or not. * - * @return bool True if this task needs to be run, false otherwise. */ public function isCandidate(): bool { diff --git a/Sources/Maintenance/Cleanup/v2_1/OldFiles.php b/Sources/Maintenance/Cleanup/v2_1/OldFiles.php index e69755f6632..7b0717e3426 100644 --- a/Sources/Maintenance/Cleanup/v2_1/OldFiles.php +++ b/Sources/Maintenance/Cleanup/v2_1/OldFiles.php @@ -15,10 +15,10 @@ namespace SMF\Maintenance\Cleanup\v2_1; -use SMF\Maintenance\Cleanup\v3_0\OldFiles as OldFilesBase; +use SMF\Maintenance\Cleanup\OldFilesBase; /** - * Just like the v3_0 version of OldFiles, but with a different list of files. + * Deletes files that were present in SMF 2.0 but not in SMF 2.1. */ class OldFiles extends OldFilesBase { diff --git a/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php b/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php index 6bf62068a1b..140713af43f 100644 --- a/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php +++ b/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php @@ -35,9 +35,7 @@ class TasksDirCase extends CleanupBase ****************/ /** - * Check if the task should be performed or not. * - * @return bool True if this task needs to be run, false otherwise. */ public function isCandidate(): bool { diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index 8d47037797a..d88a5cb5dab 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -193,7 +193,6 @@ class Upgrade extends ToolsBase implements ToolsInterface // Cleanup steps for 2.1 -> 3.0 'v3_0' => [ Cleanup\v3_0\TasksDirCase::class, - Cleanup\v3_0\OldFiles::class, ], ]; From 31235c4585c6426cfa87d2004ae0645f43e5ba52 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Fri, 20 Mar 2026 19:46:38 -0600 Subject: [PATCH 14/45] Upgrades board descriptions after upgrading smileys Fixes error when upgrading from SMF 2.0 Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Upgrade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index d88a5cb5dab..ebb5f5d08c2 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -86,7 +86,6 @@ class Upgrade extends ToolsBase implements ToolsInterface Migration\v2_1\FixDates::class, Migration\v2_1\CreateMemberLogins::class, Migration\v2_1\CollapsedCategories::class, - Migration\v2_1\BoardDescriptions::class, Migration\v2_1\LegacyAttachments::class, Migration\v2_1\AttachmentSizes::class, Migration\v2_1\AttachmentDirectory::class, @@ -154,6 +153,7 @@ class Upgrade extends ToolsBase implements ToolsInterface Migration\v2_1\IdxLogComments::class, Migration\v2_1\MysqlLegacyData::class, Migration\v2_1\Smileys::class, + Migration\v2_1\BoardDescriptions::class, Migration\v2_1\LogErrorsBacktrace::class, Migration\v2_1\BoardPermissionsView::class, Migration\v2_1\PostgreSqlSchemaDiff::class, From c006611952a558a3c0966b36e577025a08d2eea3 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 29 Mar 2026 14:44:44 -0600 Subject: [PATCH 15/45] Uses correct table names in PostgreSqlSequences migration step Signed-off-by: Jon Stovell --- Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php b/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php index 357608b120e..570fedc75dc 100644 --- a/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php @@ -248,7 +248,7 @@ public function execute(): bool [ 'key' => Config::$db_prefix . $value['key'], 'field' => $value['field'], - 'table' => $value['table'], + 'table' => Config::$db_prefix . $value['table'], ], ); } From 5de83d4761c9393db302afcaaa8e41e7576a9274 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 29 Mar 2026 15:00:48 -0600 Subject: [PATCH 16/45] Explicit null default for expire_time in Db\Schema\v2_1\BanGroups Signed-off-by: Jon Stovell --- Sources/Db/Schema/v2_1/BanGroups.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Db/Schema/v2_1/BanGroups.php b/Sources/Db/Schema/v2_1/BanGroups.php index c9aec8cb9c5..89dad594d2e 100644 --- a/Sources/Db/Schema/v2_1/BanGroups.php +++ b/Sources/Db/Schema/v2_1/BanGroups.php @@ -61,6 +61,8 @@ public function __construct() name: 'expire_time', type: 'int', unsigned: true, + not_null: false, + default: null, ), 'cannot_access' => new Column( name: 'cannot_access', From 4acc5bdbb761b67de7ce0d812b582d6d31e0beba Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Fri, 17 Apr 2026 14:12:08 -0600 Subject: [PATCH 17/45] Fixes column name in SMF\Maintenance\Migration\v2_1\UserDrafts Signed-off-by: Jon Stovell --- Sources/Maintenance/Migration/v2_1/UserDrafts.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Maintenance/Migration/v2_1/UserDrafts.php b/Sources/Maintenance/Migration/v2_1/UserDrafts.php index 7e32267a523..ac5dce5b43a 100644 --- a/Sources/Maintenance/Migration/v2_1/UserDrafts.php +++ b/Sources/Maintenance/Migration/v2_1/UserDrafts.php @@ -75,7 +75,7 @@ public function execute(): bool ) { // Anyone who can currently post unapproved topics we assume can create drafts as well ... $request = Db::$db->query( - 'SELECT id_group, id_board, add_deny, permission + 'SELECT id_group, id_profile, add_deny, permission FROM {db_prefix}board_permissions WHERE permission = {literal:post_unapproved_topics}', [], @@ -86,7 +86,7 @@ public function execute(): bool while ($row = Db::$db->fetch_assoc($request)) { $inserts[] = [ (int) $row['id_group'], - (int) $row['id_board'], + (int) $row['id_profile'], 'post_draft', (int) $row['add_deny'], ]; @@ -99,7 +99,7 @@ public function execute(): bool '{db_prefix}board_permissions', [ 'id_group' => 'int', - 'id_board' => 'int', + 'id_profile' => 'int', 'permission' => 'string', 'add_deny' => 'int', ], From b5296eb6aef742ac32641f5cf9a7ac5b2c1c5c2b Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 21 Apr 2026 16:14:54 -0600 Subject: [PATCH 18/45] Always uses MigrationBase::query() for migration db queries Signed-off-by: Jon Stovell --- .../Maintenance/Migration/v2_1/AgreementUpdate.php | 4 ++-- Sources/Maintenance/Migration/v2_1/Ipv6Base.php | 2 +- Sources/Maintenance/Migration/v2_1/Permissions.php | 4 ++-- .../Migration/v2_1/PersonalMessageLabels.php | 14 +++++++------- Sources/Maintenance/Migration/v2_1/UserDrafts.php | 2 +- .../Maintenance/Migration/v3_0/ConvertToInnoDb.php | 8 ++++---- Sources/Maintenance/Migration/v3_0/EditHistory.php | 4 ++-- Sources/Maintenance/Migration/v3_0/EventUids.php | 4 ++-- .../Migration/v3_0/HolidaysToEvents.php | 2 +- .../Migration/v3_0/LanguageDirectory.php | 4 ++-- Sources/Maintenance/Migration/v3_0/MailType.php | 3 +-- .../Migration/v3_0/PermissionChanges.php | 3 +-- .../Maintenance/Migration/v3_0/RecurringEvents.php | 8 ++++---- 13 files changed, 30 insertions(+), 32 deletions(-) diff --git a/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php b/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php index adf662aaacc..eaae86caec4 100644 --- a/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php +++ b/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php @@ -110,7 +110,7 @@ public function execute(): bool $this->handleTimeout($start); $extras = []; - $request = Db::$db->query( + $request = $this->query( 'SELECT id_action, extra FROM {db_prefix}log_actions WHERE id_member = {int:blank_id} @@ -145,7 +145,7 @@ public function execute(): bool } if (!empty($extra['applicator'])) { - $request = Db::$db->query( + $request = $this->query( 'UPDATE {db_prefix}log_actions SET id_member = {int:id_member} WHERE id_action = {int:id_action}', diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6Base.php b/Sources/Maintenance/Migration/v2_1/Ipv6Base.php index 68ff0e508d2..51091879443 100644 --- a/Sources/Maintenance/Migration/v2_1/Ipv6Base.php +++ b/Sources/Maintenance/Migration/v2_1/Ipv6Base.php @@ -111,7 +111,7 @@ public function convertData(string $targetTable, string $oldCol, string $newCol, // Execute updates every $setSize & also when done with contents of $arIp if ((($i + 1) == $count) || (($i + 1) % $setSize === 0)) { $updates['whereSet'] = array_values($updates); - Db::$db->query( + $this->query( 'UPDATE {db_prefix}' . $targetTable . ' SET ' . $newCol . ' = CASE ' . implode(' diff --git a/Sources/Maintenance/Migration/v2_1/Permissions.php b/Sources/Maintenance/Migration/v2_1/Permissions.php index 0c8cbcfa903..f7f543ae7c3 100644 --- a/Sources/Maintenance/Migration/v2_1/Permissions.php +++ b/Sources/Maintenance/Migration/v2_1/Permissions.php @@ -248,7 +248,7 @@ public function execute(): bool ); } - Db::$db->query( + $this->query( 'DELETE FROM {db_prefix}settings WHERE variable = {string:warning_show}', [ @@ -306,7 +306,7 @@ public function execute(): bool $this->handleTimeout(++$start); - Db::$db->query( + $this->query( 'DELETE FROM {db_prefix}permissions WHERE id_group = {int:guests} AND permission IN ({array_string:illegal_perms})', diff --git a/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php index d307db54142..31c537f4f59 100644 --- a/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php +++ b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php @@ -119,7 +119,7 @@ public function execute(): bool $inserts = []; // Pull the label info - $get_labels = Db::$db->query( + $get_labels = $this->query( 'SELECT id_member, message_labels FROM {db_prefix}members WHERE message_labels != {string:blank} @@ -172,7 +172,7 @@ public function execute(): bool } // This is the easy part - update the inbox stuff - Db::$db->query( + $this->query( 'UPDATE {db_prefix}pm_recipients SET in_inbox = {int:in_inbox} WHERE FIND_IN_SET({int:minusone}, labels) @@ -185,7 +185,7 @@ public function execute(): bool ); // Now we go pull the new IDs for each label - $get_new_label_ids = Db::$db->query( + $get_new_label_ids = $this->query( 'SELECT * FROM {db_prefix}pm_labels WHERE id_member IN ({array_int:member_list})', @@ -206,7 +206,7 @@ public function execute(): bool // Pull label info from pm_recipients // Ignore any that are only in the inbox - $get_pm_labels = Db::$db->query( + $get_pm_labels = $this->query( 'SELECT id_pm, id_member, labels FROM {db_prefix}pm_recipients WHERE deleted = {int:not_deleted} @@ -249,7 +249,7 @@ public function execute(): bool } // Final step of this ridiculously massive process - $get_pm_rules = Db::$db->query( + $get_pm_rules = $this->query( 'SELECT id_member, id_rule, actions FROM {db_prefix}pm_rules WHERE id_member IN ({array_int:member_list})', @@ -278,7 +278,7 @@ public function execute(): bool // Put this back into a string $actions = serialize($actions); - Db::$db->query( + $this->query( 'UPDATE {db_prefix}pm_rules SET actions = {string:actions} WHERE id_rule = {int:id_rule}', @@ -291,7 +291,7 @@ public function execute(): bool } // Remove processed pm labels, to avoid duplicated data if upgrader is restarted. - Db::$db->query( + $this->query( 'UPDATE {db_prefix}members SET message_labels = {string:blank} WHERE id_member IN ({array_int:member_list})', diff --git a/Sources/Maintenance/Migration/v2_1/UserDrafts.php b/Sources/Maintenance/Migration/v2_1/UserDrafts.php index ac5dce5b43a..9af19751a2d 100644 --- a/Sources/Maintenance/Migration/v2_1/UserDrafts.php +++ b/Sources/Maintenance/Migration/v2_1/UserDrafts.php @@ -74,7 +74,7 @@ public function execute(): bool ) ) { // Anyone who can currently post unapproved topics we assume can create drafts as well ... - $request = Db::$db->query( + $request = $this->query( 'SELECT id_group, id_profile, add_deny, permission FROM {db_prefix}board_permissions WHERE permission = {literal:post_unapproved_topics}', diff --git a/Sources/Maintenance/Migration/v3_0/ConvertToInnoDb.php b/Sources/Maintenance/Migration/v3_0/ConvertToInnoDb.php index e3d0ffa935a..a0a82b4b076 100644 --- a/Sources/Maintenance/Migration/v3_0/ConvertToInnoDb.php +++ b/Sources/Maintenance/Migration/v3_0/ConvertToInnoDb.php @@ -54,7 +54,7 @@ public function execute(): bool $result = true; if ($structure['engine'] !== 'InnoDB') { - $result = Db::$db->query( + $result = $this->query( 'ALTER TABLE {identifier:table} ENGINE {literal:InnoDB} ROW_FORMAT=DYNAMIC', @@ -63,7 +63,7 @@ public function execute(): bool ], ); } elseif ($structure['row_format'] !== 'Dynamic') { - $result = Db::$db->query( + $result = $this->query( 'ALTER TABLE {identifier:table} ROW_FORMAT=DYNAMIC', [ @@ -80,7 +80,7 @@ public function execute(): bool // Try to ensure all future tables use dynamic row format. $can_set_global_default = false; - $request = Db::$db->query('SHOW GRANTS'); + $request = $this->query('SHOW GRANTS'); while ($row = Db::$db->fetch_row($request)) { if ( @@ -95,7 +95,7 @@ public function execute(): bool Db::$db->free_result($request); if ($can_set_global_default) { - $result = Db::$db->query( + $result = $this->query( 'SET GLOBAL innodb_default_row_format=DYNAMIC', [ 'db_error_skip' => true, diff --git a/Sources/Maintenance/Migration/v3_0/EditHistory.php b/Sources/Maintenance/Migration/v3_0/EditHistory.php index 42e559964b7..28507c27929 100644 --- a/Sources/Maintenance/Migration/v3_0/EditHistory.php +++ b/Sources/Maintenance/Migration/v3_0/EditHistory.php @@ -50,7 +50,7 @@ public function execute(): bool } // Populate edit_history. - $request = Db::$db->query( + $request = $this->query( 'SELECT id_msg, body, modified_time, modified_name, modified_reason, edit_history FROM {db_prefix}messages WHERE id_msg > {int:start} @@ -77,7 +77,7 @@ public function execute(): bool $row['modified_reason'], ]]); - Db::$db->query( + $this->query( 'UPDATE {db_prefix}messages SET edit_history = {string:edit_history} WHERE id_msg = {int:id_msg}', diff --git a/Sources/Maintenance/Migration/v3_0/EventUids.php b/Sources/Maintenance/Migration/v3_0/EventUids.php index 446978eae04..bf1389e5f23 100644 --- a/Sources/Maintenance/Migration/v3_0/EventUids.php +++ b/Sources/Maintenance/Migration/v3_0/EventUids.php @@ -41,7 +41,7 @@ public function execute(): bool { $calendar_updates = []; - $request = Db::$db->query( + $request = $this->query( 'SELECT id_event, uid FROM {db_prefix}calendar', [], @@ -56,7 +56,7 @@ public function execute(): bool Db::$db->free_result($request); foreach ($calendar_updates as $calendar_update) { - Db::$db->query( + $this->query( 'UPDATE {db_prefix}calendar SET uid = {string:uid} WHERE id_event = {int:id_event}', diff --git a/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php b/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php index 199524a028d..e5697819e99 100644 --- a/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php +++ b/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php @@ -634,7 +634,7 @@ public function execute(): bool User::load(); } - $request = Db::$db->query( + $request = $this->query( 'SELECT title, GROUP_CONCAT(event_date) as rdates FROM {db_prefix}calendar_holidays GROUP BY title', diff --git a/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php b/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php index 57c527cd770..4a85c9fa1cb 100644 --- a/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php +++ b/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php @@ -72,7 +72,7 @@ public function execute(): bool $this->handleTimeout(); // Skip errors here so we don't croak if the columns don't exist... - $request = Db::$db->query( + $request = $this->query( 'SELECT id_member FROM {db_prefix}members WHERE lngfile IN ({array_string:possible_languages}) @@ -105,7 +105,7 @@ public function execute(): bool $args['search_members'] = $members; - Db::$db->query( + $this->query( 'UPDATE {db_prefix}members SET lngfile = CASE ' . implode(' ', $statements) . ' diff --git a/Sources/Maintenance/Migration/v3_0/MailType.php b/Sources/Maintenance/Migration/v3_0/MailType.php index c8e1a005d48..d60f295789f 100644 --- a/Sources/Maintenance/Migration/v3_0/MailType.php +++ b/Sources/Maintenance/Migration/v3_0/MailType.php @@ -15,7 +15,6 @@ namespace SMF\Maintenance\Migration\v3_0; -use SMF\Db\DatabaseApi as Db; use SMF\Maintenance\Migration\MigrationBase; class MailType extends MigrationBase @@ -38,7 +37,7 @@ class MailType extends MigrationBase */ public function execute(): bool { - Db::$db->query( + $this->query( 'UPDATE {db_prefix}settings SET value = CASE diff --git a/Sources/Maintenance/Migration/v3_0/PermissionChanges.php b/Sources/Maintenance/Migration/v3_0/PermissionChanges.php index 17061c1b744..75cd3dabb1f 100644 --- a/Sources/Maintenance/Migration/v3_0/PermissionChanges.php +++ b/Sources/Maintenance/Migration/v3_0/PermissionChanges.php @@ -15,7 +15,6 @@ namespace SMF\Maintenance\Migration\v3_0; -use SMF\Db\DatabaseApi as Db; use SMF\Maintenance\Migration\MigrationBase; class PermissionChanges extends MigrationBase @@ -38,7 +37,7 @@ class PermissionChanges extends MigrationBase */ public function execute(): bool { - Db::$db->query( + $this->query( 'DELETE FROM {db_prefix}permissions WHERE permission IN ({array_string:perms})', [ diff --git a/Sources/Maintenance/Migration/v3_0/RecurringEvents.php b/Sources/Maintenance/Migration/v3_0/RecurringEvents.php index ce10d2a8e54..c3cc255fb63 100644 --- a/Sources/Maintenance/Migration/v3_0/RecurringEvents.php +++ b/Sources/Maintenance/Migration/v3_0/RecurringEvents.php @@ -51,13 +51,13 @@ public function execute(): bool } if (Db::$db->title === MYSQL_TITLE) { - Db::$db->query( + $this->query( 'ALTER TABLE {db_prefix}calendar MODIFY COLUMN start_date DATE AFTER id_member', [], ); - Db::$db->query( + $this->query( 'ALTER TABLE {db_prefix}calendar MODIFY COLUMN end_date DATE AFTER start_date', [], @@ -67,7 +67,7 @@ public function execute(): bool $updates = []; - $request = Db::$db->query( + $request = $this->query( 'SELECT id_event, start_date, end_date, start_time, end_time, timezone FROM {db_prefix}calendar', [], @@ -120,7 +120,7 @@ public function execute(): bool Db::$db->free_result($request); foreach ($updates as $id_event => $changes) { - Db::$db->query( + $this->query( 'UPDATE {db_prefix}calendar SET duration = {string:duration}, end_date = {date:end_date}, rrule = {string:rrule} WHERE id_event = {int:id_event}', From 5cecc0df768d447106db33406e3590fe10e88e8a Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 21 Apr 2026 17:12:21 -0600 Subject: [PATCH 19/45] Prevents error when no members have PM labels Signed-off-by: Jon Stovell --- .../Migration/v2_1/PersonalMessageLabels.php | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php index 31c537f4f59..5cc4be1c97a 100644 --- a/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php +++ b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php @@ -116,6 +116,8 @@ public function execute(): bool while (!$is_done) { $this->handleTimeout($start); + $label_info = []; + $member_list = []; $inserts = []; // Pull the label info @@ -131,9 +133,6 @@ public function execute(): bool ], ); - $label_info = []; - $member_list = []; - while ($row = Db::$db->fetch_assoc($get_labels)) { $member_list[] = $row['id_member']; @@ -149,6 +148,11 @@ public function execute(): bool Db::$db->free_result($get_labels); + if (empty($member_list)) { + $is_done = true; + break; + } + foreach ($label_info as $id_member => $labels) { foreach ($labels as $label => $index) { $inserts[] = [$id_member, $label]; @@ -311,32 +315,10 @@ public function execute(): bool } $pm_recipients_table = new Schema\v2_1\PmRecipients(); - $existing_structure = $pm_recipients_table->getCurrentStructure(); - - foreach ($existing_structure['columns'] as $column) { - if ($column['name'] == 'labels') { - $col = new Column( - name: $column['name'], - type: 'varchar', - ); - - $pm_recipients_table->dropColumn($col); - } - } + $pm_recipients_table->dropColumn('labels'); $members_table = new Schema\v2_1\Members(); - $existing_structure = $members_table->getCurrentStructure(); - - foreach ($existing_structure['columns'] as $column) { - if ($column['name'] == 'message_labels') { - $col = new Column( - name: $column['name'], - type: 'varchar', - ); - - $members_table->dropColumn($col); - } - } + $members_table->dropColumn('message_labels'); return true; } From d17603b4fb588f0e2aec1994157b7909d3c7520f Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 21 Apr 2026 17:21:56 -0600 Subject: [PATCH 20/45] Implements SMF\Db\Schema\Table::exists() Signed-off-by: Jon Stovell --- Sources/Db/Schema/Table.php | 46 +++++++++++++++++-- .../Migration/v2_1/BoardPermissionsView.php | 7 +-- .../Migration/v2_1/CreateAlerts.php | 34 +++----------- .../Migration/v2_1/CreateBackgroundTasks.php | 9 +--- .../Migration/v2_1/CreateMemberLogins.php | 10 +--- Sources/Maintenance/Migration/v2_1/Likes.php | 29 ++++-------- .../Maintenance/Migration/v2_1/Mentions.php | 6 +-- .../Migration/v2_1/ModeratorGroups.php | 6 +-- .../Migration/v2_1/PersonalMessageLabels.php | 7 +-- .../Maintenance/Migration/v2_1/Smileys.php | 7 +-- .../Maintenance/Migration/v2_1/UserDrafts.php | 14 ++---- .../Migration/v2_1/VerificationQuestions.php | 15 ++---- 12 files changed, 80 insertions(+), 110 deletions(-) diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index b45a081a8b5..56a4f264771 100644 --- a/Sources/Db/Schema/Table.php +++ b/Sources/Db/Schema/Table.php @@ -76,6 +76,17 @@ class Table */ public ?int $auto_start; + /**************************** + * Internal static properties + ****************************/ + + /** + * @var array + * + * Cached output of Db::$db->list_tables + */ + protected static array $existing_tables = []; + /**************** * Public methods ****************/ @@ -94,6 +105,21 @@ public function __construct() } } + /** + * Checks whether a table with this name exists in the database. + * + * @param bool $force_refresh If true, force a refresh of the tables list. + * @return bool Whether this table exists. + */ + public function exists(bool $force_refresh = false) + { + if ($force_refresh || empty(self::$existing_tables)) { + self::$existing_tables = Db::$db->list_tables(); + } + + return \in_array(Db::$db->prefix . $this->name, self::$existing_tables); + } + /** * Checks all the columns and indexes in this table to make sure they * are defined the way they should be, and fixes any that aren't. @@ -106,7 +132,7 @@ public function normalize(): bool return false; } - if (empty(Db::$db->list_tables(false, Db::$db->prefix . $this->name))) { + if (!$this->exists()) { return $this->create(); } @@ -287,13 +313,20 @@ public function create(array $parameters = [], string $if_exists = 'ignore'): bo return false; } - return Db::$db->create_table( + $success = Db::$db->create_table( '{db_prefix}' . $this->name, array_map('get_object_vars', array_values($this->columns)), array_map('get_object_vars', array_values($this->indexes)), $parameters, $if_exists, ); + + if ($success) { + // Force a refresh of the list of tables. + self::$existing_tables = []; + } + + return $success; } /** @@ -305,7 +338,14 @@ public function create(array $parameters = [], string $if_exists = 'ignore'): bo */ public function drop(): bool { - return Db::$db->drop_table('{db_prefix}' . $this->name); + $success = Db::$db->drop_table('{db_prefix}' . $this->name); + + if ($success) { + // Force a refresh of the list of tables. + self::$existing_tables = []; + } + + return $success; } /** diff --git a/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php b/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php index b77b509c141..04bec7f32bc 100644 --- a/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php +++ b/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php @@ -15,7 +15,6 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; use SMF\Db\DatabaseApi as Db; use SMF\Db\Schema; use SMF\Maintenance\Maintenance; @@ -46,11 +45,7 @@ public function execute(): bool // Create table board_permissions_view if ($start <= 0) { $table = new Schema\v2_1\BoardPermissionsView(); - $existing_tables = Db::$db->list_tables(); - - if (!\in_array(Config::$db_prefix . $table->name, $existing_tables)) { - $table->create(); - } + $table->create(); $this->handleTimeout(++$start); } diff --git a/Sources/Maintenance/Migration/v2_1/CreateAlerts.php b/Sources/Maintenance/Migration/v2_1/CreateAlerts.php index cfe950fc98c..5fab23bd87f 100644 --- a/Sources/Maintenance/Migration/v2_1/CreateAlerts.php +++ b/Sources/Maintenance/Migration/v2_1/CreateAlerts.php @@ -60,15 +60,8 @@ public function execute(): bool $user_alert_table = new Schema\v2_1\UserAlerts(); $user_alert_prefs_table = new Schema\v2_1\UserAlertsPrefs(); - $tables = Db::$db->list_tables(); - - if (!\in_array($user_alert_table->name, $tables)) { - $user_alert_table->create(); - } - - if (!\in_array($user_alert_prefs_table->name, $tables)) { - $user_alert_prefs_table->create(); - } + $user_alert_table->create(); + $user_alert_prefs_table->create(); $existing_structure = $members_table->getCurrentStructure(); @@ -150,25 +143,10 @@ public function execute(): bool } while (Maintenance::getCurrentStart() < Maintenance::$total_items); } - if (\in_array('notify_send_body', $member_columns)) { - Db::$db->remove_column('{db_prefix}members', 'notify_send_body'); - $this->handleTimeout(); - } - - if (\in_array('notify_types', $member_columns)) { - Db::$db->remove_column('{db_prefix}members', 'notify_types'); - $this->handleTimeout(); - } - - if (\in_array('notify_regularity', $member_columns)) { - Db::$db->remove_column('{db_prefix}members', 'notify_regularity'); - $this->handleTimeout(); - } - - if (\in_array('notify_announcements', $member_columns)) { - Db::$db->remove_column('{db_prefix}members', 'notify_announcements'); - $this->handleTimeout(); - } + $members_table->dropColumn('notify_send_body'); + $members_table->dropColumn('notify_types'); + $members_table->dropColumn('notify_regularity'); + $members_table->dropColumn('notify_announcements'); return true; } diff --git a/Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php b/Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php index 6f4d5c0dde7..f0bdc1b2051 100644 --- a/Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php +++ b/Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php @@ -15,8 +15,6 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; -use SMF\Db\DatabaseApi as Db; use SMF\Db\Schema; use SMF\Maintenance\Migration\MigrationBase; @@ -41,12 +39,7 @@ class CreateBackgroundTasks extends MigrationBase public function execute(): bool { $background_tasks_table = new Schema\v2_1\BackgroundTasks(); - - $tables = Db::$db->list_tables(); - - if (!\in_array(Config::$db_prefix . $background_tasks_table->name, $tables)) { - $background_tasks_table->create(); - } + $background_tasks_table->create(); return true; } diff --git a/Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php b/Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php index 10a6ccae776..26a91fb77d0 100644 --- a/Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php +++ b/Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php @@ -15,8 +15,6 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; -use SMF\Db\DatabaseApi as Db; use SMF\Db\Schema; use SMF\Maintenance\Migration\MigrationBase; @@ -40,12 +38,8 @@ class CreateMemberLogins extends MigrationBase */ public function execute(): bool { - $tables = Db::$db->list_tables(); - - if (!\in_array(Config::$db_prefix . 'member_logins', $tables)) { - $member_logins = new Schema\v2_1\MemberLogins(); - $member_logins->create(); - } + $member_logins = new Schema\v2_1\MemberLogins(); + $member_logins->create(); return true; } diff --git a/Sources/Maintenance/Migration/v2_1/Likes.php b/Sources/Maintenance/Migration/v2_1/Likes.php index 61a58668711..7ff4bf2e401 100644 --- a/Sources/Maintenance/Migration/v2_1/Likes.php +++ b/Sources/Maintenance/Migration/v2_1/Likes.php @@ -15,8 +15,7 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; -use SMF\Db\DatabaseApi as Db; +use SMF\Db\Schema; use SMF\Maintenance\Maintenance; use SMF\Maintenance\Migration\MigrationBase; @@ -40,9 +39,9 @@ class Likes extends MigrationBase */ public function isCandidate(): bool { - $tables = Db::$db->list_tables(); + $table = new Schema\v2_1\UserLikes(); - return !\in_array(Config::$db_prefix . 'user_likes', $tables); + return !$table->exists(); } /** @@ -52,27 +51,19 @@ public function execute(): bool { $start = Maintenance::getCurrentStart(); - $LikesTable = new \SMF\Db\Schema\v2_1\UserLikes(); - - $tables = Db::$db->list_tables(); - - // Creating draft table. - if ($start <= 0 && !\in_array(Config::$db_prefix . 'user_likes', $tables)) { - $LikesTable->create(); - + if ($start <= 0) { + $likes_table = new Schema\v2_1\UserLikes(); + $likes_table->create(); $this->handleTimeout(++$start); } // Adding likes column to the messages table. (May take a while) if ($start <= 1) { - $MessagesTable = new \SMF\Db\Schema\v2_1\Messages(); - $existing_structure = $MessagesTable->getCurrentStructure(); + $messages_table = new Schema\v2_1\Messages(); + $existing_structure = $messages_table->getCurrentStructure(); - foreach ($MessagesTable->columns as $column) { - // Add the columns. - if ($column->name === 'likes' && !isset($existing_structure['columns'][$column->name])) { - $MessagesTable->addColumn($column); - } + if (!isset($existing_structure['columns']['likes'])) { + $messages_table->addColumn($messages_table->columns['likes']); } $this->handleTimeout(++$start); diff --git a/Sources/Maintenance/Migration/v2_1/Mentions.php b/Sources/Maintenance/Migration/v2_1/Mentions.php index de32bc69fa1..61222eb5c50 100644 --- a/Sources/Maintenance/Migration/v2_1/Mentions.php +++ b/Sources/Maintenance/Migration/v2_1/Mentions.php @@ -15,8 +15,6 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; -use SMF\Db\DatabaseApi as Db; use SMF\Db\Schema; use SMF\Maintenance\Migration\MigrationBase; @@ -40,9 +38,9 @@ class Mentions extends MigrationBase */ public function isCandidate(): bool { - $tables = Db::$db->list_tables(); + $table = new Schema\v2_1\Mentions(); - return !\in_array(Config::$db_prefix . 'mentions', $tables); + return !$table->exists(); } /** diff --git a/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php b/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php index 70be7572da7..7c160bcdf0e 100644 --- a/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php +++ b/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php @@ -15,8 +15,6 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; -use SMF\Db\DatabaseApi as Db; use SMF\Db\Schema; use SMF\Maintenance\Migration\MigrationBase; @@ -40,9 +38,9 @@ class ModeratorGroups extends MigrationBase */ public function isCandidate(): bool { - $tables = Db::$db->list_tables(); + $table = new Schema\v2_1\ModeratorGroups(); - return !\in_array(Config::$db_prefix . 'moderator_groups', $tables); + return !$table->exists(); } /** diff --git a/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php index 5cc4be1c97a..6eacbc7b075 100644 --- a/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php +++ b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php @@ -15,7 +15,6 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; use SMF\Db\DatabaseApi as Db; use SMF\Db\Schema; use SMF\Db\Schema\Column; @@ -75,15 +74,13 @@ public function execute(): bool $pm_labels_table = new Schema\v2_1\PmLabels(); $pm_labeled_messages_table = new Schema\v2_1\PmLabeledMessages(); - $tables = Db::$db->list_tables(); - if ($start <= 0) { - if (!\in_array(Config::$db_prefix . 'pm_labels', $tables)) { + if (!$pm_labels_table->exists()) { $pm_labels_table->create(); $this->handleTimeout(0); } - if (!\in_array(Config::$db_prefix . 'pm_labeled_messages', $tables)) { + if (!$pm_labeled_messages_table->exists()) { $pm_labeled_messages_table->create(); $this->handleTimeout(0); } diff --git a/Sources/Maintenance/Migration/v2_1/Smileys.php b/Sources/Maintenance/Migration/v2_1/Smileys.php index 007e8c11c4b..c4536b8e373 100644 --- a/Sources/Maintenance/Migration/v2_1/Smileys.php +++ b/Sources/Maintenance/Migration/v2_1/Smileys.php @@ -48,12 +48,7 @@ public function execute(): bool // Adding the new `smiley_files` table if ($start <= 0) { $table = new Schema\v2_1\SmileyFiles(); - $existing_tables = Db::$db->list_tables(); - - if (!\in_array(Config::$db_prefix . $table->name, $existing_tables)) { - $table->create(); - } - + $table->create(); $this->handleTimeout(++$start); } diff --git a/Sources/Maintenance/Migration/v2_1/UserDrafts.php b/Sources/Maintenance/Migration/v2_1/UserDrafts.php index 9af19751a2d..6abe26cccc8 100644 --- a/Sources/Maintenance/Migration/v2_1/UserDrafts.php +++ b/Sources/Maintenance/Migration/v2_1/UserDrafts.php @@ -41,9 +41,9 @@ class UserDrafts extends MigrationBase */ public function isCandidate(): bool { - $tables = Db::$db->list_tables(); + $table = new Schema\v2_1\UserDrafts(); - return !\in_array(Config::$db_prefix . 'user_drafts', $tables) || Maintenance::getCurrentStart() > 0; + return !$table->exists(); } /** @@ -53,14 +53,10 @@ public function execute(): bool { $start = Maintenance::getCurrentStart(); - $drafts_table = new Schema\v2_1\UserDrafts(); - - $tables = Db::$db->list_tables(); - // Creating draft table. - if ($start <= 0 && !\in_array(Config::$db_prefix . 'user_drafts', $tables)) { - $drafts_table->create(); - + if ($start <= 0) { + $table = new Schema\v2_1\UserDrafts(); + $table->create(); $this->handleTimeout(++$start); } diff --git a/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php b/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php index f0d44b5ee52..e9a500a81a1 100644 --- a/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php +++ b/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php @@ -15,7 +15,6 @@ namespace SMF\Maintenance\Migration\v2_1; -use SMF\Config; use SMF\Db\DatabaseApi as Db; use SMF\Db\Schema; use SMF\Maintenance\Maintenance; @@ -41,9 +40,9 @@ class VerificationQuestions extends MigrationBase */ public function isCandidate(): bool { - $tables = Db::$db->list_tables(); + $table = new Schema\v2_1\Qanda(); - return !\in_array(Config::$db_prefix . 'qanda', $tables); + return !$table->exists(); } /** @@ -53,14 +52,10 @@ public function execute(): bool { $start = Maintenance::getCurrentStart(); - $table = new Schema\v2_1\Qanda(); - - $tables = Db::$db->list_tables(); - - // Creating draft table. - if ($start <= 0 && !\in_array(Config::$db_prefix . 'qanda', $tables)) { + // Creating table. + if ($start <= 0) { + $table = new Schema\v2_1\Qanda(); $table->create(); - $this->handleTimeout(++$start); } From f5fdfc04ba84f8dca74a64810c5999dd499d988e Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 21 Apr 2026 20:20:26 -0600 Subject: [PATCH 21/45] Always force a fresh download of maintenance tool JavaScript and CSS Signed-off-by: Jon Stovell --- Themes/default/MaintenanceTemplate.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Themes/default/MaintenanceTemplate.php b/Themes/default/MaintenanceTemplate.php index fd6006d9964..051daf5efa7 100644 --- a/Themes/default/MaintenanceTemplate.php +++ b/Themes/default/MaintenanceTemplate.php @@ -36,11 +36,11 @@ public static function header(): void ', Maintenance::$tool->getPageTitle(), ' - - ', Lang::getTxt('lang_rtl', file: 'General') == '1' ? ' - ' : '', ' + + ', Lang::getTxt('lang_rtl', file: 'General') == '1' ? ' + ' : '', ' - + '; } } From bae2aec696df116cfe6b71e8957c6ef271a8d179 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Fri, 26 Jun 2026 16:06:53 -0600 Subject: [PATCH 42/45] Handles timeouts more gracefully in maintenance tools Signed-off-by: Jon Stovell --- Sources/Maintenance/Migration/MigrationBase.php | 14 ++++++++++++-- Sources/Maintenance/Tools/ToolsBase.php | 12 ++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Sources/Maintenance/Migration/MigrationBase.php b/Sources/Maintenance/Migration/MigrationBase.php index 22d668627de..9612bb56ac7 100644 --- a/Sources/Maintenance/Migration/MigrationBase.php +++ b/Sources/Maintenance/Migration/MigrationBase.php @@ -64,7 +64,8 @@ public function execute(): bool /** * Wrapper for the tool to handle timeout protection. * - * If a timeout needs to occur, it is handled, ensure that prior to this call, all variables are updated.. + * @param ?int $start If set, the current start value will be set to this + * value before the timeout check itself occurs. */ protected function handleTimeout(?int $start = null): void { @@ -72,7 +73,16 @@ protected function handleTimeout(?int $start = null): void Maintenance::setCurrentStart($start); } - Maintenance::$tool->checkAndHandleTimeout(); + Maintenance::$tool->checkAndHandleTimeout([ + 'name' => $this->name, + 'completed' => false, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $this::class, + ], + ]); } /** diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php index 6094c1201b0..7193b974371 100644 --- a/Sources/Maintenance/Tools/ToolsBase.php +++ b/Sources/Maintenance/Tools/ToolsBase.php @@ -547,23 +547,31 @@ final public function detectLanguages(array $key_files = ['General']): array } /** - * This will check if we need to handle a timeout, if so, it sets up data for the next round. + * This will check if we need to handle a timeout, if so, it sets up data + * for the next round. * + * @param array $json_response_data Data to send in a JSON response if we + * have timed out. This data is passed to Maintenance::jsonResponse(). + * Only used when Maintenance::isJson() returns true. * @throws \ValueError * @throws \Exception */ - public function checkAndHandleTimeout(): void + public function checkAndHandleTimeout(array $json_response_data = []): void { if (!Maintenance::isOutOfTime()) { return; } + $this->logProgress(Lang::getTxt('log_paused_step', ['num' => $this->getStep()->getId()], file: 'Maintenance')); + // If this is not json, we need to do a few things. if (!Maintenance::isJson()) { // We're going to pause after this! Maintenance::$context['pause'] = true; Maintenance::setQueryString(); + } else { + Maintenance::jsonResponse($json_response_data); } Maintenance::exit(Maintenance::isJson()); From d0e3ab14f2df7860772662413805b7a609b3f2e9 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 28 Jun 2026 22:05:03 -0600 Subject: [PATCH 43/45] Sets explicit default null for inet columns Signed-off-by: Jon Stovell --- Sources/Db/Schema/v2_1/BanItems.php | 2 ++ Sources/Db/Schema/v2_1/LogActions.php | 1 + Sources/Db/Schema/v2_1/LogBanned.php | 1 + Sources/Db/Schema/v2_1/LogErrors.php | 1 + Sources/Db/Schema/v2_1/LogFloodcontrol.php | 1 + Sources/Db/Schema/v2_1/LogOnline.php | 1 + Sources/Db/Schema/v2_1/LogReportedComments.php | 1 + Sources/Db/Schema/v2_1/MemberLogins.php | 2 ++ Sources/Db/Schema/v2_1/Members.php | 2 ++ Sources/Db/Schema/v2_1/Messages.php | 1 + Sources/Db/Schema/v3_0/BanItems.php | 2 ++ Sources/Db/Schema/v3_0/LogActions.php | 1 + Sources/Db/Schema/v3_0/LogBanned.php | 1 + Sources/Db/Schema/v3_0/LogErrors.php | 1 + Sources/Db/Schema/v3_0/LogOnline.php | 1 + Sources/Db/Schema/v3_0/LogReportedComments.php | 1 + Sources/Db/Schema/v3_0/MemberLogins.php | 2 ++ Sources/Db/Schema/v3_0/Members.php | 2 ++ Sources/Db/Schema/v3_0/Messages.php | 1 + 19 files changed, 25 insertions(+) diff --git a/Sources/Db/Schema/v2_1/BanItems.php b/Sources/Db/Schema/v2_1/BanItems.php index 7636b3e0bea..3f332e19353 100644 --- a/Sources/Db/Schema/v2_1/BanItems.php +++ b/Sources/Db/Schema/v2_1/BanItems.php @@ -54,11 +54,13 @@ public function __construct() name: 'ip_low', type: 'inet', size: 16, + default: null, ), 'ip_high' => new Column( name: 'ip_high', type: 'inet', size: 16, + default: null, ), 'hostname' => new Column( name: 'hostname', diff --git a/Sources/Db/Schema/v2_1/LogActions.php b/Sources/Db/Schema/v2_1/LogActions.php index 3a8cd455b5f..b56c23d21cd 100644 --- a/Sources/Db/Schema/v2_1/LogActions.php +++ b/Sources/Db/Schema/v2_1/LogActions.php @@ -68,6 +68,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'action' => new Column( name: 'action', diff --git a/Sources/Db/Schema/v2_1/LogBanned.php b/Sources/Db/Schema/v2_1/LogBanned.php index d3b8d52a7ed..3f900168494 100644 --- a/Sources/Db/Schema/v2_1/LogBanned.php +++ b/Sources/Db/Schema/v2_1/LogBanned.php @@ -54,6 +54,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'email' => new Column( name: 'email', diff --git a/Sources/Db/Schema/v2_1/LogErrors.php b/Sources/Db/Schema/v2_1/LogErrors.php index b08c4f03b57..ff079df5c5f 100644 --- a/Sources/Db/Schema/v2_1/LogErrors.php +++ b/Sources/Db/Schema/v2_1/LogErrors.php @@ -61,6 +61,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'url' => new Column( name: 'url', diff --git a/Sources/Db/Schema/v2_1/LogFloodcontrol.php b/Sources/Db/Schema/v2_1/LogFloodcontrol.php index 96dac986a4d..dc8d43de0fe 100644 --- a/Sources/Db/Schema/v2_1/LogFloodcontrol.php +++ b/Sources/Db/Schema/v2_1/LogFloodcontrol.php @@ -40,6 +40,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + not_null: true, ), 'log_time' => new Column( name: 'log_time', diff --git a/Sources/Db/Schema/v2_1/LogOnline.php b/Sources/Db/Schema/v2_1/LogOnline.php index 28cccb258d7..176cb3117d6 100644 --- a/Sources/Db/Schema/v2_1/LogOnline.php +++ b/Sources/Db/Schema/v2_1/LogOnline.php @@ -66,6 +66,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'url' => new Column( name: 'url', diff --git a/Sources/Db/Schema/v2_1/LogReportedComments.php b/Sources/Db/Schema/v2_1/LogReportedComments.php index 04537b48182..66463287ca3 100644 --- a/Sources/Db/Schema/v2_1/LogReportedComments.php +++ b/Sources/Db/Schema/v2_1/LogReportedComments.php @@ -65,6 +65,7 @@ public function __construct() name: 'member_ip', type: 'inet', size: 16, + default: null, ), 'comment' => new Column( name: 'comment', diff --git a/Sources/Db/Schema/v2_1/MemberLogins.php b/Sources/Db/Schema/v2_1/MemberLogins.php index 4c1f04785db..72d44dd7525 100644 --- a/Sources/Db/Schema/v2_1/MemberLogins.php +++ b/Sources/Db/Schema/v2_1/MemberLogins.php @@ -58,11 +58,13 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'ip2' => new Column( name: 'ip2', type: 'inet', size: 16, + default: null, ), ]; diff --git a/Sources/Db/Schema/v2_1/Members.php b/Sources/Db/Schema/v2_1/Members.php index 1b2c6948e7f..81a884d7834 100644 --- a/Sources/Db/Schema/v2_1/Members.php +++ b/Sources/Db/Schema/v2_1/Members.php @@ -224,11 +224,13 @@ public function __construct() name: 'member_ip', type: 'inet', size: 16, + default: null, ), 'member_ip2' => new Column( name: 'member_ip2', type: 'inet', size: 16, + default: null, ), 'secret_question' => new Column( name: 'secret_question', diff --git a/Sources/Db/Schema/v2_1/Messages.php b/Sources/Db/Schema/v2_1/Messages.php index 289c2053740..ec32c98dbb4 100644 --- a/Sources/Db/Schema/v2_1/Messages.php +++ b/Sources/Db/Schema/v2_1/Messages.php @@ -128,6 +128,7 @@ public function __construct() name: 'poster_ip', type: 'inet', size: 16, + default: null, ), 'smileys_enabled' => new Column( name: 'smileys_enabled', diff --git a/Sources/Db/Schema/v3_0/BanItems.php b/Sources/Db/Schema/v3_0/BanItems.php index 733c7960e86..a7b0a2b909a 100644 --- a/Sources/Db/Schema/v3_0/BanItems.php +++ b/Sources/Db/Schema/v3_0/BanItems.php @@ -54,11 +54,13 @@ public function __construct() name: 'ip_low', type: 'inet', size: 16, + default: null, ), 'ip_high' => new Column( name: 'ip_high', type: 'inet', size: 16, + default: null, ), 'hostname' => new Column( name: 'hostname', diff --git a/Sources/Db/Schema/v3_0/LogActions.php b/Sources/Db/Schema/v3_0/LogActions.php index 3a2b4d29432..34cb5751a9e 100644 --- a/Sources/Db/Schema/v3_0/LogActions.php +++ b/Sources/Db/Schema/v3_0/LogActions.php @@ -68,6 +68,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'action' => new Column( name: 'action', diff --git a/Sources/Db/Schema/v3_0/LogBanned.php b/Sources/Db/Schema/v3_0/LogBanned.php index 4772142d465..58de5bb6515 100644 --- a/Sources/Db/Schema/v3_0/LogBanned.php +++ b/Sources/Db/Schema/v3_0/LogBanned.php @@ -54,6 +54,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'email' => new Column( name: 'email', diff --git a/Sources/Db/Schema/v3_0/LogErrors.php b/Sources/Db/Schema/v3_0/LogErrors.php index c36f06dd7aa..3870599523f 100644 --- a/Sources/Db/Schema/v3_0/LogErrors.php +++ b/Sources/Db/Schema/v3_0/LogErrors.php @@ -61,6 +61,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'url' => new Column( name: 'url', diff --git a/Sources/Db/Schema/v3_0/LogOnline.php b/Sources/Db/Schema/v3_0/LogOnline.php index d28d018d62d..006dfa087aa 100644 --- a/Sources/Db/Schema/v3_0/LogOnline.php +++ b/Sources/Db/Schema/v3_0/LogOnline.php @@ -67,6 +67,7 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'url' => new Column( name: 'url', diff --git a/Sources/Db/Schema/v3_0/LogReportedComments.php b/Sources/Db/Schema/v3_0/LogReportedComments.php index 3139f0d5b3f..f78886777a7 100644 --- a/Sources/Db/Schema/v3_0/LogReportedComments.php +++ b/Sources/Db/Schema/v3_0/LogReportedComments.php @@ -65,6 +65,7 @@ public function __construct() name: 'member_ip', type: 'inet', size: 16, + default: null, ), 'comment' => new Column( name: 'comment', diff --git a/Sources/Db/Schema/v3_0/MemberLogins.php b/Sources/Db/Schema/v3_0/MemberLogins.php index f0d61a04c81..3b6957302aa 100644 --- a/Sources/Db/Schema/v3_0/MemberLogins.php +++ b/Sources/Db/Schema/v3_0/MemberLogins.php @@ -58,11 +58,13 @@ public function __construct() name: 'ip', type: 'inet', size: 16, + default: null, ), 'ip2' => new Column( name: 'ip2', type: 'inet', size: 16, + default: null, ), ]; diff --git a/Sources/Db/Schema/v3_0/Members.php b/Sources/Db/Schema/v3_0/Members.php index 9e3e2674c52..a4dc27fbb91 100644 --- a/Sources/Db/Schema/v3_0/Members.php +++ b/Sources/Db/Schema/v3_0/Members.php @@ -224,11 +224,13 @@ public function __construct() name: 'member_ip', type: 'inet', size: 16, + default: null, ), 'member_ip2' => new Column( name: 'member_ip2', type: 'inet', size: 16, + default: null, ), 'secret_question' => new Column( name: 'secret_question', diff --git a/Sources/Db/Schema/v3_0/Messages.php b/Sources/Db/Schema/v3_0/Messages.php index 4ffa60043af..a540f09d5bf 100644 --- a/Sources/Db/Schema/v3_0/Messages.php +++ b/Sources/Db/Schema/v3_0/Messages.php @@ -129,6 +129,7 @@ public function __construct() name: 'poster_ip', type: 'inet', size: 16, + default: null, ), 'smileys_enabled' => new Column( name: 'smileys_enabled', From b9852b6e4b1cdfc08091ea5ba4b1239f7986a087 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 5 Jul 2026 19:16:28 -0600 Subject: [PATCH 44/45] Don't try to convert smf_log_online.ip column; just drop and replace Signed-off-by: Jon Stovell --- Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php b/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php index 3201408bca5..c683524eec5 100644 --- a/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php @@ -55,9 +55,8 @@ public function execute(): bool $this->query('TRUNCATE TABLE {db_prefix}log_online'); - $this->convertIntegerColumnToInet($table, $table->columns['ip']); - - $table->normalize(); + $table->dropColumn($table->columns['ip']); + $table->addColumn($table->columns['ip']); $this->handleTimeout(); From d9edb56d47446f3604d8a2b43c4b7cd1b0c2043a Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 14 Apr 2026 14:39:43 -0600 Subject: [PATCH 45/45] Drops unused mod_prefs data This was no longer used for anything in SMF 2.1, but the column was retained in the members table. Signed-off-by: Jon Stovell --- Sources/Db/Schema/v3_0/Members.php | 7 --- .../Migration/v3_0/DropModPrefs.php | 57 +++++++++++++++++++ Sources/Maintenance/Tools/Upgrade.php | 1 + Sources/Tasks/GroupReq_Notify.php | 2 +- Sources/User.php | 8 --- 5 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 Sources/Maintenance/Migration/v3_0/DropModPrefs.php diff --git a/Sources/Db/Schema/v3_0/Members.php b/Sources/Db/Schema/v3_0/Members.php index a4dc27fbb91..e073da9005c 100644 --- a/Sources/Db/Schema/v3_0/Members.php +++ b/Sources/Db/Schema/v3_0/Members.php @@ -134,13 +134,6 @@ public function __construct() not_null: true, default: 0, ), - 'mod_prefs' => new Column( - name: 'mod_prefs', - type: 'varchar', - size: 20, - not_null: true, - default: '', - ), 'passwd' => new Column( name: 'passwd', type: 'varchar', diff --git a/Sources/Maintenance/Migration/v3_0/DropModPrefs.php b/Sources/Maintenance/Migration/v3_0/DropModPrefs.php new file mode 100644 index 00000000000..2182a4920b4 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/DropModPrefs.php @@ -0,0 +1,57 @@ +getCurrentStructure(); + + return isset($existing_structure['columns']['mod_prefs']); + } + + /** + * + */ + public function execute(): bool + { + $table = new Schema\v3_0\Members(); + $table->dropColumn('mod_prefs'); + + return true; + } +} diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index 431f3985d5e..ac4a00aa0e0 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -172,6 +172,7 @@ class Upgrade extends ToolsBase implements ToolsInterface Migration\v3_0\RecurringEvents::class, Migration\v3_0\HolidaysToEvents::class, Migration\v3_0\EventUids::class, + Migration\v3_0\DropModPrefs::class, Migration\v3_0\SpoofDetector::class, Migration\v3_0\SearchResultsPrimaryKey::class, Migration\v3_0\MailType::class, diff --git a/Sources/Tasks/GroupReq_Notify.php b/Sources/Tasks/GroupReq_Notify.php index 95a0d70cbab..37d76560529 100644 --- a/Sources/Tasks/GroupReq_Notify.php +++ b/Sources/Tasks/GroupReq_Notify.php @@ -105,7 +105,7 @@ public function execute(): bool Theme::loadEssential(); $request = Db::$db->query( - 'SELECT id_member, email_address, lngfile, member_name, mod_prefs + 'SELECT id_member, email_address, lngfile, member_name FROM {db_prefix}members WHERE id_member IN ({array_int:moderator_list}) ORDER BY lngfile', diff --git a/Sources/User.php b/Sources/User.php index 3af549bb48d..00bf23d8fad 100644 --- a/Sources/User.php +++ b/Sources/User.php @@ -575,14 +575,6 @@ class User implements \ArrayAccess */ public array $mod_cache = []; - /** - * @var string - * - * Moderator preferences. - * @todo This doesn't appear to be used anywhere. - */ - public string $mod_prefs = ''; - /** * @var bool *