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..e4b712a4f9 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; } @@ -1373,7 +1390,7 @@ 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'), [ @@ -1381,7 +1398,7 @@ public function add_column(string $table_name, array $column_info, array $parame ], ); - return true; + return $result !== false; } /** @@ -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, @@ -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; } /** @@ -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. @@ -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; } /** @@ -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 = []; @@ -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; @@ -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; @@ -2215,7 +2248,7 @@ 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, [ @@ -2223,7 +2256,7 @@ public function remove_column(string $table_name, string $column_name, array $pa ], ); - return true; + return $result !== false; } } @@ -2245,7 +2278,7 @@ 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', [ @@ -2253,12 +2286,12 @@ public function remove_index(string $table_name, string $index_name, array $para ], ); - 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, [ @@ -2266,7 +2299,7 @@ public function remove_index(string $table_name, string $index_name, array $para ], ); - return true; + return $result !== false; } } @@ -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; } /** diff --git a/Sources/Db/APIs/PostgreSQL.php b/Sources/Db/APIs/PostgreSQL.php index b15525ee35..bcfd20294b 100644 --- a/Sources/Db/APIs/PostgreSQL.php +++ b/Sources/Db/APIs/PostgreSQL.php @@ -1314,7 +1314,7 @@ public function add_column(string $table_name, array $column_info, array $parame } // Now add the thing! - $this->query( + $result = $this->query( 'ALTER TABLE ' . $short_table_name . ' ADD COLUMN ' . $column_info['name'] . ' ' . $type . $generated, [ @@ -1334,7 +1334,7 @@ public function add_column(string $table_name, array $column_info, array $parame return $this->change_column($table_name, $column_info['name'], $column_info); } - return true; + return $result !== false; } /** @@ -1876,7 +1876,7 @@ public function create_table(string $table_name, array $columns, array $indexes $table_query .= ')'; // Create the table! - $this->query( + $result = $this->query( $table_query, [ 'security_override' => true, @@ -1931,7 +1931,7 @@ public function create_table(string $table_name, array $columns, array $indexes $this->drop_table($table_name . '_old'); } - return true; + return $result !== false; } /** @@ -1966,7 +1966,7 @@ public function drop_table(string $table_name, array $parameters = [], string $e $sequence_query = 'DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq'; // drop them - $this->query( + $result = $this->query( $table_query, [ 'security_override' => true, @@ -1981,7 +1981,7 @@ public function drop_table(string $table_name, array $parameters = [], string $e $this->transaction('commit'); - return true; + return $result !== false; } // Otherwise do 'nout. @@ -2025,14 +2025,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 TO ' . $short_new_name, [ 'security_override' => true, ], ); - return true; + return $result !== false; } /** @@ -2198,7 +2198,7 @@ public function remove_column(string $table_name, string $column_name, array $pa ); } - $this->query( + $result = $this->query( 'ALTER TABLE ' . $short_table_name . ' DROP COLUMN ' . $column_name, [ @@ -2206,7 +2206,7 @@ public function remove_column(string $table_name, string $column_name, array $pa ], ); - return true; + return $result !== false; } } @@ -2234,7 +2234,7 @@ 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 ' . $real_table_name . ' DROP CONSTRAINT ' . $index['name'], [ @@ -2242,19 +2242,19 @@ public function remove_index(string $table_name, string $index_name, array $para ], ); - return true; + return $result !== false; } if ($index['name'] == $index_name) { // Drop the bugger... - $this->query( + $result = $this->query( 'DROP INDEX ' . $real_table_name . '_' . $index_name, [ 'security_override' => true, ], ); - return true; + return $result !== false; } } diff --git a/Sources/Db/Schema/Column.php b/Sources/Db/Schema/Column.php index 63539d6f2c..f5210c1cc9 100644 --- a/Sources/Db/Schema/Column.php +++ b/Sources/Db/Schema/Column.php @@ -91,8 +91,6 @@ class Column * @var bool * * Set this to true to drop the default during an ALTER TABLE operation. - * - * This is not set by __construct(). It can be set afterward. */ public bool $drop_default = false; @@ -116,6 +114,8 @@ class Column * Only applicable to integer columns. * @param ?string $charset The character set for string data. * Only applicable to string types. If null, will be set automatically. + * @param bool $drop_default Whether to drop the column's default during + * ALTER TABLE operations. Default: false. */ public function __construct( string $name, @@ -126,6 +126,7 @@ public function __construct( string|float|int|bool|null $default = null, ?bool $auto = null, ?string $charset = null, + bool $drop_default = false, ) { $this->name = strtolower($name); $this->type = strtolower($type); @@ -134,7 +135,7 @@ public function __construct( $this->default = $default === 'NULL' ? null : $default; } - foreach (['auto', 'size', 'unsigned', 'not_null'] as $var) { + foreach (['auto', 'size', 'unsigned', 'not_null', 'drop_default'] as $var) { if (isset($var)) { $this->{$var} = ${$var}; } diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index 7bd0239212..56a4f26477 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; } /** @@ -636,23 +676,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/v2_1/BanGroups.php b/Sources/Db/Schema/v2_1/BanGroups.php index c9aec8cb9c..165cbac388 100644 --- a/Sources/Db/Schema/v2_1/BanGroups.php +++ b/Sources/Db/Schema/v2_1/BanGroups.php @@ -61,6 +61,9 @@ public function __construct() name: 'expire_time', type: 'int', unsigned: true, + not_null: false, + default: null, + drop_default: true, ), 'cannot_access' => new Column( name: 'cannot_access', diff --git a/Sources/Db/Schema/v3_0/BanGroups.php b/Sources/Db/Schema/v3_0/BanGroups.php index 221135416a..0b61dbdc14 100644 --- a/Sources/Db/Schema/v3_0/BanGroups.php +++ b/Sources/Db/Schema/v3_0/BanGroups.php @@ -63,6 +63,7 @@ public function __construct() unsigned: true, not_null: false, default: null, + drop_default: true, ), '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 2043f29264..5c9997a639 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/AgreementUpdate.php b/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php index adf662aaac..eaae86caec 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/BoardPermissionsView.php b/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php index b77b509c14..04bec7f32b 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 cfe950fc98..5fab23bd87 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 6f4d5c0dde..f0bdc1b205 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 10a6ccae77..26a91fb77d 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/FixDates.php b/Sources/Maintenance/Migration/v2_1/FixDates.php index 81e46379c6..3f9b22e7f7 100644 --- a/Sources/Maintenance/Migration/v2_1/FixDates.php +++ b/Sources/Maintenance/Migration/v2_1/FixDates.php @@ -19,6 +19,12 @@ use SMF\Maintenance\Maintenance; use SMF\Maintenance\Migration\MigrationBase; +/** + * @todo Find a SQL standard way of handling this. + * Maybe DATEADD with a calc on YEAR() to find what it takes to make it 1004? + * PostgreSQL does not have DATEFROMPARTS, but does have make_date (9.4>), + * which would be similar the more standard DATEFROMPARTS. + */ class FixDates extends MigrationBase { /******************* @@ -39,121 +45,61 @@ class FixDates extends MigrationBase */ public function execute(): bool { - // @@ TODO: Find a SQL standard way of handling this. Maybe DATEADD with a calc on YEAR() to find what it takes to make it 1004? - // PostgreSQL does not have DATEFROMPARTS, but does have make_date (9.4>), which would be similar the more standard DATEFROMPARTS. - - // PostgreSQL does the query a bit different. - $is_pgsql = Db::$db->title === POSTGRE_TITLE; - - if (Maintenance::getCurrentStart() < 1 && $is_pgsql) { - $this->query( - 'UPDATE {db_prefix}calendar - SET start_date = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM start_date) < 1004 THEN 1004 END, EXTRACT(MONTH FROM start_date), EXTRACT(DAY FROM start_date))::date - WHERE EXTRACT(YEAR FROM start_date) < 1004', - [], - ); - } elseif (Maintenance::getCurrentStart() < 1) { - $this->query( - 'UPDATE {db_prefix}calendar - SET start_date = DATE(CONCAT(1004, {literal:-}, MONTH(start_date), {literal:-}, DAY(start_date))) - WHERE YEAR(start_date) < 1004', - [], - ); + if (Db::$db->title === POSTGRE_TITLE) { + $boilerplate = 'UPDATE {db_prefix}%1$s + SET %2$s = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM %2$s) < 1004 THEN 1004 END, EXTRACT(MONTH FROM %2$s), EXTRACT(DAY FROM %2$s))::date + WHERE EXTRACT(YEAR FROM %2$s) < 1004'; + + $bday_query = 'UPDATE {db_prefix}members + SET birthdate = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM birthdate) < 1004 THEN 1004 END, CASE WHEN EXTRACT(MONTH FROM birthdate) < 1 THEN 1 ELSE EXTRACT(MONTH FROM birthdate) END, CASE WHEN EXTRACT(DAY FROM birthdate) < 1 THEN 1 ELSE EXTRACT(DAY FROM birthdate) END)::date + WHERE EXTRACT(YEAR FROM birthdate) < 1004 OR EXTRACT(MONTH FROM birthdate) < 1 OR EXTRACT(DAY FROM birthdate) < 1'; + + } else { + $boilerplate = 'UPDATE {db_prefix}%1$s + SET %2$s = DATE(CONCAT(1004, {literal:-}, MONTH(%2$s), {literal:-}, DAY(%2$s))) + WHERE YEAR(%2$s) < 1004'; + + $bday_query = 'UPDATE {db_prefix}members + SET birthdate = DATE(CONCAT(IF(YEAR(birthdate) < 1004, 1004, YEAR(birthdate)), {literal:-}, IF(MONTH(birthdate) < 1, 1, MONTH(birthdate)), {literal:-}, IF(DAY(birthdate) < 1, 1, DAY(birthdate)))) + WHERE YEAR(birthdate) < 1004 OR MONTH(birthdate) < 1 OR DAY(birthdate) < 1'; } - Maintenance::setCurrentStart(); - $this->handleTimeout(); - - if (Maintenance::getCurrentStart() < 2 && $is_pgsql) { - $this->query( - 'UPDATE {db_prefix}calendar - SET end_date = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM end_date) < 1004 THEN 1004 END, EXTRACT(MONTH FROM end_date), EXTRACT(DAY FROM end_date))::date - WHERE EXTRACT(YEAR FROM end_date) < 1004', - [], - ); - } elseif (Maintenance::getCurrentStart() < 2) { - $this->query( - 'UPDATE {db_prefix}calendar - SET end_date = DATE(CONCAT(1004, {literal:-}, MONTH(end_date), {literal:-}, DAY(end_date))) - WHERE YEAR(end_date) < 1004', - [], - ); + + if (Maintenance::getCurrentStart() < 1) { + $this->query(\sprintf($boilerplate, 'calendar', 'start_date')); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); } - Maintenance::setCurrentStart(); - $this->handleTimeout(); - - if (Maintenance::getCurrentStart() < 3 && $is_pgsql) { - $this->query( - 'UPDATE {db_prefix}calendar_holidays - SET event_date = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM event_date) < 1004 THEN 1004 END, EXTRACT(MONTH FROM event_date), EXTRACT(DAY FROM event_date))::date - WHERE EXTRACT(YEAR FROM event_date) < 1004', - [], - ); - } elseif (Maintenance::getCurrentStart() < 3) { - $this->query( - 'UPDATE {db_prefix}calendar_holidays - SET event_date = DATE(CONCAT(1004, {literal:-}, MONTH(event_date), {literal:-}, DAY(event_date))) - WHERE YEAR(event_date) < 1004', - [], - ); + + if (Maintenance::getCurrentStart() < 2) { + $this->query(\sprintf($boilerplate, 'calendar', 'end_date')); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); } - Maintenance::setCurrentStart(); - $this->handleTimeout(); - - if (Maintenance::getCurrentStart() < 4 && $is_pgsql) { - $this->query( - 'UPDATE {db_prefix}log_spider_stats - SET stat_date = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM stat_date) < 1004 THEN 1004 END, EXTRACT(MONTH FROM stat_date), EXTRACT(DAY FROM stat_date))::date - WHERE EXTRACT(YEAR FROM stat_date) < 1004', - [], - ); - } elseif (Maintenance::getCurrentStart() < 4) { - $this->query( - 'UPDATE {db_prefix}log_spider_stats - SET stat_date = DATE(CONCAT(1004, {literal:-}, MONTH(stat_date), {literal:-}, DAY(stat_date))) - WHERE YEAR(stat_date) < 1004', - [], - ); + + if (Maintenance::getCurrentStart() < 3) { + $this->query(\sprintf($boilerplate, 'calendar_holidays', 'event_date')); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); } - Maintenance::setCurrentStart(); - $this->handleTimeout(); - if (Maintenance::getCurrentStart() < 5 && $is_pgsql) { - $this->query( - 'UPDATE {db_prefix}log_spider_stats - SET birthdate = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM birthdate) < 1004 THEN 1004 END, CASE WHEN EXTRACT(MONTH FROM birthdate) < 1 THEN 1 ELSE EXTRACT(MONTH FROM birthdate) END, CASE WHEN EXTRACT(DAY FROM birthdate) < 1 THEN 1 ELSE EXTRACT(DAY FROM birthdate) END)::date - WHERE EXTRACT(YEAR FROM birthdate) < 1004 OR EXTRACT(MONTH FROM birthdate) < 1 OR EXTRACT(DAY FROM birthdate) < 1', - [], - ); - } elseif (Maintenance::getCurrentStart() < 5) { - $this->query( - 'UPDATE {db_prefix}members - SET birthdate = DATE(CONCAT(IF(YEAR(birthdate) < 1004, 1004, YEAR(birthdate)), {literal:-}, IF(MONTH(birthdate) < 1, 1, MONTH(birthdate)), {literal:-}, IF(DAY(birthdate) < 1, 1, DAY(birthdate)))) - WHERE YEAR(birthdate) < 1004 OR MONTH(birthdate) < 1 OR DAY(birthdate) < 1', - [], - ); + if (Maintenance::getCurrentStart() < 4) { + $this->query(\sprintf($boilerplate, 'log_spider_stats', 'stat_date')); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); } - Maintenance::setCurrentStart(); - $this->handleTimeout(); - if (Maintenance::getCurrentStart() < 6 && $is_pgsql) { - $this->query( - 'UPDATE {db_prefix}members - SET birthdate = concat_ws({literal:-}, CASE WHEN EXTRACT(YEAR FROM birthdate) < 1004 THEN 1004 END, CASE WHEN EXTRACT(MONTH FROM birthdate) < 1 THEN 1 ELSE EXTRACT(MONTH FROM birthdate) END, CASE WHEN EXTRACT(DAY FROM birthdate) < 1 THEN 1 ELSE EXTRACT(DAY FROM birthdate) END)::date - WHERE EXTRACT(YEAR FROM birthdate) < 1004 OR EXTRACT(MONTH FROM birthdate) < 1 OR EXTRACT(DAY FROM birthdate) < 1', - [], - ); - } elseif (Maintenance::getCurrentStart() < 6) { - $this->query( - 'UPDATE {db_prefix}members - SET birthdate = DATE(CONCAT(IF(YEAR(birthdate) < 1004, 1004, YEAR(birthdate)), {literal:-}, IF(MONTH(birthdate) < 1, 1, MONTH(birthdate)), {literal:-}, IF(DAY(birthdate) < 1, 1, DAY(birthdate)))) - WHERE YEAR(birthdate) < 1004 OR MONTH(birthdate) < 1 OR DAY(birthdate) < 1', - [], - ); + if (Maintenance::getCurrentStart() < 5) { + $this->query($bday_query); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); } - Maintenance::setCurrentStart(); - $this->handleTimeout(); - if (Maintenance::getCurrentStart() < 7) { + if (Maintenance::getCurrentStart() < 6) { Db::$db->change_column( '{db_prefix}log_activity', 'DATE', @@ -162,22 +108,63 @@ public function execute(): bool 'default' => null, ], ); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); + } + + if (Maintenance::getCurrentStart() < 7) { + Db::$db->change_column( + '{db_prefix}calendar', + 'start_date', + ['default' => '1004-01-01'], + ); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); + } + + if (Maintenance::getCurrentStart() < 8) { + Db::$db->change_column( + '{db_prefix}calendar', + 'end_date', + ['default' => '1004-01-01'], + ); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); + } + + if (Maintenance::getCurrentStart() < 9) { + Db::$db->change_column( + '{db_prefix}calendar_holidays', + 'event_date', + ['default' => '1004-01-01'], + ); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); } - Maintenance::setCurrentStart(); - $this->handleTimeout(); - $fixes = [ - ['tbl' => '{db_prefix}calendar', 'col' => 'start_date'], - ['tbl' => '{db_prefix}calendar', 'col' => 'end_date'], - ['tbl' => '{db_prefix}calendar_holidays', 'col' => 'event_date'], - ['tbl' => '{db_prefix}log_spider_stats', 'col' => 'stat_date'], - ['tbl' => '{db_prefix}members', 'col' => 'birthdate'], - ]; + if (Maintenance::getCurrentStart() < 10) { + Db::$db->change_column( + '{db_prefix}log_spider_stats', + 'stat_date', + ['default' => '1004-01-01'], + ); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); + } - for ($key = Maintenance::getCurrentStart(); $key < \count($fixes); Maintenance::setCurrentStart()) { - $fix = $fixes[$key - 7]; + if (Maintenance::getCurrentStart() < 11) { + Db::$db->change_column( + '{db_prefix}members', + 'stat_date', + ['birthdate' => '1004-01-01'], + ); - Db::$db->change_column($fix['tbl'], $fix['col'], ['default' => '1004-01-01']); + Maintenance::setCurrentStart(); $this->handleTimeout(); } diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php b/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php index 20edf01d35..59418c9141 100644 --- a/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php +++ b/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php @@ -88,23 +88,6 @@ public function execute(): bool // Note, We now support MySQL 8+, which means we could use INET6_ATON. // The upgrade logic was built this way and should remain the same. // If changed, a full upgrade from 2.0 to 3.0 would need to be tested. - $this->quote( - 'UPDATE IGNORE {db_prefix}ban_items - SET ip_low = - UNHEX( - hex( - INET_ATON(concat(ip_low1,{literal:.},ip_low2,{literal:.},ip_low3,{literal:.},ip_low4)) - ) - ), - ip_high = - UNHEX( - hex( - INET_ATON(concat(ip_high1,{literal:.},ip_high2,{literal:.},ip_high3,{literal:.},ip_high4)) - ) - ) - WHERE ip_low1 > 0', - ); - $this->query( 'UPDATE IGNORE {db_prefix}ban_items SET ip_low = @@ -121,7 +104,6 @@ public function execute(): bool ) WHERE ip_low1 > 0', ); - } $this->handleTimeout(++$start); diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6Base.php b/Sources/Maintenance/Migration/v2_1/Ipv6Base.php index 68ff0e508d..5109187944 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/Likes.php b/Sources/Maintenance/Migration/v2_1/Likes.php index 61a5866871..7ff4bf2e40 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 de32bc69fa..61222eb5c5 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 70be7572da..7c160bcdf0 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/Permissions.php b/Sources/Maintenance/Migration/v2_1/Permissions.php index 0c8cbcfa90..f7f543ae7c 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 d307db5414..6eacbc7b07 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); } @@ -116,10 +113,12 @@ public function execute(): bool while (!$is_done) { $this->handleTimeout($start); + $label_info = []; + $member_list = []; $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} @@ -131,9 +130,6 @@ public function execute(): bool ], ); - $label_info = []; - $member_list = []; - while ($row = Db::$db->fetch_assoc($get_labels)) { $member_list[] = $row['id_member']; @@ -149,6 +145,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]; @@ -172,7 +173,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 +186,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 +207,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 +250,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 +279,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 +292,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})', @@ -311,32 +312,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; } diff --git a/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php b/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php index 357608b120..72a6a29210 100644 --- a/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlSequences.php @@ -238,7 +238,7 @@ public function isCandidate(): bool */ public function execute(): bool { - for ($i = Maintenance::getCurrentStart(); $i < \count($this->sequences); Maintenance::setCurrentStart()) { + for ($i = Maintenance::getCurrentStart(); $i < \count($this->sequences); $i++) { $this->handleTimeout(); $value = $this->sequences[$i]; @@ -248,9 +248,11 @@ public function execute(): bool [ 'key' => Config::$db_prefix . $value['key'], 'field' => $value['field'], - 'table' => $value['table'], + 'table' => Config::$db_prefix . $value['table'], ], ); + + Maintenance::setCurrentStart(); } return true; diff --git a/Sources/Maintenance/Migration/v2_1/Smileys.php b/Sources/Maintenance/Migration/v2_1/Smileys.php index 007e8c11c4..c4536b8e37 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 7e32267a52..6abe26cccc 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); } @@ -74,8 +70,8 @@ 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 + $request = $this->query( + 'SELECT id_group, id_profile, add_deny, permission FROM {db_prefix}board_permissions WHERE permission = {literal:post_unapproved_topics}', [], @@ -86,7 +82,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 +95,7 @@ public function execute(): bool '{db_prefix}board_permissions', [ 'id_group' => 'int', - 'id_board' => 'int', + 'id_profile' => 'int', 'permission' => 'string', 'add_deny' => 'int', ], diff --git a/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php b/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php index f0d44b5ee5..e9a500a81a 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); } diff --git a/Sources/Maintenance/Migration/v3_0/ConvertToInnoDb.php b/Sources/Maintenance/Migration/v3_0/ConvertToInnoDb.php index e3d0ffa935..a0a82b4b07 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 42e559964b..28507c2792 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 446978eae0..bf1389e5f2 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 199524a028..e5697819e9 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 57c527cd77..4a85c9fa1c 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 c8e1a005d4..d60f295789 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 17061c1b74..75cd3dabb1 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/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/Migration/v3_0/RecurringEvents.php b/Sources/Maintenance/Migration/v3_0/RecurringEvents.php index ce10d2a8e5..c3cc255fb6 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}', 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..90f98f97f0 100644 --- a/Themes/default/MaintenanceTemplate.php +++ b/Themes/default/MaintenanceTemplate.php @@ -36,16 +36,17 @@ public static function header(): void