diff --git a/Languages/en_US/Maintenance.php b/Languages/en_US/Maintenance.php index 38b817e242..3f3f4ee447 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/Config.php b/Sources/Config.php index 05fa3155df..bac937dcaa 100644 --- a/Sources/Config.php +++ b/Sources/Config.php @@ -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'; } diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index 27106cd73f..fc44181c19 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'); } } diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index ff7674844e..33aaab3ed8 100644 --- a/Sources/Db/Schema/Table.php +++ b/Sources/Db/Schema/Table.php @@ -563,6 +563,7 @@ public function populate(bool $replace = false): int /** * Gets all known table schemas. * + * @param string $schema_version A string like 'v3_0'. * @return array All known table schemas. */ final public static function getAll(string $schema_version): array @@ -594,15 +595,16 @@ final public static function getAll(string $schema_version): array } /** - * Gets all known table schemas. + * Gets database initializer queries for the indicated SMF version. * + * @param string $schema_version A string like '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 []; diff --git a/Sources/Db/Schema/v2_1/BanGroups.php b/Sources/Db/Schema/v2_1/BanGroups.php index c9aec8cb9c..89dad594d2 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', diff --git a/Sources/Db/Schema/v3_0/Initialize/Base.php b/Sources/Db/Schema/v3_0/Initialize/Base.php index 498b91235d..94cc1aed0a 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/Cleanup/CleanupBase.php b/Sources/Maintenance/Cleanup/CleanupBase.php index 4f37911097..7bdce49640 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 1f04f87e28..7d376fea78 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 e69755f663..7b0717e342 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 6bf62068a1..140713af43 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/Maintenance.php b/Sources/Maintenance/Maintenance.php index 8588a978f8..d28af320f2 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; @@ -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/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php b/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php index 357608b120..570fedc75d 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'], ], ); } diff --git a/Sources/Maintenance/Migration/v3_0/PostgreSqlFunctions.php b/Sources/Maintenance/Migration/v3_0/PostgreSqlFunctions.php index 493a23bf26..bf3ace41f8 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 29a50f32ad..72b77c54b6 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, ]); diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php index abe6ee09fa..8442c464b0 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!'); } @@ -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')); diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index 5af6b9bb82..ebb5f5d08c 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, @@ -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, ], ]; @@ -979,18 +978,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; } @@ -1223,10 +1219,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']); } @@ -1611,6 +1608,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') { diff --git a/Sources/QueryString.php b/Sources/QueryString.php index c92a0a3ed5..554130c65c 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; } diff --git a/Themes/default/Admin.template.php b/Themes/default/Admin.template.php index 2be9b2150e..bfc0c72d49 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/MaintenanceTemplate.php b/Themes/default/MaintenanceTemplate.php index e95ae46dbf..fd6006d996 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()); + } + } '; @@ -311,7 +327,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 '

', diff --git a/Themes/default/ManageNews.template.php b/Themes/default/ManageNews.template.php index aa39082a84..2b629a8e44 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 2925b00102..7997c5e0f9 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 6beda4e4d7..4f0b3ec558 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 d168725fc1..5ff3312b06 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 12fd0b60a1..96cec3ffb1 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 ' - '; } }