Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d865ba7
Fixes some calls to JavaScript function doAutoSubmit()
Sesquipedalian Feb 26, 2026
6d2f6b9
More reliable way to get theme dir in Maintenance::__construct()
Sesquipedalian Feb 26, 2026
7f26f0b
Don't mess with redirects or canonical URLs during install or upgrade
Sesquipedalian Mar 1, 2026
b1ab997
Removes unnecessary param for SMF\Db\Schema\Table::getInitializers()
Sesquipedalian Mar 4, 2026
0512208
Removes old comments when rebuilding Settings.php during upgrade
Sesquipedalian Mar 4, 2026
25a9d7e
Fixes type error when setting Config::$db_port in upgrader
Sesquipedalian Mar 5, 2026
f3ebdad
Fixes some type casting inside Config::set()
Sesquipedalian Mar 6, 2026
d68cfdd
Loads minimal user data for User::$me in upgrader
Sesquipedalian Mar 8, 2026
1e16f9f
Only show box for detailed info about migration steps when requested
Sesquipedalian Mar 9, 2026
3f2cea4
Uses JavaScript to update "time elapsed" counter in maintenance tools
Sesquipedalian Mar 9, 2026
a00b2c1
Fixes bizarre embedded template in critical error message
Sesquipedalian Mar 13, 2026
df88b3b
Handles generated columns correctly in Db\APIs\MySQL::backup_table()
Sesquipedalian Mar 19, 2026
ce666e9
Maintenance\Cleanup\v3_0\OldFiles → Maintenance\Cleanup\OldFilesBase
Sesquipedalian Mar 20, 2026
d58e773
Upgrades board descriptions after upgrading smileys
Sesquipedalian Mar 21, 2026
6071a23
Uses correct table names in PostgreSqlSequences migration step
Sesquipedalian Mar 29, 2026
117cc3c
Explicit null default for expire_time in Db\Schema\v2_1\BanGroups
Sesquipedalian Mar 29, 2026
a82770c
Fixes column name in SMF\Maintenance\Migration\v2_1\UserDrafts
Sesquipedalian Apr 17, 2026
2c20bc3
Always uses MigrationBase::query() for migration db queries
Sesquipedalian Apr 21, 2026
7fe10fd
Prevents error when no members have PM labels
Sesquipedalian Apr 21, 2026
e0832be
Implements SMF\Db\Schema\Table::exists()
Sesquipedalian Apr 21, 2026
83bd00e
Always force a fresh download of maintenance tool JavaScript and CSS
Sesquipedalian Apr 22, 2026
b6a15e8
Fixes broken loop in PostgreSqlSequences migration step
Sesquipedalian Apr 22, 2026
56fd36a
Drop default for expire_time in ban_groups table before ALTER TABLE
Sesquipedalian Apr 22, 2026
08f3c1a
Fixes misbegotten query in SMF\Maintenance\Migration\v2_1\Ipv6BanItem
Sesquipedalian Apr 24, 2026
d9c5ca8
Rewrites SMF\Maintenance\Migration\v2_1\FixDates
Sesquipedalian Apr 24, 2026
da8c9e5
Uses consistent delimiter in getNextSubstep() url
Sesquipedalian Apr 24, 2026
de61eda
Use standard SHOW COLUMNS command in SMF\Db\APIs\MySQL::list_columns()
Sesquipedalian Apr 24, 2026
fc34779
Improves reporting of results of ALTER TABLE etc. in db APIs
Sesquipedalian Apr 30, 2026
ba66d8f
Rewrites upgrader substeps for adding IPv6 support in 2.1
Sesquipedalian May 12, 2026
c187523
Uses property hook to set SMF\Db\Schema\Table::$default_charset
Sesquipedalian May 14, 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