Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9b9d710
Fixes some calls to JavaScript function doAutoSubmit()
Sesquipedalian Feb 26, 2026
8de0dfd
More reliable way to get theme dir in Maintenance::__construct()
Sesquipedalian Feb 26, 2026
b1ad5a7
Don't mess with redirects or canonical URLs during install or upgrade
Sesquipedalian Mar 1, 2026
50fb2f4
Removes unnecessary param for SMF\Db\Schema\Table::getInitializers()
Sesquipedalian Mar 4, 2026
ebe57a8
Removes old comments when rebuilding Settings.php during upgrade
Sesquipedalian Mar 4, 2026
0aafb87
Fixes type error when setting Config::$db_port in upgrader
Sesquipedalian Mar 5, 2026
32ab11e
Fixes some type casting inside Config::set()
Sesquipedalian Mar 6, 2026
ecb37f7
Loads minimal user data for User::$me in upgrader
Sesquipedalian Mar 8, 2026
34c9e9b
Only show box for detailed info about migration steps when requested
Sesquipedalian Mar 9, 2026
6123f83
Uses JavaScript to update "time elapsed" counter in maintenance tools
Sesquipedalian Mar 9, 2026
b9a63ff
Fixes bizarre embedded template in critical error message
Sesquipedalian Mar 13, 2026
fc05760
Handles generated columns correctly in Db\APIs\MySQL::backup_table()
Sesquipedalian Mar 19, 2026
7f38a4f
Maintenance\Cleanup\v3_0\OldFiles → Maintenance\Cleanup\OldFilesBase
Sesquipedalian Mar 20, 2026
0db57b2
Upgrades board descriptions after upgrading smileys
Sesquipedalian Mar 21, 2026
d62fcc3
Uses correct table names in PostgreSqlSequences migration step
Sesquipedalian Mar 29, 2026
9993daa
Explicit null default for expire_time in Db\Schema\v2_1\BanGroups
Sesquipedalian Mar 29, 2026
cea9f79
Fixes column name in SMF\Maintenance\Migration\v2_1\UserDrafts
Sesquipedalian Apr 17, 2026
355d78f
Always uses MigrationBase::query() for migration db queries
Sesquipedalian Apr 21, 2026
1920aba
Prevents error when no members have PM labels
Sesquipedalian Apr 21, 2026
b5fea45
Implements SMF\Db\Schema\Table::exists()
Sesquipedalian Apr 21, 2026
7591092
Always force a fresh download of maintenance tool JavaScript and CSS
Sesquipedalian Apr 22, 2026
cb2ee36
Fixes broken loop in PostgreSqlSequences migration step
Sesquipedalian Apr 22, 2026
663ad2f
Drop default for expire_time in ban_groups table before ALTER TABLE
Sesquipedalian Apr 22, 2026
5381b15
Fixes misbegotten query in SMF\Maintenance\Migration\v2_1\Ipv6BanItem
Sesquipedalian Apr 24, 2026
994c042
Rewrites SMF\Maintenance\Migration\v2_1\FixDates
Sesquipedalian Apr 24, 2026
9185aa1
Uses consistent delimiter in getNextSubstep() url
Sesquipedalian Apr 24, 2026
1ad08a3
Use standard SHOW COLUMNS command in SMF\Db\APIs\MySQL::list_columns()
Sesquipedalian Apr 24, 2026
7d1d4da
Improves reporting of results of ALTER TABLE etc. in db APIs
Sesquipedalian Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions Languages/en_US/Maintenance.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 5 additions & 5 deletions Sources/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -998,23 +998,23 @@ 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__);
}

if ((empty(self::$sourcedir) || !is_dir(realpath(self::$sourcedir))) && is_dir(self::$boarddir . '/Sources')) {
if ((empty(self::$sourcedir) || !is_dir((string) realpath(self::$sourcedir))) && is_dir(self::$boarddir . '/Sources')) {
self::$sourcedir = self::$boarddir . '/Sources';
}

if ((empty(self::$vendordir) || !is_dir(realpath(self::$vendordir))) && is_dir(self::$boarddir . '/vendor')) {
if ((empty(self::$vendordir) || !is_dir((string) realpath(self::$vendordir))) && is_dir(self::$boarddir . '/vendor')) {
self::$vendordir = self::$boarddir . '/vendor';
}

if ((empty(self::$packagesdir) || !is_dir(realpath(self::$packagesdir))) && is_dir(self::$boarddir . '/Packages')) {
if ((empty(self::$packagesdir) || !is_dir((string) realpath(self::$packagesdir))) && is_dir(self::$boarddir . '/Packages')) {
self::$packagesdir = self::$boarddir . '/Packages';
}

if ((empty(self::$languagesdir) || !is_dir(realpath(self::$languagesdir))) && is_dir(self::$boarddir . '/Languages')) {
if ((empty(self::$languagesdir) || !is_dir((string) realpath(self::$languagesdir))) && is_dir(self::$boarddir . '/Languages')) {
self::$languagesdir = self::$boarddir . '/Languages';
}

Expand Down
93 changes: 63 additions & 30 deletions Sources/Db/APIs/MySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -1373,15 +1390,15 @@ public function add_column(string $table_name, array $column_info, array $parame
$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;

// Now add the thing!
$this->query(
$result = $this->query(
'ALTER TABLE ' . $short_table_name . '
ADD ' . $this->create_query_column($column_info) . (empty($column_info['auto']) ? '' : ' primary key'),
[
'security_override' => true,
],
);

return true;
return $result !== false;
}

/**
Expand Down Expand Up @@ -1913,7 +1930,7 @@ public function create_table(string $table_name, array $columns, array $indexes
}

// Create the table!
$this->query(
$result = $this->query(
$table_query,
[
'security_override' => true,
Expand Down Expand Up @@ -1954,7 +1971,7 @@ public function create_table(string $table_name, array $columns, array $indexes
$this->drop_table($short_table_name . '_old');
}

return true;
return $result !== false;
}

/**
Expand All @@ -1980,15 +1997,14 @@ public function drop_table(string $table_name, array $parameters = [], string $e
$tables = $this->list_tables($database);

if (\in_array($full_table_name, $tables)) {
$query = 'DROP TABLE ' . $short_table_name;
$this->query(
$query,
$result = $this->query(
'DROP TABLE ' . $short_table_name,
[
'security_override' => true,
],
);

return true;
return $result !== false;
}

// Otherwise do 'nout.
Expand Down Expand Up @@ -2032,14 +2048,14 @@ public function rename_table(string $old_name, string $new_name, bool $allowed_r
return false;
}

$this->query(
$result = $this->query(
'ALTER TABLE ' . $short_old_name . ' RENAME ' . $short_new_name,
[
'security_override' => true,
],
);

return true;
return $result !== false;
}

/**
Expand Down Expand Up @@ -2088,14 +2104,12 @@ public function list_columns(string $table_name, bool $detail = false, array $pa
$database = !empty($match[2]) ? $match[2] : $this->name;

$result = $this->query(
'SELECT column_name "Field", COLUMN_TYPE "Type", is_nullable "Null", COLUMN_KEY "Key" , column_default "Default", extra "Extra", generation_expression "generation_expression"
FROM information_schema.columns
WHERE table_name = {string:table_name}
AND table_schema = {string:db_name}
ORDER BY ordinal_position',
'SHOW COLUMNS
FROM {identifier:table_name}
IN {identifier:db}',
[
'db' => strtr($database, ['`' => '']),
'table_name' => $real_table_name,
'db_name' => $this->name,
],
);
$columns = [];
Expand All @@ -2109,8 +2123,7 @@ public function list_columns(string $table_name, bool $detail = false, array $pa

// Can we split out the size?
if (preg_match('~^(.+?)\s*\((\d+)\)$~', $row['Type'], $matches)) {
$type = $matches[1];
$size = $matches[2];
[$type, $size] = $this->calculate_type($matches[1], (int) $matches[2], true);
} elseif (preg_match('~^(.+?)\s+unsigned$~', $row['Type'], $matches)) {
$type = $matches[1];
$size = null;
Expand All @@ -2135,12 +2148,32 @@ public function list_columns(string $table_name, bool $detail = false, array $pa
unset($unsigned);
}

// If this is a generated column, look up its generation expression.
if (str_contains($row['Extra'], 'GENERATED')) {
$columns[$row['Field']]['generation_expression'] = $row['generation_expression'];
$result2 = $this->query(
'SELECT generation_expression
FROM information_schema.columns
WHERE column_name = {string:field}
AND table_name = {string:table_name}
AND table_schema = {string:db}',
[
'db' => strtr($database, ['`' => '']),
'table_name' => $real_table_name,
'field' => $row['Field'],
],
);

[$generation_expression] = $this->fetch_row($result2);

$this->free_result($result2);

$columns[$row['Field']]['generation_expression'] = $this->unescape_string($generation_expression);

$columns[$row['Field']]['stored'] = str_contains($row['Extra'], 'STORED');
}
}
}

$this->free_result($result);

return $columns;
Expand Down Expand Up @@ -2215,15 +2248,15 @@ public function remove_column(string $table_name, string $column_name, array $pa

foreach ($columns as $column) {
if ($column['name'] == $column_name) {
$this->query(
$result = $this->query(
'ALTER TABLE ' . $short_table_name . '
DROP COLUMN ' . $column_name,
[
'security_override' => true,
],
);

return true;
return $result !== false;
}
}

Expand All @@ -2245,28 +2278,28 @@ public function remove_index(string $table_name, string $index_name, array $para
// If the name is primary we want the primary key!
if ($index['type'] == 'primary' && $index_name == 'primary') {
// Dropping primary key?
$this->query(
$result = $this->query(
'ALTER TABLE ' . $short_table_name . '
DROP PRIMARY KEY',
[
'security_override' => true,
],
);

return true;
return $result !== false;
}

if ($index['name'] == $index_name) {
// Drop the bugger...
$this->query(
$result = $this->query(
'ALTER TABLE ' . $short_table_name . '
DROP INDEX ' . $index_name,
[
'security_override' => true,
],
);

return true;
return $result !== false;
}
}

Expand Down Expand Up @@ -2399,11 +2432,11 @@ public function setSqlMode(string $mode = 'default'): bool
$sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT';
}

$this->query('SET SESSION sql_mode = {string:sql_mode}', [
$result = $this->query('SET SESSION sql_mode = {string:sql_mode}', [
'sql_mode' => $sql_mode,
]);

return true;
return $result !== false;
}

/**
Expand Down
Loading
Loading