Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
094fad9
Returns bool from Sapi::setTimeLimit()
Sesquipedalian May 12, 2025
6eea6d4
Makes a couple tweaks to Config::updateSettingsFile()
Sesquipedalian May 15, 2025
27e4055
Allows Lang::get() to work while installing
Sesquipedalian Apr 27, 2025
ed85743
Fixes Db::$db->detect_charset() for tables with no string columns
Sesquipedalian May 19, 2025
2977b67
Adds $reset param to Db::$db->detect_charset() method
Sesquipedalian May 11, 2025
0492bae
Renames Install language file to Maintenance
Sesquipedalian Apr 27, 2025
8909b0c
Adds SMF\Db\Schema and descendants
Sesquipedalian Apr 24, 2025
697732f
Adds new methods to DatabaseApiInterface
Sesquipedalian Apr 25, 2025
5727046
Rewrites installer
Sesquipedalian Apr 28, 2025
99321ba
Rewrites upgrader
Sesquipedalian May 19, 2025
56f0939
Updates composer.lock to point to new version of SMF BuildTools
Sesquipedalian May 19, 2025
ab28698
Tweaks .editorconfig to use spaces for indentation in composer.lock
Sesquipedalian May 19, 2025
fdc21c1
Fixes content of Sources/Maintenance/*/index.php files
Sesquipedalian May 19, 2025
3c4049b
Allows explicit handling of row format in MySQL::create_table()
Sesquipedalian May 19, 2025
555a7e5
Includes row format and collation in MySQL::table_sql() output
Sesquipedalian May 19, 2025
d48f71d
Updates version numbers
Sesquipedalian May 20, 2025
fc82f9b
Fixes logic in Table::alterIndex()
Sesquipedalian May 20, 2025
3312c03
PHP's safe_mode was deprecated long ago
Sesquipedalian May 20, 2025
f048b40
Fixes a bug in SMF\Tasks\Utf8EntityDecode::updateDirectly()
Sesquipedalian May 20, 2025
a36098e
Merge branch 'release-3.0' into 3.0/installer_and_upgrader
Sesquipedalian May 27, 2025
924b6c1
Bulletproofing for TasksDirCase::execute()
Sesquipedalian May 27, 2025
f7a6c91
Fixes language file name in installer
Sesquipedalian May 27, 2025
1b5f7bc
Fixes 'Undefined array key' in Utf8ConverterStep::convertDatabase()
Sesquipedalian May 27, 2025
05adf5b
Fixes 'Undefined array key' in Upgrade::backupDatabase()
Sesquipedalian May 28, 2025
640e261
Improves parsing in Maintenance::parseCliArguments()
Sesquipedalian May 28, 2025
7e55644
Improves error handling when updating the settings file in upgrader
Sesquipedalian May 27, 2025
862da9a
Uses Lang::getTxt() for all localized strings in the upgrader
Sesquipedalian May 27, 2025
d0c8f2d
Adds some methods to Maintenance\Tools\ToolsInterface
Sesquipedalian May 27, 2025
3daf906
Code deduplication
Sesquipedalian May 27, 2025
e963318
Improves logging in maintenance tools
Sesquipedalian May 27, 2025
223e3b9
Improves username validation in installer
Sesquipedalian May 28, 2025
683485e
Implements SMF\Migration\v3_0\PermissionChanges
Sesquipedalian May 28, 2025
a539ad0
Removes obsolete other/*.sql files
Sesquipedalian May 28, 2025
4305a83
Implements SMF\Migration\v3_0\IndexUpdates
Sesquipedalian May 28, 2025
2cff8a6
Adds PackageVersion to list of migration steps for 2.1 → 3.0
Sesquipedalian May 30, 2025
baf2e21
Revert "Fixes logic in Table::alterIndex()"
Sesquipedalian May 30, 2025
d56597b
Saves maintenance logs in Config::$boarddir . '/logs'
Sesquipedalian May 30, 2025
b2a365f
Ensures we only pass strings, not nulls, to Utf8EntityDecode::decode()
Sesquipedalian May 30, 2025
54c268a
Simplifies code to get substep names
Sesquipedalian May 30, 2025
4933416
Removes useless ToolsBase::quickFileWritable() method
Sesquipedalian Jun 1, 2025
76cb6fb
Handles missing directories better in ToolsBase::makeFilesWritable()
Sesquipedalian Jun 1, 2025
4efdaa6
Simplifies the code in Settings.php that redirects to the installer
Sesquipedalian Jun 1, 2025
db3b518
Prevent creating duplicate entries of `$upgradeData` in Settings.php
Sesquipedalian Jun 1, 2025
2c709ef
Always log 'welcome' step of installer
Sesquipedalian Jun 1, 2025
7996cd9
Improves Installer::determineTimezone()
Sesquipedalian Jun 1, 2025
32a63c4
Fixes glob pattern in SMF\Db\Schema\Table::getAll()
Sesquipedalian Jun 1, 2025
1e80b07
Moves initial data for smileys into table definitions
Sesquipedalian Jun 1, 2025
a742717
Fixes issues populating database in installer
Sesquipedalian Jun 1, 2025
9a63445
Adds support to installer for 'debug' URL param
Sesquipedalian Jun 1, 2025
1deb47a
Don't error in Security::secureDirectory() if dir is already secure
Sesquipedalian Jun 1, 2025
e359ab6
More logging improvements for installer and upgrader
Sesquipedalian Jun 2, 2025
4e182f9
Renames `$upgradeData` to `$maintenance_tool_progress`
Sesquipedalian Jun 2, 2025
b6ef9b9
Removes empty $initial_data property from db schema table classes
Sesquipedalian Jun 2, 2025
807fabd
Fixes indexes in SMF\Db\Schema\v2_1\LogFloodcontrol
Sesquipedalian Jun 2, 2025
3952c2d
Fixes indexes in SMF\Db\Schema\v2_1\LogOnline
Sesquipedalian Jun 2, 2025
69c2f0c
Fixes columns and indexes in SMF\Db\Schema\v2_1\LogPackages
Sesquipedalian Jun 2, 2025
ac7de6e
Fixes indexes in SMF\Db\Schema\v2_1\LogSpiderHits
Sesquipedalian Jun 2, 2025
f11a255
Fixes indexes in SMF\Db\Schema\v2_1\LogSubscribed
Sesquipedalian Jun 2, 2025
1367f09
Adds missing SMF\Db\Schema\v2_1\Settings class
Sesquipedalian Jun 2, 2025
490f530
Fixes indexes in SMF\Db\Schema\v2_1\Spiders
Sesquipedalian Jun 2, 2025
c6bdacf
Don't automatically continue upgrader when debug mode is enabled
Sesquipedalian Jun 4, 2025
e275e16
Fixes type detection for unsigned ints in MySql::list_columns()
Sesquipedalian Jun 3, 2025
add0b7b
Sets not_null to true on auto-increment columns in table definitions
Sesquipedalian Jun 3, 2025
0b9d018
Adds missing column properties in SMF\Db\Schema\v3_0\*
Sesquipedalian Jun 3, 2025
d4999a4
Fixes issue altering primary key when column auto-increments
Sesquipedalian Jun 3, 2025
5ad0935
Implements SMF\Db\Schema\Table::normalize()
Sesquipedalian Jun 4, 2025
4dc99dd
Adds table normalization substeps to upgrader
Sesquipedalian Jun 4, 2025
c46cf5b
Improves column type casting in SMF\Db\Schema\Column
Sesquipedalian Jun 4, 2025
11fc75f
Fixes a few cases where version_compare() was given malformed inputs
Sesquipedalian Jun 6, 2025
1470155
Improves handling of unexpected primary keys in Table::normalize()
Sesquipedalian Jun 6, 2025
b040a46
Merge branch 'release-3.0' into 3.0/installer_and_upgrader
Sesquipedalian Jun 9, 2025
41d4e81
Fixes a query identifier in Upgrade::upgradeOptions()
Sesquipedalian Jun 9, 2025
40f0a95
Perform all expected steps when re-running the upgrader
Sesquipedalian Jun 9, 2025
dd42711
Resolved a identifier issue from merging #8664
jdarwood007 Jun 9, 2025
a4dae5f
Fixes some mistakes in SMF\Db\Schema\v2_1\* classes
Sesquipedalian Jun 14, 2025
9625f8c
Fixes calls to MigrationBase::query()
Sesquipedalian Jun 11, 2025
febf589
Fixes broken query in Migration\v2_1\MembersLangUTF8::execute()
Sesquipedalian Jun 11, 2025
69bdea7
Does a better job defining the default charset in Table::__construct()
Sesquipedalian Jun 13, 2025
34a3e37
Allows Table::dropColumn() & Table::dropIndex() to accept string input
Sesquipedalian Jun 13, 2025
626606a
Implements Table::populate() method
Sesquipedalian Jun 13, 2025
877f075
Merge branch 'release-3.0' into 3.0/installer_and_upgrader
Sesquipedalian Jun 18, 2025
09828a9
Improves file checks in Upgrade::welcomeLogin()
Sesquipedalian Jun 20, 2025
1666c06
Adds missing use statements to Table class
Sesquipedalian Jun 22, 2025
d566eb2
Fixes a copy-paste mistake in Table::normalize()
Sesquipedalian Jun 22, 2025
0eb04de
Improves methods that define and create database indexes
Sesquipedalian Jun 21, 2025
b3c9577
Adds methods to rename database indexes
Sesquipedalian Jun 22, 2025
22889d7
Documentation fixes
Sesquipedalian Jun 22, 2025
4c3c906
Standardize case of 'PostgreSql' in class names, part 1
Sesquipedalian Jun 22, 2025
2d4a3d6
Standardize case of 'PostgreSql' in class names, part 2
Sesquipedalian Jun 22, 2025
b99e480
Standardizes code patterns in migration steps
Sesquipedalian Jun 21, 2025
9750c64
Improves efficiency in v2_1 migration steps
Sesquipedalian Jun 22, 2025
ddbd36e
Deletes unneeded schema & maintenance files after install/upgrade
Sesquipedalian Jun 23, 2025
5447981
Disable background tasks while installing, upgrading, etc.
Sesquipedalian Jun 26, 2025
f0c1dca
Merge branch 'release-3.0' into 3.0/installer_and_upgrader
Sesquipedalian Jul 16, 2025
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
5 changes: 4 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ tab_width = 4
trim_trailing_whitespace = true
charset = utf-8

[*.lock]
indent_style = space

[*.yml]
indent_style = space
indent_size = 2
indent_size = 2
4 changes: 2 additions & 2 deletions .github/crowdin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ files: [
translation: "Languages/%locale_with_underscore%/Help.php",
},
{
source: "Languages/en_US/Install.php",
translation: "Languages/%locale_with_underscore%/Install.php",
source: "Languages/en_US/Maintenance.php",
translation: "Languages/%locale_with_underscore%/Maintenance.php",
},
{
source: "Languages/en_US/Login.php",
Expand Down
449 changes: 272 additions & 177 deletions Languages/en_US/Install.php → Languages/en_US/Maintenance.php

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions Sources/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -1420,11 +1420,6 @@ public static function updateSettingsFile(array $config_vars, ?bool $keep_quotes
$config_vars['db_last_error'] = 0;
}

// Rebuilding should not be undertaken lightly, so we're picky about the parameter.
if (!is_bool($rebuild)) {
$rebuild = false;
}

$mtime = isset($mtime) ? (int) $mtime : (defined('TIME_START') ? TIME_START : $_SERVER['REQUEST_TIME']);

/*****************
Expand Down Expand Up @@ -1457,6 +1452,7 @@ public static function updateSettingsFile(array $config_vars, ?bool $keep_quotes
}

// When was Settings.php last changed?
clearstatcache();
$last_settings_change = filemtime($settingsFile);

// Get the current values of everything in Settings.php.
Expand Down Expand Up @@ -2233,7 +2229,7 @@ function ($a, $b) {
$success = self::safeFileWrite($settingsFile, $settingsText, $backupFile, $last_settings_change);

// Remember this in case updateSettingsFile is called twice.
$mtime = filemtime($settingsFile);
$mtime = microtime(true);

return $success;
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Cookie.php
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ public static function setLoginCookie(int $cookie_length, int $id, string $passw
// Backup and remove the old session.
$oldSessionData = $_SESSION;
$_SESSION = [];
session_destroy();
@session_destroy();

// Recreate and restore the new session.
Session::load();
Expand Down
257 changes: 246 additions & 11 deletions Sources/Db/APIs/MySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -886,10 +886,14 @@ public function connect_errno(): int
/**
*
*/
public function detect_charset(?string $table = null, ?string $column = null): string
public function detect_charset(?string $table = null, ?string $column = null, bool $reset = false): string
{
static $detected;

if ($reset) {
$detected = null;
}

// MySQL has a default character set for the database, but tables can
// use different character sets, and even columns within those tables
// can use different character sets again. So figuring out the actual
Expand All @@ -908,11 +912,9 @@ public function detect_charset(?string $table = null, ?string $column = null): s
INNER JOIN information_schema.COLUMNS AS c ON (c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME)
INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY AS a ON (t.TABLE_COLLATION = a.COLLATION_NAME)
WHERE t.TABLE_SCHEMA = {string:db_name}
AND c.DATA_TYPE IN ({array_string:types})
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME, c.COLUMN_NAME',
[
'db_name' => $this->name,
'types' => ['enum', 'varchar', 'char', 'tinytext', 'text', 'mediumtext', 'longtext'],
],
);

Expand Down Expand Up @@ -1245,7 +1247,7 @@ public function table_sql(string $tableName): string
$this->free_result($result);

// Probably InnoDB.... and it might have a comment.
$schema_create .= $crlf . ') ENGINE=' . $row['Engine'] . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');
$schema_create .= $crlf . ') ENGINE=' . $row['Engine'] . ' ROW_FORMAT=' . $row['Row_format'] . ' COLLATE=' . $row['Collation'] . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');

return $schema_create;
}
Expand Down Expand Up @@ -1722,7 +1724,7 @@ public function create_table(string $table_name, array $columns, array $indexes
$short_table_name = str_replace('{db_prefix}', $this->prefix, $table_name);

// First - no way do we touch SMF tables.
if (in_array(strtolower($short_table_name), $this->reservedTables)) {
if (!defined('SMF_INSTALLING') && in_array(strtolower($short_table_name), $this->reservedTables)) {
return false;
}

Expand Down Expand Up @@ -1824,6 +1826,29 @@ public function create_table(string $table_name, array $columns, array $indexes
}
}

// Which row format (if any) should be specified?
switch ($parameters['engine']) {
case 'InnoDB':
if (!in_array(strtoupper($parameters['row_format'] ?? ''), ['REDUNDANT', 'COMPACT', 'DYNAMIC', 'COMPRESSED'])) {
$parameters['row_format'] = 'DYNAMIC';
}
break;

case 'MyISAM':
if (!in_array(strtoupper($parameters['row_format'] ?? ''), ['FIXED', 'DYNAMIC', 'COMPRESSED'])) {
unset($parameters['row_format']);
}
break;

default:
unset($parameters['row_format']);
break;
}

if (isset($parameters['row_format'])) {
$table_query .= ' ROW_FORMAT=' . $parameters['row_format'];
}

// Create the table!
$this->query(
'',
Expand Down Expand Up @@ -2142,20 +2167,230 @@ public function remove_index(string $table_name, string $index_name, array $para
return false;
}

/**************************************
* Methods used during installion, etc.
**************************************/

/**
*
*/
public function getMinimumVersion(): string
{
return '8.0.35';
}

/**
*
*/
public function isSupported(): bool
{
return function_exists('mysqli_connect');
}

/**
*
*/
public function skipSelectDatabase(): bool
{
return false;
}

/**
*
*/
public function getDefaultUser(): string
{
return ini_get('mysql.default_user') === false ? '' : ini_get('mysql.default_user');
}

/**
*
*/
public function getDefaultPassword(): string
{
return ini_get('mysql.default_password') === false ? '' : ini_get('mysql.default_password');
}

/**
*
*/
public function getDefaultHost(): string
{
return ini_get('mysql.default_host') === false ? '' : ini_get('mysql.default_host');
}

/**
*
*/
public function getDefaultPort(): int
{
return ini_get('mysql.default_port') === false ? 3306 : (int) ini_get('mysql.default_port');
}

/**
*
*/
public function getDefaultName(): string
{
return 'smf';
}

/**
*
*/
public function checkConfiguration(): bool
{
return true;
}

/**
*
*/
public function hasPermissions(): bool
{
// Find database user privileges.
$privs = [];
$get_privs = self::$db->query('', 'SHOW PRIVILEGES', []);

while ($row = self::$db->fetch_assoc($get_privs)) {
if ($row['Privilege'] == 'Alter') {
$privs[] = $row['Privilege'];
}
}
self::$db->free_result($get_privs);

// Check for the ALTER privilege.
return !(!in_array('Alter', $privs));
}

/**
*
*/
public function validatePrefix(&$value): bool
{
$value = preg_replace('~[^A-Za-z0-9_\$]~', '', $value);

return true;
}

/**
*
*/
public function alwaysHasDb(): bool
{
return false;
}

/**
*
*/
public function setSqlMode(string $mode = 'default'): bool
{
$sql_mode = '';

if ($mode === 'strict') {
$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}', [
'sql_mode' => $sql_mode,
]);

return true;
}

/**
*
*/
public function processError(string $error_msg, string $query): mixed
{
$mysqli_errno = mysqli_errno($this->connection);

$error_query = in_array(substr(trim($query), 0, 11), ['INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR', 'INSERT IGNO']);

// Error numbers:
// 1016: Can't open file '....MYI'
// 1050: Table already exists.
// 1054: Unknown column name.
// 1060: Duplicate column name.
// 1061: Duplicate key name.
// 1062: Duplicate entry for unique key.
// 1068: Multiple primary keys.
// 1072: Key column '%s' doesn't exist in table.
// 1091: Can't drop key, doesn't exist.
// 1146: Table doesn't exist.
// 2013: Lost connection to server during query.

if ($mysqli_errno == 1016) {
if (preg_match('~\'([^\.\']+)~', $error_msg, $match) != 0 && !empty($match[1])) {
mysqli_query($this->connection, 'REPAIR TABLE `' . $match[1] . '`');
$result = mysqli_query($this->connection, $query);

if ($result !== false) {
return $result;
}
}
} elseif ($mysqli_errno == 2013) {
$this->connection = mysqli_connect($this->server, $this->user, $this->passwd);
mysqli_select_db($this->connection, $this->name);

if ($this->connection) {
$result = mysqli_query($this->connection, $query);

if ($result !== false) {
return $result;
}
}
}
// Duplicate column name... should be okay ;).
elseif (in_array($mysqli_errno, [1060, 1061, 1068, 1091])) {
return false;
}
// Duplicate insert... make sure it's the proper type of query ;).
elseif (in_array($mysqli_errno, [1054, 1062, 1146]) && $error_query) {
return false;
}
// Creating an index on a non-existent column.
elseif ($mysqli_errno == 1072) {
return false;
} elseif ($mysqli_errno == 1050 && substr(trim($query), 0, 12) == 'RENAME TABLE') {
return false;
}
// Testing for legacy tables or columns? Needed for 1.0 & 1.1 scripts.
elseif (in_array($mysqli_errno, [1054, 1146]) && in_array(substr(trim($query), 0, 7), ['SELECT ', 'SHOW CO'])) {
return false;
}

// If a table already exists don't go potty.
if (in_array(substr(trim($query), 0, 8), ['CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U'])) {
if (strpos($error_msg, 'exist') !== false) {
return false;
}
} elseif (strpos(trim($query), 'INSERT ') !== false) {
if (strpos($error_msg, 'duplicate') !== false) {
return false;
}
}

return true;
}

/******************
* Internal methods
******************/

/**
* Constructor.
* Prepares this instance for use.
*
* If $options is empty, correct settings will be determined automatically.
*
* @param array $options An array of database options.
*/
protected function __construct(array $options = [])
protected function initialize(array $options = []): void
{
parent::__construct();
if ($this !== DatabaseApi::$db) {
return;
}

// If caller was explicit about non_fatal, respect that.
$non_fatal = !empty($options['non_fatal']);
Expand All @@ -2166,7 +2401,7 @@ protected function __construct(array $options = [])
$options = ['non_fatal' => true, 'dont_select_db' => true];
}

$this->initiate(Config::$ssi_db_user, Config::$ssi_db_passwd, $options);
$this->connect(Config::$ssi_db_user, Config::$ssi_db_passwd, $options);
}

// Either we aren't in SSI mode, or it failed.
Expand All @@ -2175,7 +2410,7 @@ protected function __construct(array $options = [])
$options = ['dont_select_db' => SMF == 'SSI'];
}

$this->initiate(Config::$db_user, Config::$db_passwd, $options);
$this->connect(Config::$db_user, Config::$db_passwd, $options);
}

// Safe guard here, if there isn't a valid connection let's put a stop to it.
Expand Down Expand Up @@ -2229,7 +2464,7 @@ protected function __construct(array $options = [])
* @param string $passwd The database password
* @param array $options An array of database options
*/
protected function initiate(string $user, string $passwd, array $options = []): void
protected function connect(string $user, string $passwd, array $options = []): void
{
$server = ($this->persist ? 'p:' : '') . $this->server;

Expand Down
Loading