From 094fad90f6d5e3b7934b23cb9b1c187363ffcaa6 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 11 May 2025 23:35:45 -0600 Subject: [PATCH 01/90] Returns bool from Sapi::setTimeLimit() Signed-off-by: Jon Stovell --- Sources/Sapi.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/Sapi.php b/Sources/Sapi.php index 98b1043acc4..24119a78dac 100644 --- a/Sources/Sapi.php +++ b/Sources/Sapi.php @@ -377,12 +377,14 @@ public static function httpsOn(): bool * Makes call to the Server API (SAPI) to increase the time limit. * * @param int $limit Requested amount of time, defaults to 600 seconds. + * @return bool True on success, or false on failure. */ - public static function setTimeLimit(int $limit = 600) + public static function setTimeLimit(int $limit = 600): bool { try { - set_time_limit($limit); + return set_time_limit($limit); } catch (\Exception $e) { + return false; } } From 6eea6d4000d28fab2babca92d439ce83a21351b5 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Thu, 15 May 2025 12:15:47 -0600 Subject: [PATCH 02/90] Makes a couple tweaks to Config::updateSettingsFile() Signed-off-by: Jon Stovell --- Sources/Config.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Sources/Config.php b/Sources/Config.php index 94d572e653e..a06d31f285c 100644 --- a/Sources/Config.php +++ b/Sources/Config.php @@ -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']); /***************** @@ -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. @@ -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; } From 27e4055cf7c4ad5b81da21b75c35bf5dcf123c2e Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 27 Apr 2025 14:38:29 -0600 Subject: [PATCH 03/90] Allows Lang::get() to work while installing Signed-off-by: Jon Stovell --- Sources/Lang.php | 55 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/Sources/Lang.php b/Sources/Lang.php index cc9fd357328..27513022264 100644 --- a/Sources/Lang.php +++ b/Sources/Lang.php @@ -466,24 +466,29 @@ public static function get(bool $use_cache = true): array { // Either we don't use the cache, or its expired. if (!$use_cache || (Utils::$context['languages'] = CacheApi::get('known_languages', !empty(CacheApi::$enable) && CacheApi::$enable < 1 ? 86400 : 3600)) == null) { - // If we don't have our theme information yet, let's get it. - if (empty(Theme::$current->settings['default_theme_dir'])) { - Theme::load(0, false); - } + // Special case during install. + if (defined('SMF_INSTALLING')) { + $language_directories = [Config::$languagesdir]; + } else { + // If we don't have our theme information yet, let's get it. + if (empty(Theme::$current->settings['default_theme_dir'])) { + Theme::load(0, false); + } - // Default language directories to try. - $language_directories = [ - Config::$languagesdir, - Theme::$current->settings['default_theme_dir'] . '/languages', - ]; + // Default language directories to try. + $language_directories = [ + Config::$languagesdir, + Theme::$current->settings['default_theme_dir'] . '/languages', + ]; - if (!empty(Theme::$current->settings['actual_theme_dir']) && Theme::$current->settings['actual_theme_dir'] != Theme::$current->settings['default_theme_dir']) { - $language_directories[] = Theme::$current->settings['actual_theme_dir'] . '/languages'; - } + if (!empty(Theme::$current->settings['actual_theme_dir']) && Theme::$current->settings['actual_theme_dir'] != Theme::$current->settings['default_theme_dir']) { + $language_directories[] = Theme::$current->settings['actual_theme_dir'] . '/languages'; + } - // We possibly have a base theme directory. - if (!empty(Theme::$current->settings['base_theme_dir'])) { - $language_directories[] = Theme::$current->settings['base_theme_dir'] . '/languages'; + // We possibly have a base theme directory. + if (!empty(Theme::$current->settings['base_theme_dir'])) { + $language_directories[] = Theme::$current->settings['base_theme_dir'] . '/languages'; + } } // Remove any duplicates. @@ -597,7 +602,15 @@ public static function txtExists(string|array $txt_key, string $var = 'txt', ?st // Load the specified file. if (is_string($file) && !isset(self::$already_loaded[$file])) { - self::load($file . (!str_contains($file, 'ThemeStrings') ? '+ThemeStrings' : '') . (!str_contains($file, 'Modifications') ? '+Modifications' : ''), $lang); + self::load($file, $lang, force_reload: true); + + if (!str_contains($file, 'Modifications')) { + self::load('Modifications', $lang, fatal: false, force_reload: true); + } + + if (!str_contains($file, 'ThemeStrings')) { + self::load('ThemeStrings', $lang, fatal: false, force_reload: true); + } } $target = &self::${$var}; @@ -740,7 +753,15 @@ public static function getTxt(string|array $txt_key, array $args = [], string $v ) ) ) { - self::load($file . (!str_contains($file, 'ThemeStrings') ? '+ThemeStrings' : '') . (!str_contains($file, 'Modifications') ? '+Modifications' : ''), $lang, force_reload: true); + self::load($file, $lang, force_reload: true); + + if (!str_contains($file, 'Modifications')) { + self::load('Modifications', $lang, fatal: false, force_reload: true); + } + + if (!str_contains($file, 'ThemeStrings')) { + self::load('ThemeStrings', $lang, fatal: false, force_reload: true); + } } // Don't waste time when getting a simple string. From ed85743cd1c0a564ee286e31e006d499238143a6 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 18 May 2025 22:17:38 -0600 Subject: [PATCH 04/90] Fixes Db::$db->detect_charset() for tables with no string columns Signed-off-by: Jon Stovell --- Sources/Db/APIs/MySQL.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index 8659df14ba0..05b9cf1df7e 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -908,11 +908,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'], ], ); From 2977b67204247307c4adaf16bd56a48f1289276e Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sat, 10 May 2025 22:59:43 -0600 Subject: [PATCH 05/90] Adds $reset param to Db::$db->detect_charset() method Signed-off-by: Jon Stovell --- Sources/Db/APIs/MySQL.php | 6 +++++- Sources/Db/APIs/PostgreSQL.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index 05b9cf1df7e..5223a838fbc 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -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 diff --git a/Sources/Db/APIs/PostgreSQL.php b/Sources/Db/APIs/PostgreSQL.php index c32f1d89126..aa3b7e6989b 100644 --- a/Sources/Db/APIs/PostgreSQL.php +++ b/Sources/Db/APIs/PostgreSQL.php @@ -881,10 +881,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; + } + // PostgreSQL uses one character set per database. So sane and simple. if (!isset($detected)) { $request = $this->query( From 0492bae6ecba53eead80f499913c0fc6f5d98de5 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 27 Apr 2025 14:37:38 -0600 Subject: [PATCH 06/90] Renames Install language file to Maintenance Signed-off-by: Jon Stovell --- .github/crowdin.yml | 4 +- .../en_US/{Install.php => Maintenance.php} | 429 +++++++++++------- other/install.php | 8 +- other/upgrade.php | 28 +- 4 files changed, 277 insertions(+), 192 deletions(-) rename Languages/en_US/{Install.php => Maintenance.php} (88%) diff --git a/.github/crowdin.yml b/.github/crowdin.yml index 5be8ba54373..eaea855b75a 100644 --- a/.github/crowdin.yml +++ b/.github/crowdin.yml @@ -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", diff --git a/Languages/en_US/Install.php b/Languages/en_US/Maintenance.php similarity index 88% rename from Languages/en_US/Install.php rename to Languages/en_US/Maintenance.php index 01beda3e83f..bdaf02debed 100644 --- a/Languages/en_US/Install.php +++ b/Languages/en_US/Maintenance.php @@ -1,10 +1,81 @@ (does not work on all servers.)'; +$txt['error_message_click'] = 'Click here'; +$txt['error_message_try_again'] = 'Try again.'; + +// Errors and warnings. +$txt['critical_error'] = 'Critical Error!'; +$txt['warning'] = 'Warning!'; +$txt['error_db_queries'] = 'Some of the queries were not executed properly. This could be caused by an unsupported (development or old) version of your database software.

Technical information about the queries:'; +$txt['error_php_too_low'] = 'Warning! You do not appear to have a version of PHP installed on your webserver that meets SMF’s minimum installations requirements.

Please ask your host to upgrade.'; +$txt['error_dir_not_writable'] = 'The directory "{dir}" has to be writable to continue the upgrade. Please make sure permissions are correctly set to allow this.'; +$txt['error_unknown'] = 'Unknown Error!'; +$txt['query_unsuccessful'] = 'Unsuccessful!'; +$txt['query_failed'] = 'This query: {QUERY_STRING} +Caused the error: {QUERY_ERROR}'; + +// Progress bars and steps. +$txt['maintenance_progress'] = 'Progress'; +$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} +}'; + +// File Permissions. +$txt['chmod_linux_info'] = 'If you have a shell account, the convenient below command can automatically correct permissions on these files'; +$txt['error_windows_chmod'] = 'You are on a windows server and some crucial files are not writable. Please ask your host to give write permissions to the user PHP is running under for the files in your SMF installation. The following files or directories need to be writable:'; + +// FTP +$txt['ftp_setup_why_info'] = 'Some files need to be writable for SMF to work properly. This step allows you to let the installer make them writable for you. However, in some cases it will not work. In this case, please make the following files 777 (writable, 755 on some hosts):'; +$txt['ftp_login'] = 'Your FTP connection information'; +$txt['ftp_login_info'] = 'This web installer needs your FTP information in order to automate the installation for you. Please note that none of this information is saved in your installation, it is just used to setup SMF.'; +$txt['ftp_server'] = 'Server'; +$txt['ftp_server_info'] = 'The address (often localhost) and port for your FTP server.'; +$txt['ftp_port'] = 'Port'; +$txt['ftp_username'] = 'Username'; +$txt['ftp_username_info'] = 'The username to login with. This will not be saved anywhere.'; +$txt['ftp_password'] = 'Password'; +$txt['ftp_password_info'] = 'The password to login with. This will not be saved anywhere.'; +$txt['ftp_path'] = 'Install Path'; +$txt['ftp_path_info'] = 'This is the relative path you use in your FTP client.'; +$txt['ftp_path_found_info'] = 'The path in the box above was automatically detected.'; +$txt['ftp_path_help'] = 'Your FTP path is the path you see when you log in to your FTP client. It commonly starts with "
www
", "
public_html
", or "
httpdocs
", but it should include the directory SMF is in too, such as "/public_html/forum". It is different from your URL and full path.

Files in this path may be overwritten, so make sure it is correct.'; +$txt['ftp_path_help_close'] = 'Close'; +$txt['ftp_connect'] = 'Connect'; +$txt['ftp_checking_writable'] = 'Checking files are writable'; +$txt['ftp_setup'] = 'FTP Connection Information'; +$txt['ftp_setup_info'] = 'This installer can connect via FTP to fix the files that need to be writable and are not. If this does not work for you, you will have to go in manually and make the files writable. Please note that this does not support SSL right now.'; +$txt['ftp_setup_why'] = 'What is this step for?'; +$txt['ftp_setup_again'] = 'to test if these files are writable again.'; +$txt['error_ftp_no_connect'] = 'Unable to connect to FTP server with this combination of details.'; + +// Install steps. $txt['install_step_welcome'] = 'Welcome'; $txt['install_step_writable'] = 'Writable check'; $txt['install_step_forum'] = 'Forum Settings'; @@ -13,27 +84,113 @@ $txt['install_step_admin'] = 'Admin account'; $txt['install_step_delete'] = 'Finalize install'; -$txt['smf_installer'] = 'SMF Installer'; -$txt['installer_language'] = 'Language'; -$txt['installer_language_set'] = 'Set'; -$txt['congratulations'] = 'Congratulations, the installation process is complete!'; -$txt['congratulations_help'] = 'If at any time you need support, or SMF fails to work properly, please remember that help is available if you need it.'; -$txt['still_writable'] = 'Your installation directory is still writable. It is a good idea to chmod it, so that it is not writable for security reasons.'; -$txt['delete_installer'] = 'Click here to delete this install.php file now.'; -$txt['delete_installer_maybe'] = '(does not work on all servers.)'; -$txt['go_to_your_forum'] = 'Now you can see your newly installed forum and begin to use it. You should first make sure you are logged in, after which you will be able to access the administration center.'; -$txt['good_luck'] = 'Good luck!
Simple Machines'; +// Upgrade steps. +$txt['upgrade_step_login'] = 'Login'; +$txt['upgrade_step_options'] = 'Upgrade Options'; +$txt['upgrade_step_backup'] = 'Backup'; +$txt['upgrade_step_migration'] = 'Migrations'; +$txt['upgrade_step_cleanup'] = 'Cleanup'; +$txt['upgrade_step_delete'] = 'Finalize Upgrade'; +// Installer - Welcome. $txt['install_welcome'] = 'Welcome'; $txt['install_welcome_desc'] = 'Welcome to SMF. This script will guide you through the process for installing {SMF_VERSION}. We will gather a few details about your forum over the next few steps, and after a couple of minutes your forum will be ready for use.'; -$txt['install_no_https'] = 'Your environment does not support https streams. Certain functions, e.g., receiving updates from simplemachines.org, will not work.'; +$txt['error_script_outdated'] = 'This install script is out of date! The current version of SMF is {smfVersion}, but this install script is for {yourVersion}.

+ It is recommended that you visit the Simple Machines website to ensure you are installing the latest version.'; +$txt['error_already_installed'] = 'The installer has detected that you already have SMF installed. It is strongly advised that you do not try to overwrite an existing installation, continuing with installation may result in the loss or corruption of existing data.

If you wish to upgrade please visit the Simple Machines Website and download the latest upgrade package.

If you wish to overwrite your existing installation, including all data, it is recommended that you delete the existing database tables and replace Settings.php and try again.'; +$txt['error_db_missing'] = 'The installer was unable to detect any database support in PHP. Please ask your host to ensure that PHP was compiled with the desired database, or that the proper extension is being loaded.'; +$txt['error_session_missing'] = 'The installer was unable to detect sessions support in your server’s installation of PHP. Please ask your host to ensure that PHP was compiled with session support (which in fact is the PHP default, meaning your host currently has explicitly disabled it).'; +$txt['error_missing_files'] = 'Unable to find crucial installation files in the directory of this script!

Please make sure you uploaded the entire installation package, including the sql file, and then try again.'; +$txt['error_session_save_path'] = 'Please inform your host that the session.save_path specified in php.ini is not valid! It needs to be changed to a directory that exists and is writable by the user PHP is running under.
'; +$txt['error_mod_security'] = 'The installer has detected the mod_security module is installed on your web server. Mod_security will block submitted forms even before SMF gets a say in anything. SMF has a built-in security scanner that will work more effectively than mod_security and that will not block submitted forms.

More information about disabling mod_security'; +$txt['error_mod_security_no_write'] = 'The installer has detected the mod_security module is installed on your web server. Mod_security will block submitted forms even before SMF gets a say in anything. SMF has a built-in security scanner that will work more effectively than mod_security and that will not block submitted forms.

More information about disabling mod_security

Alternatively, you may wish to use your ftp client to chmod .htaccess in the forum directory to be writable (777), and then refresh this page.'; $txt['install_no_mbstring'] = 'Your environment does not support the required mbstring library. Please enable mbstring and try again.'; $txt['install_no_fileinfo'] = 'Your environment does not support the required fileinfo library. Please enable fileinfo and try again.'; -$txt['install_all_lovely'] = 'We have completed some initial tests on your server and everything appears to be in order. Simply click the "Continue" button below to get started.'; +$txt['install_no_https'] = 'Your environment does not support https streams. Certain functions, e.g., receiving updates from simplemachines.org, will not work.'; -$txt['user_refresh_install'] = 'Forum Refreshed'; +// Installer - Check Files Writable. + +// Installer - Database Settings. +$txt['db_settings'] = 'Database Server Settings'; +$txt['db_settings_info'] = 'These are the settings to use for your database server. If you do not know the values, you should ask your host what they are.'; +$txt['db_settings_type'] = 'Database type'; +$txt['db_settings_type_info'] = 'Multiple supported database types were detected, which do you wish to use? Please note that running pre-SMF 2.0 RC3 along with newer SMF versions in the same PostgreSQL database is not supported. You need to upgrade your older installations in that case.'; +$txt['db_settings_server'] = 'Server name'; +$txt['db_settings_server_info'] = 'This is nearly always localhost, so if you do not know, try localhost.'; +$txt['db_settings_port'] = 'Database port'; +$txt['db_settings_port_info'] = 'Leave blank to use the default'; +$txt['db_settings_username'] = 'Username'; +$txt['db_settings_username_info'] = 'Fill in the username you need to connect to your database here.
If you do not know what it is, try the username of your ftp account, most of the time they are the same.'; +$txt['db_settings_password'] = 'Password'; +$txt['db_settings_password_info'] = 'Put the password you need to connect to your database here.
If you do not know this, you should try the password to your ftp account.'; +$txt['db_settings_database'] = 'Database name'; +$txt['db_settings_database_info'] = 'Fill in the name of the database you want to use for SMF to store its data in.'; +$txt['db_settings_database_info_note'] = 'If this database does not exist, this installer will try to create it.'; +$txt['db_settings_prefix'] = 'Table prefix'; +$txt['db_settings_prefix_info'] = 'The prefix for every table in the database. Do not install two forums with the same prefix!
This key allows for multiple installations in one database.'; +$txt['error_db_prefix_reserved'] = 'The prefix that you entered is a reserved prefix. Please enter another prefix.'; +$txt['error_db_prefix_numeric'] = 'The selected database type does not support the use of numeric prefixes.'; + +// Installer - Forum Settings. +$txt['install_settings'] = 'Forum Settings'; +$txt['install_settings_info'] = 'This page requires you to define a few key settings for your forum. SMF has automatically detected key settings for you.'; +$txt['install_settings_name'] = 'Forum name'; +$txt['install_settings_name_info'] = 'This is the name of your forum, ie. "The Testing Forum".'; +$txt['install_settings_name_default'] = 'My Community'; +$txt['install_settings_url'] = 'Forum URL'; +$txt['install_settings_url_info'] = 'This is the URL to your forum without the trailing "/"!.
In most cases, you can leave the default value in this box alone - it is usually right.'; +$txt['install_settings_reg_mode'] = 'Registration Mode'; +$txt['install_settings_reg_modes'] = 'Registration Modes'; +$txt['install_settings_reg_immediate'] = 'Immediate Registration'; +$txt['install_settings_reg_email'] = 'Email Activation'; +$txt['install_settings_reg_admin'] = 'Admin Approval'; +$txt['install_settings_reg_disabled'] = 'Registration Disabled'; +$txt['install_settings_reg_mode_info'] = 'This field allows you to change the mode of registration on installation to prevent unwanted registrations.'; +$txt['install_settings_compress'] = 'Gzip Output'; +$txt['install_settings_compress_title'] = 'Compress output to save bandwidth.'; +// In this string, you can translate the word "PASS" to change what it says when the test passes. +$txt['install_settings_compress_info'] = 'This function does not work properly on all servers, but it can save you a lot of bandwidth.
Click here to test it. (it should just say "PASS".)'; +$txt['install_settings_dbsession'] = 'Database Sessions'; +$txt['install_settings_dbsession_title'] = 'Use the database for sessions instead of using files.'; +$txt['install_settings_dbsession_info1'] = 'This feature is almost always for the best, as it makes sessions more dependable.'; +$txt['install_settings_dbsession_info2'] = 'This feature is generally a good idea, but it may not work properly on this server.'; +$txt['install_settings_stats'] = 'Allow stat Collection'; +$txt['install_settings_stats_title'] = 'Allow Simple Machines to collect basic stats monthly'; +$txt['install_settings_stats_info'] = 'If enabled, this will allow Simple Machines to visit your site once a month to collect basic statistics. This will help us make decisions as to which configurations to optimize the software for. For more information please visit our info page.'; +$txt['force_ssl'] = 'Enable SSL'; +$txt['force_ssl_label'] = 'Force SSL throughout the forum'; +$txt['force_ssl_info'] = 'Make sure SSL and HTTPS are supported throughout the forum, otherwise your forum may become inaccessible'; +$txt['error_pg_scs'] = 'PostgreSQL is configured incorrectly. Please turn on the standard_conforming_strings configuration parameter.'; +$txt['error_utf8_version'] = 'The current version of your database does not support the use of the UTF-8 character set. You can still install SMF without any problems, but only with UTF-8 support unchecked. If you would like to switch over to UTF-8 in the future (e.g. after the database server of your forum has been upgraded to version >= {utf8_version}), you can convert your forum to UTF-8 through the admin panel.'; + +// Installer - Database Population. +$txt['db_populate'] = 'Populated Database'; $txt['user_refresh_install_desc'] = 'While installing, the installer found that (with the details you provided) one or more of the tables this installer might create already existed.
Any missing tables in your installation have been recreated with the default data, but no data was deleted from existing tables.'; +$txt['db_populate_info'] = 'Your settings have now been saved and the database has been populated with all the data required to get your forum up and running. Summary of population:'; +$txt['db_populate_info2'] = 'Click "Continue" to progress to the admin account creation page.'; + +// Installer - Admin Account. +$txt['user_settings'] = 'Create your account'; +$txt['user_settings_info'] = 'The installer will now create a new administrator account for you.'; +$txt['user_settings_username'] = 'Your username'; +$txt['user_settings_username_info'] = 'Choose the name you want to login with.
This can be changed later.'; +$txt['user_settings_password'] = 'Password'; +$txt['user_settings_password_info'] = 'Fill in your preferred password here and remember it well!'; +$txt['user_settings_again'] = 'Password'; +$txt['user_settings_again_info'] = '(just for verification).'; +$txt['user_settings_admin_email'] = 'Administrator email address'; +$txt['user_settings_admin_email_info'] = 'Provide your email address. This must be a valid email address!'; +$txt['user_settings_server_email'] = 'Webmaster email address'; +$txt['user_settings_server_email_info'] = 'Provide the email address that SMF will use to send emails. This must be a valid email address!'; +$txt['user_settings_database'] = 'Database Password'; +$txt['user_settings_database_info'] = 'For security reasons, the installer requires that you supply the database password to create an administrator account.'; +// Installer - Delete Install. +$txt['congratulations_help'] = 'If at any time you need support, or SMF fails to work properly, please remember that help is available if you need it.'; +$txt['go_to_your_forum'] = 'Now you can see your newly installed forum and begin to use it. You should first make sure you are logged in, after which you will be able to access the administration center.'; +$txt['good_luck'] = 'Good luck!
Simple Machines'; + +// Installer - Defaults. $txt['default_topic_subject'] = 'Welcome to SMF!'; $txt['default_topic_message'] = 'Welcome to Simple Machines Forum!

We hope you enjoy using your forum.  If you have any problems, please feel free to [url=https://www.simplemachines.org/community/index.php]ask us for assistance[/url].

Thanks!
Simple Machines'; $txt['default_board_name'] = 'General Discussion'; @@ -48,7 +205,6 @@ $txt['default_akyhne_smileyset_name'] = 'Akyhne’s Set'; $txt['default_legacy_smileyset_name'] = '2.0 Default'; $txt['default_theme_name'] = 'SMF Default Theme - Curve2'; - $txt['default_administrator_group'] = 'Administrator'; $txt['default_global_moderator_group'] = 'Global Moderator'; $txt['default_moderator_group'] = 'Moderator'; @@ -57,7 +213,6 @@ $txt['default_full_group'] = 'Full Member'; $txt['default_senior_group'] = 'Sr. Member'; $txt['default_hero_group'] = 'Hero Member'; - $txt['default_smiley_smiley'] = 'Smiley'; $txt['default_wink_smiley'] = 'Wink'; $txt['default_cheesy_smiley'] = 'Cheesy'; @@ -81,101 +236,102 @@ $txt['default_police_smiley'] = 'Police'; $txt['default_angel_smiley'] = 'Angel'; -$txt['error_message_click'] = 'Click here'; -$txt['error_message_try_again'] = 'to try this step again.'; -$txt['error_message_bad_try_again'] = 'to try installing anyway, but note that this is strongly discouraged.'; +// Upgrade - Welcome Login Page +$txt['updating_smf_installation'] = 'Updating Your SMF Installation!'; +$txt['error_no_javascript'] = 'No javascript support was detected! Please enable javascript in your browser settings.'; +$txt['upgrade_ready_proceed'] = 'Thank you for choosing to upgrade to SMF {SMF_VERSION}. All files appear to be in place and the upgrade can now proceed.'; -$txt['install_settings'] = 'Forum Settings'; -$txt['install_settings_info'] = 'This page requires you to define a few key settings for your forum. SMF has automatically detected key settings for you.'; -$txt['install_settings_name'] = 'Forum name'; -$txt['install_settings_name_info'] = 'This is the name of your forum, ie. "The Testing Forum".'; -$txt['install_settings_name_default'] = 'My Community'; -$txt['install_settings_url'] = 'Forum URL'; -$txt['install_settings_url_info'] = 'This is the URL to your forum without the trailing "/"!.
In most cases, you can leave the default value in this box alone - it is usually right.'; -$txt['install_settings_reg_mode'] = 'Registration Mode'; -$txt['install_settings_reg_modes'] = 'Registration Modes'; -$txt['install_settings_reg_immediate'] = 'Immediate Registration'; -$txt['install_settings_reg_email'] = 'Email Activation'; -$txt['install_settings_reg_admin'] = 'Admin Approval'; -$txt['install_settings_reg_disabled'] = 'Registration Disabled'; -$txt['install_settings_reg_mode_info'] = 'This field allows you to change the mode of registration on installation to prevent unwanted registrations.'; -$txt['install_settings_compress'] = 'Gzip Output'; -$txt['install_settings_compress_title'] = 'Compress output to save bandwidth.'; -// In this string, you can translate the word "PASS" to change what it says when the test passes. -$txt['install_settings_compress_info'] = 'This function does not work properly on all servers, but it can save you a lot of bandwidth.
Click here to test it. (it should just say "PASS".)'; -$txt['install_settings_dbsession'] = 'Database Sessions'; -$txt['install_settings_dbsession_title'] = 'Use the database for sessions instead of using files.'; -$txt['install_settings_dbsession_info1'] = 'This feature is almost always for the best, as it makes sessions more dependable.'; -$txt['install_settings_dbsession_info2'] = 'This feature is generally a good idea, but it may not work properly on this server.'; -$txt['install_settings_stats'] = 'Allow stat Collection'; -$txt['install_settings_stats_title'] = 'Allow Simple Machines to collect basic stats monthly'; -$txt['install_settings_stats_info'] = 'If enabled, this will allow Simple Machines to visit your site once a month to collect basic statistics. This will help us make decisions as to which configurations to optimize the software for. For more information please visit our info page.'; -$txt['install_settings_proceed'] = 'Proceed'; +// We represent the time here in backwards variables, as it makes the code easier. +$txt['upgrade_time_hms'] = 'The upgrade script has been running for the last {h, plural, + one {# hour} + other {# hours} +}, {m, plural, + one {# minute} + other {# minutes} +}, and {s, plural, + one {# second} + other {# seconds} +}.'; +$txt['upgrade_time_ms'] = 'The upgrade script has been running for the last {m, plural, + one {# minute} + other {# minutes} +} and {s, plural, + one {# second} + other {# seconds} +}.'; +$txt['upgrade_time_s'] = 'The upgrade script has been running for the last {s, plural, + one {# second} + other {# seconds} +}.'; +$txt['upgrade_time_updated_hms'] = 'The upgrade script was last updated {h, plural, + one {# hour} + other {# hours} +}, {m, plural, + one {# minute} + other {# minutes} +}, and {s, plural, + one {# second} + other {# seconds} +} ago.'; +$txt['upgrade_time_updated_ms'] = 'The upgrade script was last updated {m, plural, + one {# minute} + other {# minutes} +} and {s, plural, + one {# second} + other {# seconds} +} ago.'; +$txt['upgrade_time_updated_s'] = 'The upgrade script was last updated {s, plural, + one {# second} + other {# seconds} +} ago.'; -$txt['db_settings'] = 'Database Server Settings'; -$txt['db_settings_info'] = 'These are the settings to use for your database server. If you do not know the values, you should ask your host what they are.'; -$txt['db_settings_type'] = 'Database type'; -$txt['db_settings_type_info'] = 'Multiple supported database types were detected, which do you wish to use? Please note that running pre-SMF 2.0 RC3 along with newer SMF versions in the same PostgreSQL database is not supported. You need to upgrade your older installations in that case.'; -$txt['db_settings_server'] = 'Server name'; -$txt['db_settings_server_info'] = 'This is nearly always localhost, so if you do not know, try localhost.'; -$txt['db_settings_username'] = 'Username'; -$txt['db_settings_username_info'] = 'Fill in the username you need to connect to your database here.
If you do not know what it is, try the username of your ftp account, most of the time they are the same.'; -$txt['db_settings_password'] = 'Password'; -$txt['db_settings_password_info'] = 'Put the password you need to connect to your database here.
If you do not know this, you should try the password to your ftp account.'; -$txt['db_settings_database'] = 'Database name'; -$txt['db_settings_database_info'] = 'Fill in the name of the database you want to use for SMF to store its data in.'; -$txt['db_settings_database_info_note'] = 'If this database does not exist, this installer will try to create it.'; -$txt['db_settings_port'] = 'Database port'; -$txt['db_settings_port_info'] = 'Leave blank to use the default'; -$txt['db_settings_prefix'] = 'Table prefix'; -$txt['db_settings_prefix_info'] = 'The prefix for every table in the database. Do not install two forums with the same prefix!
This key allows for multiple installations in one database.'; -$txt['db_populate'] = 'Populated Database'; -$txt['db_populate_info'] = 'Your settings have now been saved and the database has been populated with all the data required to get your forum up and running. Summary of population:'; -$txt['db_populate_info2'] = 'Click "Continue" to progress to the admin account creation page.'; +// Upgrade - Backup database. +$txt['upgrade_completedtables_outof'] = 'Completed {cur_table_num} {table_count, plural, + one {out of # table} + other {out of # tables} +}.'; + +// Upgrade - Migrations +$txt['upgrade_steps'] = 'Steps'; +$txt['upgrade_substeps'] = 'Migrations'; +$txt['upgrade_db_changes'] = 'Executing database changes'; +$txt['upgrade_db_patient'] = 'Please be patient - this may take some time on large forums. The time elapsed increments from the server to show progress is being made.'; +$txt['upgrade_current_step'] = 'Current Step:'; +$txt['upgrade_current_substep'] = 'Current Migration:'; +$txt['upgrade_completed'] = 'Completed'; +$txt['upgrade_outof'] = 'out of'; +$txt['upgrade_db_complete'] = '1 Database Updates Complete! Click Continue to Proceed.'; +$txt['upgrade_completed_migration'] = ' Completed Migration:'; + + + + + + + + + +// Unused Installer strings. +$txt['install_all_lovely'] = 'We have completed some initial tests on your server and everything appears to be in order. Simply click the "Continue" button below to get started.'; +$txt['user_refresh_install'] = 'Forum Refreshed'; +$txt['install_settings_proceed'] = 'Proceed'; $txt['db_populate_inserts'] = 'Inserted {0, number, integer} rows.'; $txt['db_populate_tables'] = 'Created {0, number, integer} tables.'; $txt['db_populate_insert_dups'] = 'Ignored {0, number, integer} duplicated inserts.'; $txt['db_populate_table_dups'] = 'Ignored {0, number, integer} duplicated tables.'; - -$txt['user_settings'] = 'Create your account'; -$txt['user_settings_info'] = 'The installer will now create a new administrator account for you.'; -$txt['user_settings_username'] = 'Your username'; -$txt['user_settings_username_info'] = 'Choose the name you want to login with.
This can be changed later.'; -$txt['user_settings_password'] = 'Password'; -$txt['user_settings_password_info'] = 'Fill in your preferred password here and remember it well!'; -$txt['user_settings_again'] = 'Password'; -$txt['user_settings_again_info'] = '(just for verification).'; -$txt['user_settings_admin_email'] = 'Administrator email address'; -$txt['user_settings_admin_email_info'] = 'Provide your email address. This must be a valid email address!'; -$txt['user_settings_server_email'] = 'Webmaster email address'; -$txt['user_settings_server_email_info'] = 'Provide the email address that SMF will use to send emails. This must be a valid email address!'; -$txt['user_settings_database'] = 'Database Password'; -$txt['user_settings_database_info'] = 'For security reasons, the installer requires that you supply the database password to create an administrator account.'; +$txt['congratulations'] = 'Congratulations, the installation process is complete!'; +$txt['error_message_bad_try_again'] = 'to try installing anyway, but note that this is strongly discouraged.'; $txt['user_settings_skip'] = 'Skip'; $txt['user_settings_skip_sure'] = 'Are you sure you wish to skip admin account creation?'; $txt['user_settings_proceed'] = 'Finish'; -$txt['ftp_checking_writable'] = 'Checking files are writable'; -$txt['ftp_setup'] = 'FTP Connection Information'; -$txt['ftp_setup_info'] = 'This installer can connect via FTP to fix the files that need to be writable and are not. If this does not work for you, you will have to go in manually and make the files writable. Please note that this does not support SSL right now.'; -$txt['ftp_setup_why'] = 'What is this step for?'; -$txt['ftp_setup_why_info'] = 'Some files need to be writable for SMF to work properly. This step allows you to let the installer make them writable for you. However, in some cases it will not work. In this case, please make the following files 777 (writable, 755 on some hosts):'; -$txt['ftp_setup_again'] = 'to test if these files are writable again.'; - -$txt['error_missing_files'] = 'Unable to find crucial installation files in the directory of this script!

Please make sure you uploaded the entire installation package, including the sql file, and then try again.'; -$txt['error_session_save_path'] = 'Please inform your host that the session.save_path specified in php.ini is not valid! It needs to be changed to a directory that exists and is writable by the user PHP is running under.
'; -$txt['error_windows_chmod'] = 'You are on a windows server and some crucial files are not writable. Please ask your host to give write permissions to the user PHP is running under for the files in your SMF installation. The following files or directories need to be writable:'; $txt['settings_error'] = 'Your settings could not be saved to Settings.php.'; -$txt['error_ftp_no_connect'] = 'Unable to connect to FTP server with this combination of details.'; $txt['error_db_file'] = 'Cannot find database source script! Please check file {0} is within your forum source directory.'; $txt['error_db_connect'] = 'Cannot connect to the database server with the supplied data.

If you are not sure about what to type in, please contact your host.'; $txt['error_db_connect_settings'] = 'Cannot connect to the database server.

Please check that the database info variables are correct in Settings.php.'; $txt['error_db_database'] = 'The installer was unable to access the "{db_name}" database. With some hosts, you have to create the database in your administration panel before SMF can use it. Some also add prefixes, such as your username, to your database names.'; -$txt['error_db_queries'] = 'Some of the queries were not executed properly. This could be caused by an unsupported (development or old) version of your database software.

Technical information about the queries:'; $txt['error_db_queries_line'] = 'Line #'; -$txt['error_db_missing'] = 'The installer was unable to detect any database support in PHP. Please ask your host to ensure that PHP was compiled with the desired database, or that the proper extension is being loaded.'; $txt['error_db_script_missing'] = 'The installer could not find any install script files for the detected databases. Please check you have uploaded the necessary install script files to your forum directory, for example "{file}"'; -$txt['error_session_missing'] = 'The installer was unable to detect sessions support in your server’s installation of PHP. Please ask your host to ensure that PHP was compiled with session support (which in fact is the PHP default, meaning your host currently has explicitly disabled it).'; $txt['error_user_settings_again_match'] = 'You typed in two completely different passwords!'; $txt['error_user_settings_no_password'] = 'Your password must be at least four characters long.'; $txt['error_user_settings_taken'] = 'Sorry, a member is already registered with that username and/or email address.

A new account has not been created.'; @@ -183,20 +339,12 @@ $txt['error_sourcefile_missing'] = 'Unable to find the Sources/{file} file. Please make sure it was uploaded properly, and then try again.'; $txt['error_db_alter_priv'] = 'The database account you specified does not have permission to ALTER, CREATE, and/or DROP tables in the database. This is necessary for SMF to function properly.'; $txt['error_versions_do_not_match'] = 'The installer has detected another version of SMF already installed with the specified information. If you are trying to upgrade, you should use the upgrader, not the installer.

Otherwise, you may wish to use different information, or create a backup and then delete the data currently in the database.'; -$txt['error_mod_security'] = 'The installer has detected the mod_security module is installed on your web server. Mod_security will block submitted forms even before SMF gets a say in anything. SMF has a built-in security scanner that will work more effectively than mod_security and that will not block submitted forms.

More information about disabling mod_security'; -$txt['error_mod_security_no_write'] = 'The installer has detected the mod_security module is installed on your web server. Mod_security will block submitted forms even before SMF gets a say in anything. SMF has a built-in security scanner that will work more effectively than mod_security and that will not block submitted forms.

More information about disabling mod_security

Alternatively, you may wish to use your ftp client to chmod .htaccess in the forum directory to be writable (777), and then refresh this page.'; -$txt['error_utf8_version'] = 'The current version of your database does not support the use of the UTF-8 character set. You can still install SMF without any problems, but only with UTF-8 support unchecked. If you would like to switch over to UTF-8 in the future (e.g. after the database server of your forum has been upgraded to version >= {utf8_version}), you can convert your forum to UTF-8 through the admin panel.'; $txt['error_valid_admin_email_needed'] = 'You have not entered a valid email address for your administrator account.'; $txt['error_valid_server_email_needed'] = 'You have not entered a valid webmaster email address.'; -$txt['error_already_installed'] = 'The installer has detected that you already have SMF installed. It is strongly advised that you do not try to overwrite an existing installation, continuing with installation may result in the loss or corruption of existing data.

If you wish to upgrade please visit the Simple Machines Website and download the latest upgrade package.

If you wish to overwrite your existing installation, including all data, it is recommended that you delete the existing database tables and replace Settings.php and try again.'; -$txt['error_warning_notice'] = 'Warning!'; -$txt['error_script_outdated'] = 'This install script is out of date! The current version of SMF is {smfVersion}, but this install script is for {yourVersion}.

- It is recommended that you visit the Simple Machines website to ensure you are installing the latest version.'; -$txt['error_db_prefix_numeric'] = 'The selected database type does not support the use of numeric prefixes.'; -$txt['error_pg_scs'] = 'PostgreSQL is configured incorrectly. Please turn on the standard_conforming_strings configuration parameter.'; + +$txt['error_invalid_characters_username'] = 'Invalid character used in Username.'; $txt['error_username_too_long'] = 'Username may only be up to 25 characters long.'; $txt['error_username_left_empty'] = 'Username field was left empty.'; -$txt['error_db_prefix_reserved'] = 'The prefix that you entered is a reserved prefix. Please enter another prefix.'; $txt['error_utf8_support'] = 'The database you are trying to use is not using UTF-8 charset'; $txt['ftp_login'] = 'Your FTP connection information'; @@ -239,10 +387,7 @@ $txt['upgrade_continue'] = 'Continue'; $txt['upgrade_skip'] = 'Skip'; $txt['upgrade_note'] = 'Note!'; -$txt['upgrade_step'] = 'Step'; -$txt['upgrade_steps'] = 'Steps'; -$txt['upgrade_progress'] = 'Progress'; -$txt['upgrade_overall_progress'] = 'Overall Progress'; + $txt['upgrade_step_progress'] = 'Step Progress'; $txt['upgrade_time_elapsed'] = 'Time Elapsed'; $txt['upgrade_time_mins'] = 'mins'; @@ -283,16 +428,10 @@ $txt['upgrade_stats_collection'] = 'Allow Simple Machines to collect basic stats monthly.'; $txt['upgrade_stats_info'] = 'If enabled, this will allow Simple Machines to visit your site once a month to collect basic statistics. This will help us make decisions as to which configurations to optimise the software for. For more information please visit our info page.'; $txt['upgrade_migrate_settings_file'] = 'Migrate to a new Settings file.'; -$txt['upgrade_db_changes'] = 'Executing database changes'; -$txt['upgrade_db_patient'] = 'Please be patient - this may take some time on large forums. The time elapsed increments from the server to show progress is being made.'; -$txt['upgrade_db_complete'] = '1 Database Updates Complete! Click Continue to Proceed.'; $txt['upgrade_db_complete2'] = 'Database Updates Complete! Click Continue to Proceed.'; $txt['upgrade_script'] = 'Executing upgrade script'; $txt['upgrade_error'] = 'Error!'; -$txt['upgrade_unknown_error'] = 'Unknown Error!'; /* Same sentence, 3 different strings */ -$txt['upgrade_completed'] = 'Completed'; -$txt['upgrade_outof'] = 'out of'; $txt['upgrade_tables'] = 'tables.'; $txt['upgrade_run_script'] = 'We recommend that you do not run this script unless you are sure that'; @@ -301,6 +440,7 @@ $txt['upgrade_completed_table'] = 'Completed Table:'; $txt['upgrade_current_table'] = 'Current Table:'; + $txt['upgrade_fulltext'] = 'Please note that your fulltext index was dropped to facilitate the conversion and will need to be recreated in the admin area after the upgrade is complete.'; $txt['upgrade_conversion_proceed'] = 'Conversion Complete! Click Continue to Proceed.'; $txt['upgrade_convert_datajson'] = 'Converting data from serialize to JSON...'; @@ -323,7 +463,6 @@ $txt['upgrade_ftp_shell'] = 'If you have a shell account, the command below can automatically correct permissions on these files'; $txt['upgrade_ftp_error'] = 'The following error was encountered when trying to connect:'; -$txt['upgrade_ready_proceed'] = 'Thank you for choosing to upgrade to SMF {SMF_VERSION}. All files appear to be in place and the upgrade can now proceed.'; $txt['upgrade_error_script_js'] = 'The upgrade script cannot find script.js or it is out of date. Make sure your theme paths are correct. You can download a setting checker tool from the Simple Machines Website'; $txt['upgrade_warning_lots_data'] = 'This upgrade script has detected that your forum contains a lot of data which needs upgrading. This process may take quite some time depending on your server and forum size, and for very large forums (~300,000 messages) may take several hours to complete.'; $txt['upgrade_warning_out_of_date'] = 'This upgrade script is out of date! The current version of SMF is ?? but this upgrade script is for {SMF_VERSION}.

It is recommended that you visit the Simple Machines Website to ensure you are upgrading to the latest version.'; @@ -338,49 +477,6 @@ $txt['upgrade_writable_files'] = 'The following files need to be writable to continue the upgrade. Please ensure the Windows permissions are correctly set to allow this:'; $txt['upgrade_time_user'] = '"{name}" is running the upgrade script.'; -// We represent the time here in backwards variables, as it makes the code easier. -$txt['upgrade_time_hms'] = 'The upgrade script has been running for the last {h, plural, - one {# hour} - other {# hours} -}, {m, plural, - one {# minute} - other {# minutes} -}, and {s, plural, - one {# second} - other {# seconds} -}.'; -$txt['upgrade_time_ms'] = 'The upgrade script has been running for the last {m, plural, - one {# minute} - other {# minutes} -} and {s, plural, - one {# second} - other {# seconds} -}.'; -$txt['upgrade_time_s'] = 'The upgrade script has been running for the last {s, plural, - one {# second} - other {# seconds} -}.'; -$txt['upgrade_time_updated_hms'] = 'The upgrade script was last updated {h, plural, - one {# hour} - other {# hours} -}, {m, plural, - one {# minute} - other {# minutes} -}, and {s, plural, - one {# second} - other {# seconds} -} ago.'; -$txt['upgrade_time_updated_hm'] = 'The upgrade script was last updated {m, plural, - one {# minute} - other {# minutes} -} and {s, plural, - one {# second} - other {# seconds} -} ago.'; -$txt['upgrade_time_updated_s'] = 'The upgrade script was last updated {s, plural, - one {# second} - other {# seconds} -} ago.'; $txt['upgrade_completed_time_hms'] = 'Upgrade completed in {h, plural, one {# hour} other {# hours} @@ -416,32 +512,22 @@ $txt['upgrade_cleanup_completed'] = 'Cleanup has completed'; $txt['upgrade_current_step'] = 'Current Step'; -$txt['upgrade_unsuccessful'] = 'Unsuccessful!'; -$txt['upgrade_thisquery'] = 'This query:'; -$txt['upgrade_causerror'] = 'Caused the error:'; -$txt['upgrade_completedtables_outof'] = 'Completed {cur_table_num} {table_count, plural, - one {out of # table} - other {out of # tables} -}.'; $txt['upgrade_success'] = 'Successful!'; $txt['upgrade_loop'] = 'Upgrade script appears to be going into a loop - step: '; $txt['upgrade_respondtime'] = 'Server has not responded for {0, number, integer} seconds. It may be worth waiting a little longer before trying again.'; -$txt['upgrade_respondtime_clickhere'] = 'Click here to try again.'; $txt['mtitle'] = 'Upgrading the forum...'; $txt['mmessage'] = 'Don’t worry, your forum will be updated shortly. It will only be a minute ;).'; -// Upgrader error messages +// Upgrade error messages // argument(s): template name (if applicable) $txt['error_unexpected_template_call'] = 'Error: Unexpected call to use the {sub_template} template. Please copy and paste all the text above and visit the SMF support forum to let the developers know that there is a bug.'; $txt['error_invalid_template'] = 'Upgrade aborted! Invalid template: template_{sub_template}'; -$txt['error_lang_index_missing'] = 'The upgrader was unable to find language files for the selected language, {lang}.
SMF will not work in this language without the language files installed.

Please either install them, or try English instead.'; +$txt['error_lang_general_missing'] = 'The upgrader was unable to find language files for the selected language, {lang}.
SMF will not work in this language without the language files installed.

Please either install them, or try English instead.'; $txt['error_upgrade_files_missing'] = 'The upgrader was unable to find some crucial files.

Please make sure you uploaded all of the files included in the package, including the Themes, Sources, and other directories.'; $txt['error_upgrade_old_files'] = 'The upgrader found some old or outdated files.

Please make certain you uploaded the new versions of all the files included in the package.'; $txt['error_upgrade_old_lang_files'] = 'The upgrader found some old or outdated language files for the selected language, {lang}.

Please make certain you uploaded the new versions of all the files included in the package, even the theme and language files for the default theme.
   [SKIP] [Try English]'; -$txt['error_php_too_low'] = 'Warning! You do not appear to have a version of PHP installed on your webserver that meets SMF’s minimum installations requirements.

Please ask your host to upgrade.'; $txt['error_db_too_low'] = 'Your {name} version does not meet the minimum requirements of SMF.

Please ask your host to upgrade.'; $txt['error_db_privileges'] = 'The {name} user you have set in Settings.php does not have proper privileges.

Please ask your host to give this user the ALTER, CREATE, and DROP privileges.'; -$txt['error_dir_not_writable'] = 'The directory "{dir}" has to be writable to continue the upgrade. Please make sure permissions are correctly set to allow this.'; $txt['error_cache_not_found'] = 'The cache directory could not be found.

Please make sure you have a directory called "cache" in your forum directory before continuing.'; $txt['error_agreement_not_writable'] = 'The upgrader was unable to obtain write access to agreement.txt.

If you are using a linux or unix based server, please ensure that the file is chmod’d to 777, or if it does not exist that the directory this upgrader is in is 777.
If your server is running Windows, please ensure that the internet guest account has the proper permissions on it or its folder.'; $txt['error_not_admin'] = 'You need to be an admin to perform an upgrade!'; @@ -455,7 +541,6 @@ $txt['warning_att_dir_missing'] = 'Warning! One or more attachment directories not found. Continuing may be unsafe. Please confirm folder settings before proceeding.'; // Page titles -$txt['updating_smf_installation'] = 'Updating Your SMF Installation!'; $txt['upgrade_options'] = 'Upgrade Options'; $txt['backup_database'] = 'Backup Database'; $txt['database_changes'] = 'Database Changes'; diff --git a/other/install.php b/other/install.php index 17344350c92..f4e70f6c30b 100644 --- a/other/install.php +++ b/other/install.php @@ -323,11 +323,11 @@ function load_lang_file() // Make sure the languages directory actually exists. if (file_exists(Config::$languagesdir)) { - // Find all the "Install" language files in the directory. + // Find all the "Maintenance" language files in the directory. $dir = dir(Config::$languagesdir); while ($entry = $dir->read()) { - if (!is_dir(Config::$languagesdir . '/' . $entry) || !file_exists(Config::$languagesdir . '/' . $entry . '/' . 'Install.php') || !file_exists(Config::$languagesdir . '/' . $entry . '/' . 'General.php')) { + if (!is_dir(Config::$languagesdir . '/' . $entry) || !file_exists(Config::$languagesdir . '/' . $entry . '/' . 'Maintenance.php') || !file_exists(Config::$languagesdir . '/' . $entry . '/' . 'General.php')) { continue; } @@ -413,7 +413,7 @@ function load_lang_file() } // Make sure it exists, if it doesn't reset it. - if (!isset($_SESSION['installer_temp_lang']) || preg_match('~[^\\w_\\-.]~', $_SESSION['installer_temp_lang']) === 1 || !file_exists(Config::$languagesdir . '/' . $_SESSION['installer_temp_lang'] . '/Install.php')) { + if (!isset($_SESSION['installer_temp_lang']) || preg_match('~[^\\w_\\-.]~', $_SESSION['installer_temp_lang']) === 1 || !file_exists(Config::$languagesdir . '/' . $_SESSION['installer_temp_lang'] . '/Maintenance.php')) { // Use the first one... list($_SESSION['installer_temp_lang']) = array_keys($incontext['detected_languages']); @@ -430,7 +430,7 @@ function load_lang_file() Lang::addDirs(Config::$languagesdir); // And now load the language file. - Lang::load('General+Install'); + Lang::load('General+Maintenance'); } // This handy function loads some settings and the like. diff --git a/other/upgrade.php b/other/upgrade.php index 3b97853a6cf..c1df58329c8 100644 --- a/other/upgrade.php +++ b/other/upgrade.php @@ -550,18 +550,18 @@ function load_lang_file() $lang_dir = !empty(Config::$languagesdir) ? Config::$languagesdir : fixRelativePath(Config::$boarddir) . '/Languages'; // Override the language file? - if (isset($upcontext['language']) && file_exists($lang_dir . '/' . $upcontext['language'] . '/Install.php')) { + if (isset($upcontext['language']) && file_exists($lang_dir . '/' . $upcontext['language'] . '/Maintenance.php')) { $_SESSION['upgrader_lang'] = $upcontext['language']; - } elseif (isset($upcontext['lang']) && file_exists($lang_dir . '/' . $upcontext['lang'] . '/Install.php')) { + } elseif (isset($upcontext['lang']) && file_exists($lang_dir . '/' . $upcontext['lang'] . '/Maintenance.php')) { $_SESSION['upgrader_lang'] = $upcontext['lang']; - } elseif (isset($current_language) && file_exists($lang_dir . '/' . $current_language . '/Install.php')) { + } elseif (isset($current_language) && file_exists($lang_dir . '/' . $current_language . '/Maintenance.php')) { $_SESSION['upgrader_lang'] = $current_language; } else { $_SESSION['upgrader_lang'] = 'en_US'; } // Avoid pointless repetition - if (isset($_SESSION['upgrader_lang']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Install.php') { + if (isset($_SESSION['upgrader_lang']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Maintenance.php') { return; } @@ -569,7 +569,7 @@ function load_lang_file() if (empty($detected_languages)) { // Make sure the languages directory actually exists. if (file_exists($lang_dir)) { - // Find all the "Install" language files in the directory. + // Find all the "Maintenance" language files in the directory. $dir = dir($lang_dir); while ($entry = $dir->read()) { @@ -578,7 +578,7 @@ function load_lang_file() continue; } - if (!is_dir($lang_dir . '/' . $entry) || !file_exists($lang_dir . '/' . $entry . '/' . 'Install.php') || !file_exists($lang_dir . '/' . $entry . '/' . 'General.php')) { + if (!is_dir($lang_dir . '/' . $entry) || !file_exists($lang_dir . '/' . $entry . '/' . 'Maintenance.php') || !file_exists($lang_dir . '/' . $entry . '/' . 'General.php')) { continue; } @@ -620,7 +620,7 @@ function load_lang_file() Lang::$txt['error_sourcefile_missing'] = 'Unable to find the Sources/{file} file. Please make sure it was uploaded properly, and then try again.'; Lang::$txt['warning_lang_old'] = 'The language files for your selected language, {user_language}, have not been updated to the latest version. Upgrade will continue with the forum default, {default_language}.'; - Lang::$txt['warning_lang_missing'] = 'The upgrader could not find the "Install" language file for your selected language, {user_language}. Upgrade will continue with the forum default, {default_language}.'; + Lang::$txt['warning_lang_missing'] = 'The upgrader could not find the "Maintenance" language file for your selected language, {user_language}. Upgrade will continue with the forum default, {default_language}.'; return; } @@ -697,7 +697,7 @@ function load_lang_file() } // Make sure it exists. If it doesn't, reset it. - if (!isset($_SESSION['upgrader_lang']) || preg_match('~^[A-Za-z0-9_-]+$~', $_SESSION['upgrader_lang']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Install.php')) { + if (!isset($_SESSION['upgrader_lang']) || preg_match('~^[A-Za-z0-9_-]+$~', $_SESSION['upgrader_lang']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Maintenance.php')) { // Use the first one... list($_SESSION['upgrader_lang']) = array_keys($detected_languages); @@ -711,10 +711,10 @@ function load_lang_file() Lang::addDirs($lang_dir); // And now load the language files. - Lang::load('General+Install', $_SESSION['upgrader_lang']); + Lang::load('General+Maintenance', $_SESSION['upgrader_lang']); // Remember what we've done - $loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Install.php'; + $loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Maintenance.php'; } // Used to direct the user to another location. @@ -1403,7 +1403,7 @@ function checkLogin() if (empty($match[1]) || $match[1] != SMF_LANG_VERSION) { $upcontext['upgrade_options_warning'] = Lang::getTxt('warning_lang_old', ['user_language' => $user_language, 'default_language' => $upcontext['language']]); - } elseif (!file_exists($lang_dir . '/' . $user_language . '/Install.php')) { + } elseif (!file_exists($lang_dir . '/' . $user_language . '/Maintenance.php')) { $upcontext['upgrade_options_warning'] = Lang::getTxt('warning_lang_missing', ['user_language' => $user_language, 'default_language' => $upcontext['language']]); } else { // Set this as the new language. @@ -3150,12 +3150,12 @@ function cmdStep0() print_error('Error: Language files out of date.', true); } - if (!file_exists($lang_dir . '/' . $upcontext['language'] . '/Install.php')) { - print_error('Error: Install language is missing for selected language.', true); + if (!file_exists($lang_dir . '/' . $upcontext['language'] . '/Maintenance.php')) { + print_error('Error: Maintenance language is missing for selected language.', true); } // Otherwise include it! - require_once $lang_dir . '/' . $upcontext['language'] . '/Install.php'; + require_once $lang_dir . '/' . $upcontext['language'] . '/Maintenance.php'; } // Do we need to add this setting? From 8909b0cfb8f4c21d9a08bbfe3077cc3fd75e4a0d Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Thu, 24 Apr 2025 14:55:36 -0600 Subject: [PATCH 07/90] Adds SMF\Db\Schema and descendants Signed-off-by: Jon Stovell --- Sources/Db/Schema/Column.php | 162 ++ Sources/Db/Schema/DbIndex.php | 81 + Sources/Db/Schema/Table.php | 278 +++ Sources/Db/Schema/index.php | 8 + Sources/Db/Schema/v2_1/AdminInfoFiles.php | 139 ++ Sources/Db/Schema/v2_1/ApprovalQueue.php | 72 + Sources/Db/Schema/v2_1/Attachments.php | 189 ++ Sources/Db/Schema/v2_1/BackgroundTasks.php | 93 + Sources/Db/Schema/v2_1/BanGroups.php | 126 ++ Sources/Db/Schema/v2_1/BanItems.php | 125 ++ Sources/Db/Schema/v2_1/BoardPermissions.php | 1627 ++++++++++++++++ .../Db/Schema/v2_1/BoardPermissionsView.php | 96 + Sources/Db/Schema/v2_1/Boards.php | 233 +++ Sources/Db/Schema/v2_1/Calendar.php | 146 ++ Sources/Db/Schema/v2_1/CalendarHolidays.php | 623 +++++++ Sources/Db/Schema/v2_1/Categories.php | 99 + Sources/Db/Schema/v2_1/CustomFields.php | 277 +++ Sources/Db/Schema/v2_1/GroupModerators.php | 74 + Sources/Db/Schema/v2_1/LogActions.php | 163 ++ Sources/Db/Schema/v2_1/LogActivity.php | 101 + Sources/Db/Schema/v2_1/LogBanned.php | 99 + Sources/Db/Schema/v2_1/LogBoards.php | 81 + Sources/Db/Schema/v2_1/LogComments.php | 144 ++ Sources/Db/Schema/v2_1/LogDigest.php | 86 + Sources/Db/Schema/v2_1/LogErrors.php | 149 ++ Sources/Db/Schema/v2_1/LogFloodcontrol.php | 93 + Sources/Db/Schema/v2_1/LogGroupRequests.php | 140 ++ Sources/Db/Schema/v2_1/LogMarkRead.php | 81 + Sources/Db/Schema/v2_1/LogMemberNotices.php | 79 + Sources/Db/Schema/v2_1/LogNotify.php | 95 + Sources/Db/Schema/v2_1/LogOnline.php | 123 ++ Sources/Db/Schema/v2_1/LogPackages.php | 183 ++ Sources/Db/Schema/v2_1/LogPolls.php | 84 + Sources/Db/Schema/v2_1/LogReported.php | 174 ++ .../Db/Schema/v2_1/LogReportedComments.php | 120 ++ Sources/Db/Schema/v2_1/LogScheduledTasks.php | 84 + Sources/Db/Schema/v2_1/LogSearchMessages.php | 74 + Sources/Db/Schema/v2_1/LogSearchResults.php | 95 + Sources/Db/Schema/v2_1/LogSearchSubjects.php | 80 + Sources/Db/Schema/v2_1/LogSearchTopics.php | 74 + Sources/Db/Schema/v2_1/LogSpiderHits.php | 100 + Sources/Db/Schema/v2_1/LogSpiderStats.php | 86 + Sources/Db/Schema/v2_1/LogSubscribed.php | 158 ++ Sources/Db/Schema/v2_1/LogTopics.php | 93 + Sources/Db/Schema/v2_1/MailQueue.php | 128 ++ Sources/Db/Schema/v2_1/MemberLogins.php | 100 + Sources/Db/Schema/v2_1/Membergroups.php | 209 +++ Sources/Db/Schema/v2_1/Members.php | 459 +++++ Sources/Db/Schema/v2_1/Mentions.php | 102 ++ Sources/Db/Schema/v2_1/MessageIcons.php | 167 ++ Sources/Db/Schema/v2_1/Messages.php | 263 +++ Sources/Db/Schema/v2_1/ModeratorGroups.php | 74 + Sources/Db/Schema/v2_1/Moderators.php | 74 + Sources/Db/Schema/v2_1/PackageServers.php | 103 ++ Sources/Db/Schema/v2_1/PermissionProfiles.php | 90 + Sources/Db/Schema/v2_1/Permissions.php | 284 +++ Sources/Db/Schema/v2_1/PersonalMessages.php | 133 ++ Sources/Db/Schema/v2_1/PmLabeledMessages.php | 74 + Sources/Db/Schema/v2_1/PmLabels.php | 81 + Sources/Db/Schema/v2_1/PmRecipients.php | 117 ++ Sources/Db/Schema/v2_1/PmRules.php | 116 ++ Sources/Db/Schema/v2_1/PollChoices.php | 88 + Sources/Db/Schema/v2_1/Polls.php | 143 ++ Sources/Db/Schema/v2_1/Qanda.php | 92 + Sources/Db/Schema/v2_1/ScheduledTasks.php | 241 +++ Sources/Db/Schema/v2_1/Sessions.php | 80 + Sources/Db/Schema/v2_1/SmileyFiles.php | 82 + Sources/Db/Schema/v2_1/Smileys.php | 102 ++ Sources/Db/Schema/v2_1/Spiders.php | 189 ++ Sources/Db/Schema/v2_1/Subscriptions.php | 141 ++ Sources/Db/Schema/v2_1/Themes.php | 147 ++ Sources/Db/Schema/v2_1/Topics.php | 240 +++ Sources/Db/Schema/v2_1/UserAlerts.php | 140 ++ Sources/Db/Schema/v2_1/UserAlertsPrefs.php | 226 +++ Sources/Db/Schema/v2_1/UserDrafts.php | 161 ++ Sources/Db/Schema/v2_1/UserLikes.php | 101 + Sources/Db/Schema/v2_1/index.php | 8 + Sources/Db/Schema/v3_0/AdminInfoFiles.php | 141 ++ Sources/Db/Schema/v3_0/ApprovalQueue.php | 74 + Sources/Db/Schema/v3_0/Attachments.php | 191 ++ Sources/Db/Schema/v3_0/BackgroundTasks.php | 95 + Sources/Db/Schema/v3_0/BanGroups.php | 128 ++ Sources/Db/Schema/v3_0/BanItems.php | 127 ++ Sources/Db/Schema/v3_0/BoardPermissions.php | 1629 +++++++++++++++++ .../Db/Schema/v3_0/BoardPermissionsView.php | 98 + Sources/Db/Schema/v3_0/Boards.php | 235 +++ Sources/Db/Schema/v3_0/Calendar.php | 586 ++++++ Sources/Db/Schema/v3_0/Categories.php | 101 + Sources/Db/Schema/v3_0/CustomFields.php | 279 +++ Sources/Db/Schema/v3_0/GroupModerators.php | 76 + Sources/Db/Schema/v3_0/LogActions.php | 165 ++ Sources/Db/Schema/v3_0/LogActivity.php | 103 ++ Sources/Db/Schema/v3_0/LogBanned.php | 101 + Sources/Db/Schema/v3_0/LogBoards.php | 83 + Sources/Db/Schema/v3_0/LogComments.php | 146 ++ Sources/Db/Schema/v3_0/LogDigest.php | 88 + Sources/Db/Schema/v3_0/LogErrors.php | 151 ++ Sources/Db/Schema/v3_0/LogFloodcontrol.php | 82 + Sources/Db/Schema/v3_0/LogGroupRequests.php | 142 ++ Sources/Db/Schema/v3_0/LogMarkRead.php | 83 + Sources/Db/Schema/v3_0/LogMemberNotices.php | 81 + Sources/Db/Schema/v3_0/LogNotify.php | 103 ++ Sources/Db/Schema/v3_0/LogOnline.php | 113 ++ Sources/Db/Schema/v3_0/LogPackages.php | 192 ++ Sources/Db/Schema/v3_0/LogPolls.php | 86 + Sources/Db/Schema/v3_0/LogReported.php | 176 ++ .../Db/Schema/v3_0/LogReportedComments.php | 122 ++ Sources/Db/Schema/v3_0/LogScheduledTasks.php | 86 + Sources/Db/Schema/v3_0/LogSearchMessages.php | 76 + Sources/Db/Schema/v3_0/LogSearchResults.php | 98 + Sources/Db/Schema/v3_0/LogSearchSubjects.php | 82 + Sources/Db/Schema/v3_0/LogSearchTopics.php | 76 + Sources/Db/Schema/v3_0/LogSpiderHits.php | 102 ++ Sources/Db/Schema/v3_0/LogSpiderStats.php | 88 + Sources/Db/Schema/v3_0/LogSubscribed.php | 160 ++ Sources/Db/Schema/v3_0/LogTopics.php | 95 + Sources/Db/Schema/v3_0/MailQueue.php | 130 ++ Sources/Db/Schema/v3_0/MemberLogins.php | 102 ++ Sources/Db/Schema/v3_0/Membergroups.php | 211 +++ Sources/Db/Schema/v3_0/Members.php | 483 +++++ Sources/Db/Schema/v3_0/Mentions.php | 104 ++ Sources/Db/Schema/v3_0/MessageIcons.php | 169 ++ Sources/Db/Schema/v3_0/Messages.php | 272 +++ Sources/Db/Schema/v3_0/ModeratorGroups.php | 76 + Sources/Db/Schema/v3_0/Moderators.php | 76 + Sources/Db/Schema/v3_0/PackageServers.php | 105 ++ Sources/Db/Schema/v3_0/PermissionProfiles.php | 92 + Sources/Db/Schema/v3_0/Permissions.php | 286 +++ Sources/Db/Schema/v3_0/PersonalMessages.php | 142 ++ Sources/Db/Schema/v3_0/PmLabeledMessages.php | 76 + Sources/Db/Schema/v3_0/PmLabels.php | 83 + Sources/Db/Schema/v3_0/PmRecipients.php | 119 ++ Sources/Db/Schema/v3_0/PmRules.php | 118 ++ Sources/Db/Schema/v3_0/PollChoices.php | 90 + Sources/Db/Schema/v3_0/Polls.php | 145 ++ Sources/Db/Schema/v3_0/Qanda.php | 94 + Sources/Db/Schema/v3_0/ScheduledTasks.php | 243 +++ Sources/Db/Schema/v3_0/Sessions.php | 82 + Sources/Db/Schema/v3_0/Settings.php | 893 +++++++++ Sources/Db/Schema/v3_0/SmileyFiles.php | 84 + Sources/Db/Schema/v3_0/Smileys.php | 104 ++ Sources/Db/Schema/v3_0/Spiders.php | 201 ++ Sources/Db/Schema/v3_0/Subscriptions.php | 143 ++ Sources/Db/Schema/v3_0/Themes.php | 149 ++ Sources/Db/Schema/v3_0/Topics.php | 242 +++ Sources/Db/Schema/v3_0/UserAlerts.php | 142 ++ Sources/Db/Schema/v3_0/UserAlertsPrefs.php | 228 +++ Sources/Db/Schema/v3_0/UserDrafts.php | 163 ++ Sources/Db/Schema/v3_0/UserLikes.php | 103 ++ Sources/Db/Schema/v3_0/index.php | 8 + 150 files changed, 24440 insertions(+) create mode 100644 Sources/Db/Schema/Column.php create mode 100644 Sources/Db/Schema/DbIndex.php create mode 100644 Sources/Db/Schema/Table.php create mode 100644 Sources/Db/Schema/index.php create mode 100644 Sources/Db/Schema/v2_1/AdminInfoFiles.php create mode 100644 Sources/Db/Schema/v2_1/ApprovalQueue.php create mode 100644 Sources/Db/Schema/v2_1/Attachments.php create mode 100644 Sources/Db/Schema/v2_1/BackgroundTasks.php create mode 100644 Sources/Db/Schema/v2_1/BanGroups.php create mode 100644 Sources/Db/Schema/v2_1/BanItems.php create mode 100644 Sources/Db/Schema/v2_1/BoardPermissions.php create mode 100644 Sources/Db/Schema/v2_1/BoardPermissionsView.php create mode 100644 Sources/Db/Schema/v2_1/Boards.php create mode 100644 Sources/Db/Schema/v2_1/Calendar.php create mode 100644 Sources/Db/Schema/v2_1/CalendarHolidays.php create mode 100644 Sources/Db/Schema/v2_1/Categories.php create mode 100644 Sources/Db/Schema/v2_1/CustomFields.php create mode 100644 Sources/Db/Schema/v2_1/GroupModerators.php create mode 100644 Sources/Db/Schema/v2_1/LogActions.php create mode 100644 Sources/Db/Schema/v2_1/LogActivity.php create mode 100644 Sources/Db/Schema/v2_1/LogBanned.php create mode 100644 Sources/Db/Schema/v2_1/LogBoards.php create mode 100644 Sources/Db/Schema/v2_1/LogComments.php create mode 100644 Sources/Db/Schema/v2_1/LogDigest.php create mode 100644 Sources/Db/Schema/v2_1/LogErrors.php create mode 100644 Sources/Db/Schema/v2_1/LogFloodcontrol.php create mode 100644 Sources/Db/Schema/v2_1/LogGroupRequests.php create mode 100644 Sources/Db/Schema/v2_1/LogMarkRead.php create mode 100644 Sources/Db/Schema/v2_1/LogMemberNotices.php create mode 100644 Sources/Db/Schema/v2_1/LogNotify.php create mode 100644 Sources/Db/Schema/v2_1/LogOnline.php create mode 100644 Sources/Db/Schema/v2_1/LogPackages.php create mode 100644 Sources/Db/Schema/v2_1/LogPolls.php create mode 100644 Sources/Db/Schema/v2_1/LogReported.php create mode 100644 Sources/Db/Schema/v2_1/LogReportedComments.php create mode 100644 Sources/Db/Schema/v2_1/LogScheduledTasks.php create mode 100644 Sources/Db/Schema/v2_1/LogSearchMessages.php create mode 100644 Sources/Db/Schema/v2_1/LogSearchResults.php create mode 100644 Sources/Db/Schema/v2_1/LogSearchSubjects.php create mode 100644 Sources/Db/Schema/v2_1/LogSearchTopics.php create mode 100644 Sources/Db/Schema/v2_1/LogSpiderHits.php create mode 100644 Sources/Db/Schema/v2_1/LogSpiderStats.php create mode 100644 Sources/Db/Schema/v2_1/LogSubscribed.php create mode 100644 Sources/Db/Schema/v2_1/LogTopics.php create mode 100644 Sources/Db/Schema/v2_1/MailQueue.php create mode 100644 Sources/Db/Schema/v2_1/MemberLogins.php create mode 100644 Sources/Db/Schema/v2_1/Membergroups.php create mode 100644 Sources/Db/Schema/v2_1/Members.php create mode 100644 Sources/Db/Schema/v2_1/Mentions.php create mode 100644 Sources/Db/Schema/v2_1/MessageIcons.php create mode 100644 Sources/Db/Schema/v2_1/Messages.php create mode 100644 Sources/Db/Schema/v2_1/ModeratorGroups.php create mode 100644 Sources/Db/Schema/v2_1/Moderators.php create mode 100644 Sources/Db/Schema/v2_1/PackageServers.php create mode 100644 Sources/Db/Schema/v2_1/PermissionProfiles.php create mode 100644 Sources/Db/Schema/v2_1/Permissions.php create mode 100644 Sources/Db/Schema/v2_1/PersonalMessages.php create mode 100644 Sources/Db/Schema/v2_1/PmLabeledMessages.php create mode 100644 Sources/Db/Schema/v2_1/PmLabels.php create mode 100644 Sources/Db/Schema/v2_1/PmRecipients.php create mode 100644 Sources/Db/Schema/v2_1/PmRules.php create mode 100644 Sources/Db/Schema/v2_1/PollChoices.php create mode 100644 Sources/Db/Schema/v2_1/Polls.php create mode 100644 Sources/Db/Schema/v2_1/Qanda.php create mode 100644 Sources/Db/Schema/v2_1/ScheduledTasks.php create mode 100644 Sources/Db/Schema/v2_1/Sessions.php create mode 100644 Sources/Db/Schema/v2_1/SmileyFiles.php create mode 100644 Sources/Db/Schema/v2_1/Smileys.php create mode 100644 Sources/Db/Schema/v2_1/Spiders.php create mode 100644 Sources/Db/Schema/v2_1/Subscriptions.php create mode 100644 Sources/Db/Schema/v2_1/Themes.php create mode 100644 Sources/Db/Schema/v2_1/Topics.php create mode 100644 Sources/Db/Schema/v2_1/UserAlerts.php create mode 100644 Sources/Db/Schema/v2_1/UserAlertsPrefs.php create mode 100644 Sources/Db/Schema/v2_1/UserDrafts.php create mode 100644 Sources/Db/Schema/v2_1/UserLikes.php create mode 100644 Sources/Db/Schema/v2_1/index.php create mode 100644 Sources/Db/Schema/v3_0/AdminInfoFiles.php create mode 100644 Sources/Db/Schema/v3_0/ApprovalQueue.php create mode 100644 Sources/Db/Schema/v3_0/Attachments.php create mode 100644 Sources/Db/Schema/v3_0/BackgroundTasks.php create mode 100644 Sources/Db/Schema/v3_0/BanGroups.php create mode 100644 Sources/Db/Schema/v3_0/BanItems.php create mode 100644 Sources/Db/Schema/v3_0/BoardPermissions.php create mode 100644 Sources/Db/Schema/v3_0/BoardPermissionsView.php create mode 100644 Sources/Db/Schema/v3_0/Boards.php create mode 100644 Sources/Db/Schema/v3_0/Calendar.php create mode 100644 Sources/Db/Schema/v3_0/Categories.php create mode 100644 Sources/Db/Schema/v3_0/CustomFields.php create mode 100644 Sources/Db/Schema/v3_0/GroupModerators.php create mode 100644 Sources/Db/Schema/v3_0/LogActions.php create mode 100644 Sources/Db/Schema/v3_0/LogActivity.php create mode 100644 Sources/Db/Schema/v3_0/LogBanned.php create mode 100644 Sources/Db/Schema/v3_0/LogBoards.php create mode 100644 Sources/Db/Schema/v3_0/LogComments.php create mode 100644 Sources/Db/Schema/v3_0/LogDigest.php create mode 100644 Sources/Db/Schema/v3_0/LogErrors.php create mode 100644 Sources/Db/Schema/v3_0/LogFloodcontrol.php create mode 100644 Sources/Db/Schema/v3_0/LogGroupRequests.php create mode 100644 Sources/Db/Schema/v3_0/LogMarkRead.php create mode 100644 Sources/Db/Schema/v3_0/LogMemberNotices.php create mode 100644 Sources/Db/Schema/v3_0/LogNotify.php create mode 100644 Sources/Db/Schema/v3_0/LogOnline.php create mode 100644 Sources/Db/Schema/v3_0/LogPackages.php create mode 100644 Sources/Db/Schema/v3_0/LogPolls.php create mode 100644 Sources/Db/Schema/v3_0/LogReported.php create mode 100644 Sources/Db/Schema/v3_0/LogReportedComments.php create mode 100644 Sources/Db/Schema/v3_0/LogScheduledTasks.php create mode 100644 Sources/Db/Schema/v3_0/LogSearchMessages.php create mode 100644 Sources/Db/Schema/v3_0/LogSearchResults.php create mode 100644 Sources/Db/Schema/v3_0/LogSearchSubjects.php create mode 100644 Sources/Db/Schema/v3_0/LogSearchTopics.php create mode 100644 Sources/Db/Schema/v3_0/LogSpiderHits.php create mode 100644 Sources/Db/Schema/v3_0/LogSpiderStats.php create mode 100644 Sources/Db/Schema/v3_0/LogSubscribed.php create mode 100644 Sources/Db/Schema/v3_0/LogTopics.php create mode 100644 Sources/Db/Schema/v3_0/MailQueue.php create mode 100644 Sources/Db/Schema/v3_0/MemberLogins.php create mode 100644 Sources/Db/Schema/v3_0/Membergroups.php create mode 100644 Sources/Db/Schema/v3_0/Members.php create mode 100644 Sources/Db/Schema/v3_0/Mentions.php create mode 100644 Sources/Db/Schema/v3_0/MessageIcons.php create mode 100644 Sources/Db/Schema/v3_0/Messages.php create mode 100644 Sources/Db/Schema/v3_0/ModeratorGroups.php create mode 100644 Sources/Db/Schema/v3_0/Moderators.php create mode 100644 Sources/Db/Schema/v3_0/PackageServers.php create mode 100644 Sources/Db/Schema/v3_0/PermissionProfiles.php create mode 100644 Sources/Db/Schema/v3_0/Permissions.php create mode 100644 Sources/Db/Schema/v3_0/PersonalMessages.php create mode 100644 Sources/Db/Schema/v3_0/PmLabeledMessages.php create mode 100644 Sources/Db/Schema/v3_0/PmLabels.php create mode 100644 Sources/Db/Schema/v3_0/PmRecipients.php create mode 100644 Sources/Db/Schema/v3_0/PmRules.php create mode 100644 Sources/Db/Schema/v3_0/PollChoices.php create mode 100644 Sources/Db/Schema/v3_0/Polls.php create mode 100644 Sources/Db/Schema/v3_0/Qanda.php create mode 100644 Sources/Db/Schema/v3_0/ScheduledTasks.php create mode 100644 Sources/Db/Schema/v3_0/Sessions.php create mode 100644 Sources/Db/Schema/v3_0/Settings.php create mode 100644 Sources/Db/Schema/v3_0/SmileyFiles.php create mode 100644 Sources/Db/Schema/v3_0/Smileys.php create mode 100644 Sources/Db/Schema/v3_0/Spiders.php create mode 100644 Sources/Db/Schema/v3_0/Subscriptions.php create mode 100644 Sources/Db/Schema/v3_0/Themes.php create mode 100644 Sources/Db/Schema/v3_0/Topics.php create mode 100644 Sources/Db/Schema/v3_0/UserAlerts.php create mode 100644 Sources/Db/Schema/v3_0/UserAlertsPrefs.php create mode 100644 Sources/Db/Schema/v3_0/UserDrafts.php create mode 100644 Sources/Db/Schema/v3_0/UserLikes.php create mode 100644 Sources/Db/Schema/v3_0/index.php diff --git a/Sources/Db/Schema/Column.php b/Sources/Db/Schema/Column.php new file mode 100644 index 00000000000..277359944a2 --- /dev/null +++ b/Sources/Db/Schema/Column.php @@ -0,0 +1,162 @@ +name = strtolower($name); + $this->type = strtolower($type); + + if (isset($default)) { + $this->default = $default === 'NULL' ? null : $default; + } + + foreach (['auto', 'size', 'unsigned', 'not_null'] as $var) { + if (isset($var)) { + $this->{$var} = ${$var}; + } + } + + if (isset($charset)) { + $this->charset = strtolower($charset); + } elseif ( + in_array( + $this->type, + [ + 'varchar', + 'char', + 'tinytext', + 'text', + 'mediumtext', + 'longtext', + 'enum', + 'set', + ], + ) + ) { + $this->charset = Config::$db_type === 'mysql' ? 'utf8mb4' : 'utf8'; + } + } +} diff --git a/Sources/Db/Schema/DbIndex.php b/Sources/Db/Schema/DbIndex.php new file mode 100644 index 00000000000..9cd88364cf5 --- /dev/null +++ b/Sources/Db/Schema/DbIndex.php @@ -0,0 +1,81 @@ +columns = array_map('strtolower', $columns); + + $this->type = isset($type) ? strtolower((string) $type) : null; + + if (($this->type ?? null) !== 'primary') { + $this->name = $name ?? 'idx_' . trim(implode('_', preg_replace(['/\s*/', '/\(\d+\)/'], ['', ''], $this->columns))); + } else { + $this->name = $name ?? 'primary'; + } + } +} diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php new file mode 100644 index 00000000000..cfc7b8475c6 --- /dev/null +++ b/Sources/Db/Schema/Table.php @@ -0,0 +1,278 @@ +default_charset = Db::$db->title === MYSQL_TITLE ? 'utf8mb4' : 'utf8'; + } + + /** + * Creates the table in the database. + * + * @see SMF\Db\DatabaseApi::create_table + * + * @param array $parameters Extra parameters. Currently only 'engine', the + * desired MySQL storage engine, is used. + * @param string $if_exists What to do if the table exists. + * @return bool Whether or not the operation was successful. + */ + public function create(array $parameters = [], string $if_exists = 'ignore'): bool + { + if (!isset($this->columns) || count($this->columns) === 0) { + return false; + } + + return 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, + ); + } + + /** + * Drop the table from the database. + * + * @see SMF\Db\DatabaseApi::drop_table + * + * @return bool Whether or not the operation was successful. + */ + public function drop(): bool + { + return Db::$db->drop_table('{db_prefix}' . $this->name); + } + + /** + * Get the table's current structure as it exists in the database. + * + * @see SMF\Db\DatabaseApi::table_structure + * + * @return array An array of table structure info: the name, the column + * info from SMF\Db\DatabaseApi::list_columns() and index info from + * SMF\Db\DatabaseApi::list_indexes(). + */ + public function getCurrentStructure(): array + { + return Db::$db->table_structure('{db_prefix}' . $this->name); + } + + /** + * Adds a column to this table in the database. + * + * @see SMF\Db\DatabaseApi::add_column + * + * @param Column $col The column to add to this table. + * @param string $if_exists What to do if the column exists. + * If 'update', column is updated. + * @return bool Whether or not the operation was successful. + */ + public function addColumn(Column $col, string $if_exists = 'update'): bool + { + return Db::$db->add_column( + '{db_prefix}' . $this->name, + get_object_vars($col), + [], + $if_exists, + ); + } + + /** + * Updates a column in the database to match the definition given by the + * supplied object's properties. + * + * @see SMF\Db\DatabaseApi::change_column + * + * @param Column $col The column to alter. + * @param ?string $old_name If passed, uses this as the old column name. + * @return bool Whether or not the operation was successful. + */ + public function alterColumn(Column $col, ?string $old_name = null): bool + { + return Db::$db->change_column( + '{db_prefix}' . $this->name, + $old_name ?? $col->name, + get_object_vars($col), + ); + } + + /** + * Drops a column from this table in the database. + * + * @see SMF\Db\DatabaseApi::remove_column + * + * @param Column $col The column to drop. + * @return bool Whether or not the operation was successful. + */ + public function dropColumn(Column $col): bool + { + return Db::$db->remove_column( + '{db_prefix}' . $this->name, + $col->name, + ); + } + + /** + * Adds an index to this table in the database. + * + * @see SMF\Db\DatabaseApi::add_index + * + * @param DbIndex $index The index to add to this table. + * @param string $if_exists What to do if the index exists. + * If 'update', index is updated. + * @return bool Whether or not the operation was successful. + */ + public function addIndex(DbIndex $index, string $if_exists = 'update'): bool + { + return Db::$db->add_index( + '{db_prefix}' . $this->name, + get_object_vars($index), + [], + $if_exists, + ); + } + + /** + * Updates an index in the database to match the definition given by the + * supplied object's properties. + * + * @param DbIndex $index The index to update. + * @return bool Whether or not the operation was successful. + */ + public function alterIndex(DbIndex $index): bool + { + // This method is really just a convenient way to replace an existing index. + $this->dropIndex($index); + + return $this->addIndex($index); + } + + /** + * Drops an index from this table in the database. + * + * @see SMF\Db\DatabaseApi::remove_index + * + * @param DbIndex $index The index to drop. + * @return bool Whether or not the operation was successful. + */ + public function dropIndex(DbIndex $index): bool + { + return Db::$db->remove_index( + '{db_prefix}' . $this->name, + $index->name, + ); + } + + /*********************** + * Public static methods + ***********************/ + + /** + * Gets all known table schemas. + * + * @return array All known table schemas. + */ + final public static function getAll(string $schema_version): array + { + $tables = []; + + $file_list = new \GlobIterator(__DIR__ . '/*.php', \FilesystemIterator::NEW_CURRENT_AND_KEY); + + foreach ($file_list as $file_path => $file_info) { + $class_name = $file_info->getBasename('.php'); + $fully_qualified_class_name = __NAMESPACE__ . '\\' . $schema_version . '\\' . $class_name; + + if (!class_exists($fully_qualified_class_name)) { + continue; + } + + $table = new $fully_qualified_class_name(); + + if ($table instanceof Table) { + $tables[$table->name] = $table; + } + } + + return $tables; + } +} diff --git a/Sources/Db/Schema/index.php b/Sources/Db/Schema/index.php new file mode 100644 index 00000000000..cc9dd085708 --- /dev/null +++ b/Sources/Db/Schema/index.php @@ -0,0 +1,8 @@ + 1, + 'filename' => 'current-version.js', + 'path' => '/smf/', + 'parameters' => 'version=%3$s', + 'data' => '', + 'filetype' => 'text/javascript', + ], + [ + 'id_file' => 2, + 'filename' => 'detailed-version.js', + 'path' => '/smf/', + 'parameters' => 'language=%1$s&version=%3$s', + 'data' => '', + 'filetype' => 'text/javascript', + ], + [ + 'id_file' => 3, + 'filename' => 'latest-news.js', + 'path' => '/smf/', + 'parameters' => 'language=%1$s&format=%2$s', + 'data' => '', + 'filetype' => 'text/javascript', + ], + [ + 'id_file' => 4, + 'filename' => 'latest-versions.txt', + 'path' => '/smf/', + 'parameters' => 'version=%3$s', + 'data' => '', + 'filetype' => 'text/plain', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'admin_info_files'; + + $this->columns = [ + 'id_file' => new Column( + name: 'id_file', + type: 'tinyint', + unsigned: true, + auto: true, + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'path' => new Column( + name: 'path', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'parameters' => new Column( + name: 'parameters', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'data' => new Column( + name: 'data', + type: 'text', + not_null: true, + ), + 'filetype' => new Column( + name: 'filetype', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_file', + ], + ), + 'idx_filename' => new DbIndex( + name: 'idx_filename', + columns: [ + 'filename(30)', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/ApprovalQueue.php b/Sources/Db/Schema/v2_1/ApprovalQueue.php new file mode 100644 index 00000000000..5c79741294e --- /dev/null +++ b/Sources/Db/Schema/v2_1/ApprovalQueue.php @@ -0,0 +1,72 @@ +name = 'approval_queue'; + + $this->columns = [ + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_attach' => new Column( + name: 'id_attach', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_event' => new Column( + name: 'id_event', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Attachments.php b/Sources/Db/Schema/v2_1/Attachments.php new file mode 100644 index 00000000000..e060e36a72e --- /dev/null +++ b/Sources/Db/Schema/v2_1/Attachments.php @@ -0,0 +1,189 @@ +name = 'attachments'; + + $this->columns = [ + 'id_attach' => new Column( + name: 'id_attach', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_thumb' => new Column( + name: 'id_thumb', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_folder' => new Column( + name: 'id_folder', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'attachment_type' => new Column( + name: 'attachment_type', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'file_hash' => new Column( + name: 'file_hash', + type: 'varchar', + size: 40, + not_null: true, + default: '', + ), + 'fileext' => new Column( + name: 'fileext', + type: 'varchar', + size: 8, + not_null: true, + default: '', + ), + 'size' => new Column( + name: 'size', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'downloads' => new Column( + name: 'downloads', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'width' => new Column( + name: 'width', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'height' => new Column( + name: 'height', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'mime_type' => new Column( + name: 'mime_type', + type: 'varchar', + size: 128, + not_null: true, + default: '', + ), + 'approved' => new Column( + name: 'approved', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_attach', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'id_attach', + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + 'id_msg', + ], + ), + 'idx_attachment_type' => new DbIndex( + name: 'idx_attachment_type', + columns: [ + 'attachment_type', + ], + ), + 'idx_id_thumb' => new DbIndex( + name: 'idx_id_thumb', + columns: [ + 'id_thumb', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/BackgroundTasks.php b/Sources/Db/Schema/v2_1/BackgroundTasks.php new file mode 100644 index 00000000000..42e732eb17c --- /dev/null +++ b/Sources/Db/Schema/v2_1/BackgroundTasks.php @@ -0,0 +1,93 @@ +name = 'background_tasks'; + + $this->columns = [ + 'id_task' => new Column( + name: 'id_task', + type: 'int', + unsigned: true, + auto: true, + ), + 'task_file' => new Column( + name: 'task_file', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'task_class' => new Column( + name: 'task_class', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'task_data' => new Column( + name: 'task_data', + type: 'mediumtext', + not_null: true, + ), + 'claimed_time' => new Column( + name: 'claimed_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_task', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/BanGroups.php b/Sources/Db/Schema/v2_1/BanGroups.php new file mode 100644 index 00000000000..512897e6f5c --- /dev/null +++ b/Sources/Db/Schema/v2_1/BanGroups.php @@ -0,0 +1,126 @@ +name = 'ban_groups'; + + $this->columns = [ + 'id_ban_group' => new Column( + name: 'id_ban_group', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 20, + not_null: true, + default: '', + ), + 'ban_time' => new Column( + name: 'ban_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'expire_time' => new Column( + name: 'expire_time', + type: 'int', + unsigned: true, + ), + 'cannot_access' => new Column( + name: 'cannot_access', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'cannot_register' => new Column( + name: 'cannot_register', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'cannot_post' => new Column( + name: 'cannot_post', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'cannot_login' => new Column( + name: 'cannot_login', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'reason' => new Column( + name: 'reason', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'notes' => new Column( + name: 'notes', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_ban_group', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/BanItems.php b/Sources/Db/Schema/v2_1/BanItems.php new file mode 100644 index 00000000000..d6450924327 --- /dev/null +++ b/Sources/Db/Schema/v2_1/BanItems.php @@ -0,0 +1,125 @@ +name = 'ban_items'; + + $this->columns = [ + 'id_ban' => new Column( + name: 'id_ban', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_ban_group' => new Column( + name: 'id_ban_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip_low' => new Column( + name: 'ip_low', + type: 'inet', + size: 16, + ), + 'ip_high' => new Column( + name: 'ip_high', + type: 'inet', + size: 16, + ), + 'hostname' => new Column( + name: 'hostname', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'email_address' => new Column( + name: 'email_address', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'hits' => new Column( + name: 'hits', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_ban', + ], + ), + 'idx_id_ban_group' => new DbIndex( + name: 'idx_id_ban_group', + columns: [ + 'id_ban_group', + ], + ), + 'idx_id_ban_ip' => new DbIndex( + name: 'idx_id_ban_ip', + columns: [ + 'ip_low', + 'ip_high', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/BoardPermissions.php b/Sources/Db/Schema/v2_1/BoardPermissions.php new file mode 100644 index 00000000000..1d933157165 --- /dev/null +++ b/Sources/Db/Schema/v2_1/BoardPermissions.php @@ -0,0 +1,1627 @@ + -1, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'remove_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_add_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_edit_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_new', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => -1, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'remove_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_new', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => -1, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'remove_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => -1, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'view_attachments', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'board_permissions'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + default: 0, + ), + 'id_profile' => new Column( + name: 'id_profile', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'permission' => new Column( + name: 'permission', + type: 'varchar', + size: 30, + default: '', + ), + 'add_deny' => new Column( + name: 'add_deny', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'id_profile', + 'permission', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/BoardPermissionsView.php b/Sources/Db/Schema/v2_1/BoardPermissionsView.php new file mode 100644 index 00000000000..eeea940d3b8 --- /dev/null +++ b/Sources/Db/Schema/v2_1/BoardPermissionsView.php @@ -0,0 +1,96 @@ + -1, + 'id_board' => 1, + 'deny' => 0, + ], + [ + 'id_group' => 0, + 'id_board' => 1, + 'deny' => 0, + ], + [ + 'id_group' => 2, + 'id_board' => 1, + 'deny' => 0, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'board_permissions_view'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + ), + 'deny' => new Column( + name: 'deny', + type: 'smallint', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'id_board', + 'deny', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Boards.php b/Sources/Db/Schema/v2_1/Boards.php new file mode 100644 index 00000000000..4e64c3fed10 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Boards.php @@ -0,0 +1,233 @@ + 1, + 'id_cat' => 1, + 'board_order' => 1, + 'id_last_msg' => 1, + 'id_msg_updated' => 1, + 'name' => '{$default_board_name}', + 'description' => '{$default_board_description}', + 'num_topics' => 1, + 'num_posts' => 1, + 'member_groups' => '-1,0,2', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'boards'; + + $this->columns = [ + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'id_cat' => new Column( + name: 'id_cat', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'child_level' => new Column( + name: 'child_level', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_parent' => new Column( + name: 'id_parent', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'board_order' => new Column( + name: 'board_order', + type: 'smallint', + not_null: true, + default: 0, + ), + 'id_last_msg' => new Column( + name: 'id_last_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg_updated' => new Column( + name: 'id_msg_updated', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_groups' => new Column( + name: 'member_groups', + type: 'varchar', + size: 255, + not_null: true, + default: '-1,0', + ), + 'id_profile' => new Column( + name: 'id_profile', + type: 'smallint', + unsigned: true, + not_null: true, + default: 1, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'text', + not_null: true, + ), + 'num_topics' => new Column( + name: 'num_topics', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_posts' => new Column( + name: 'num_posts', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'count_posts' => new Column( + name: 'count_posts', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'id_theme' => new Column( + name: 'id_theme', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'override_theme' => new Column( + name: 'override_theme', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'unapproved_posts' => new Column( + name: 'unapproved_posts', + type: 'smallint', + not_null: true, + default: 0, + ), + 'unapproved_topics' => new Column( + name: 'unapproved_topics', + type: 'smallint', + not_null: true, + default: 0, + ), + 'redirect' => new Column( + name: 'redirect', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'deny_member_groups' => new Column( + name: 'deny_member_groups', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_board', + ], + ), + 'idx_categories' => new DbIndex( + name: 'idx_categories', + type: 'unique', + columns: [ + 'id_cat', + 'id_board', + ], + ), + 'idx_id_parent' => new DbIndex( + name: 'idx_id_parent', + columns: [ + 'id_parent', + ], + ), + 'idx_id_msg_updated' => new DbIndex( + name: 'idx_id_msg_updated', + columns: [ + 'id_msg_updated', + ], + ), + 'idx_member_groups' => new DbIndex( + name: 'idx_member_groups', + columns: [ + 'member_groups(48)', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Calendar.php b/Sources/Db/Schema/v2_1/Calendar.php new file mode 100644 index 00000000000..7fbf37c8bfb --- /dev/null +++ b/Sources/Db/Schema/v2_1/Calendar.php @@ -0,0 +1,146 @@ +name = 'calendar'; + + $this->columns = [ + 'id_event' => new Column( + name: 'id_event', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'start_date' => new Column( + name: 'start_date', + type: 'date', + not_null: true, + default: '1004-01-01', + ), + 'end_date' => new Column( + name: 'end_date', + type: 'date', + not_null: true, + default: '1004-01-01', + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'title' => new Column( + name: 'title', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'start_time' => new Column( + name: 'start_time', + type: 'time', + ), + 'end_time' => new Column( + name: 'end_time', + type: 'time', + ), + 'timezone' => new Column( + name: 'timezone', + type: 'varchar', + size: 80, + ), + 'location' => new Column( + name: 'location', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_event', + ], + ), + 'idx_start_date' => new DbIndex( + name: 'idx_start_date', + columns: [ + 'start_date', + ], + ), + 'idx_end_date' => new DbIndex( + name: 'idx_end_date', + columns: [ + 'end_date', + ], + ), + 'idx_topic' => new DbIndex( + name: 'idx_topic', + columns: [ + 'id_topic', + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/CalendarHolidays.php b/Sources/Db/Schema/v2_1/CalendarHolidays.php new file mode 100644 index 00000000000..8b26a1658ab --- /dev/null +++ b/Sources/Db/Schema/v2_1/CalendarHolidays.php @@ -0,0 +1,623 @@ + 'New Year\'s', + 'event_date' => '1004-01-01', + ], + [ + 'title' => 'Christmas', + 'event_date' => '1004-12-25', + ], + [ + 'title' => 'Valentine\'s Day', + 'event_date' => '1004-02-14', + ], + [ + 'title' => 'St. Patrick\'s Day', + 'event_date' => '1004-03-17', + ], + [ + 'title' => 'April Fools', + 'event_date' => '1004-04-01', + ], + [ + 'title' => 'Earth Day', + 'event_date' => '1004-04-22', + ], + [ + 'title' => 'United Nations Day', + 'event_date' => '1004-10-24', + ], + [ + 'title' => 'Halloween', + 'event_date' => '1004-10-31', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2010-05-09', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2011-05-08', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2012-05-13', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2013-05-12', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2014-05-11', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2015-05-10', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2016-05-08', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2017-05-14', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2018-05-13', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2019-05-12', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2020-05-10', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2021-05-09', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2022-05-08', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2023-05-14', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2024-05-12', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2025-05-11', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2026-05-10', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2027-05-09', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2028-05-14', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2029-05-13', + ], + [ + 'title' => 'Mother\'s Day', + 'event_date' => '2030-05-12', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2010-06-20', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2011-06-19', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2012-06-17', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2013-06-16', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2014-06-15', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2015-06-21', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2016-06-19', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2017-06-18', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2018-06-17', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2019-06-16', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2020-06-21', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2021-06-20', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2022-06-19', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2023-06-18', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2024-06-16', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2025-06-15', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2026-06-21', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2027-06-20', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2028-06-18', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2029-06-17', + ], + [ + 'title' => 'Father\'s Day', + 'event_date' => '2030-06-16', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2010-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2011-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2012-06-20', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2013-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2014-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2015-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2016-06-20', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2017-06-20', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2018-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2019-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2020-06-20', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2021-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2022-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2023-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2024-06-20', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2025-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2026-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2027-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2028-06-20', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2029-06-21', + ], + [ + 'title' => 'Summer Solstice', + 'event_date' => '2030-06-21', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2010-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2011-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2012-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2013-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2014-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2015-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2016-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2017-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2018-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2019-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2020-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2021-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2022-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2023-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2024-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2025-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2026-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2027-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2028-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2029-03-20', + ], + [ + 'title' => 'Vernal Equinox', + 'event_date' => '2030-03-20', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2010-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2011-12-22', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2012-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2013-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2014-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2015-12-22', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2016-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2017-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2018-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2019-12-22', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2020-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2021-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2022-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2023-12-22', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2024-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2025-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2026-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2027-12-22', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2028-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2029-12-21', + ], + [ + 'title' => 'Winter Solstice', + 'event_date' => '2030-12-21', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2010-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2011-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2012-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2013-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2014-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2015-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2016-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2017-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2018-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2019-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2020-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2021-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2022-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2023-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2024-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2025-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2026-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2027-09-23', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2028-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2029-09-22', + ], + [ + 'title' => 'Autumnal Equinox', + 'event_date' => '2030-09-22', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'calendar_holidays'; + + $this->columns = [ + 'id_holiday' => new Column( + name: 'id_holiday', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'event_date' => new Column( + name: 'event_date', + type: 'date', + not_null: true, + default: '1004-01-01', + ), + 'title' => new Column( + name: 'title', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_holiday', + ], + ), + 'idx_event_date' => new DbIndex( + name: 'idx_event_date', + columns: [ + 'event_date', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Categories.php b/Sources/Db/Schema/v2_1/Categories.php new file mode 100644 index 00000000000..80b40077cd9 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Categories.php @@ -0,0 +1,99 @@ + 1, + 'cat_order' => 0, + 'name' => '{$default_category_name}', + 'description' => '', + 'can_collapse' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'categories'; + + $this->columns = [ + 'id_cat' => new Column( + name: 'id_cat', + type: 'tinyint', + unsigned: true, + auto: true, + ), + 'cat_order' => new Column( + name: 'cat_order', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'text', + not_null: true, + ), + 'can_collapse' => new Column( + name: 'can_collapse', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_cat', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/CustomFields.php b/Sources/Db/Schema/v2_1/CustomFields.php new file mode 100644 index 00000000000..ce9408547ac --- /dev/null +++ b/Sources/Db/Schema/v2_1/CustomFields.php @@ -0,0 +1,277 @@ + 'cust_icq', + 'field_name' => '{icq}', + 'field_desc' => '{icq_desc}', + 'field_type' => 'text', + 'field_length' => 12, + 'field_options' => '', + 'field_order' => 1, + 'mask' => 'regex~[1-9][0-9]{4,9}~i', + 'show_reg' => 0, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '', + 'enclose' => 'ICQ - {INPUT}', + 'placement' => 1, + ], + [ + 'col_name' => 'cust_skype', + 'field_name' => '{skype}', + 'field_desc' => '{skype_desc}', + 'field_type' => 'text', + 'field_length' => 32, + 'field_options' => '', + 'field_order' => 2, + 'mask' => 'nohtml', + 'show_reg' => 0, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '', + 'enclose' => '{INPUT} ', + 'placement' => 1, + ], + [ + 'col_name' => 'cust_loca', + 'field_name' => '{location}', + 'field_desc' => '{location_desc}', + 'field_type' => 'text', + 'field_length' => 50, + 'field_options' => '', + 'field_order' => 4, + 'mask' => 'nohtml', + 'show_reg' => 0, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '', + 'enclose' => '', + 'placement' => 0, + ], + [ + 'col_name' => 'cust_gender', + 'field_name' => '{gender}', + 'field_desc' => '{gender_desc}', + 'field_type' => 'radio', + 'field_length' => 255, + 'field_options' => '{gender_0},{gender_1},{gender_2}', + 'field_order' => 5, + 'mask' => 'nohtml', + 'show_reg' => 1, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '{gender_0}', + 'enclose' => '', + 'placement' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'custom_fields'; + + $this->columns = [ + 'id_field' => new Column( + name: 'id_field', + type: 'smallint', + auto: true, + ), + 'col_name' => new Column( + name: 'col_name', + type: 'varchar', + size: 12, + not_null: true, + default: '', + ), + 'field_name' => new Column( + name: 'field_name', + type: 'varchar', + size: 40, + not_null: true, + default: '', + ), + 'field_desc' => new Column( + name: 'field_desc', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'field_type' => new Column( + name: 'field_type', + type: 'varchar', + size: 8, + not_null: true, + default: 'text', + ), + 'field_length' => new Column( + name: 'field_length', + type: 'smallint', + not_null: true, + default: 255, + ), + 'field_options' => new Column( + name: 'field_options', + type: 'text', + not_null: true, + ), + 'field_order' => new Column( + name: 'field_order', + type: 'smallint', + not_null: true, + default: 0, + ), + 'mask' => new Column( + name: 'mask', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'show_reg' => new Column( + name: 'show_reg', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'show_display' => new Column( + name: 'show_display', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'show_mlist' => new Column( + name: 'show_mlist', + type: 'smallint', + not_null: true, + default: 0, + ), + 'show_profile' => new Column( + name: 'show_profile', + type: 'varchar', + size: 20, + not_null: true, + default: 'forumprofile', + ), + 'private' => new Column( + name: 'private', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'active' => new Column( + name: 'active', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'bbc' => new Column( + name: 'bbc', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'can_search' => new Column( + name: 'can_search', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'default_value' => new Column( + name: 'default_value', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'enclose' => new Column( + name: 'enclose', + type: 'text', + not_null: true, + ), + 'placement' => new Column( + name: 'placement', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_field', + ], + ), + 'idx_col_name' => new DbIndex( + name: 'idx_col_name', + type: 'unique', + columns: [ + 'col_name', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/GroupModerators.php b/Sources/Db/Schema/v2_1/GroupModerators.php new file mode 100644 index 00000000000..2b15bc0bbac --- /dev/null +++ b/Sources/Db/Schema/v2_1/GroupModerators.php @@ -0,0 +1,74 @@ +name = 'group_moderators'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogActions.php b/Sources/Db/Schema/v2_1/LogActions.php new file mode 100644 index 00000000000..f6b1c898057 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogActions.php @@ -0,0 +1,163 @@ +name = 'log_actions'; + + $this->columns = [ + 'id_action' => new Column( + name: 'id_action', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_log' => new Column( + name: 'id_log', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'action' => new Column( + name: 'action', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'extra' => new Column( + name: 'extra', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_action', + ], + ), + 'idx_id_log' => new DbIndex( + name: 'idx_id_log', + columns: [ + 'id_log', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + 'id_board', + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + 'id_msg', + ], + ), + 'idx_id_topic_id_log' => new DbIndex( + name: 'idx_id_topic_id_log', + columns: [ + 'id_topic', + 'id_log', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogActivity.php b/Sources/Db/Schema/v2_1/LogActivity.php new file mode 100644 index 00000000000..8edbca5c3e2 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogActivity.php @@ -0,0 +1,101 @@ +name = 'log_activity'; + + $this->columns = [ + 'date' => new Column( + name: 'date', + type: 'date', + not_null: true, + ), + 'hits' => new Column( + name: 'hits', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'topics' => new Column( + name: 'topics', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'posts' => new Column( + name: 'posts', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'registers' => new Column( + name: 'registers', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'most_on' => new Column( + name: 'most_on', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'date', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogBanned.php b/Sources/Db/Schema/v2_1/LogBanned.php new file mode 100644 index 00000000000..1cf339991fb --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogBanned.php @@ -0,0 +1,99 @@ +name = 'log_banned'; + + $this->columns = [ + 'id_ban_log' => new Column( + name: 'id_ban_log', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'email' => new Column( + name: 'email', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_ban_log', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogBoards.php b/Sources/Db/Schema/v2_1/LogBoards.php new file mode 100644 index 00000000000..1dd0e48e403 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogBoards.php @@ -0,0 +1,81 @@ +name = 'log_boards'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_board', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogComments.php b/Sources/Db/Schema/v2_1/LogComments.php new file mode 100644 index 00000000000..78bb3629703 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogComments.php @@ -0,0 +1,144 @@ +name = 'log_comments'; + + $this->columns = [ + 'id_comment' => new Column( + name: 'id_comment', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_name' => new Column( + name: 'member_name', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'comment_type' => new Column( + name: 'comment_type', + type: 'varchar', + size: 8, + not_null: true, + default: 'warning', + ), + 'id_recipient' => new Column( + name: 'id_recipient', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'recipient_name' => new Column( + name: 'recipient_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + not_null: true, + default: 0, + ), + 'id_notice' => new Column( + name: 'id_notice', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'counter' => new Column( + name: 'counter', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_comment', + ], + ), + 'idx_id_recipient' => new DbIndex( + name: 'idx_id_recipient', + columns: [ + 'id_recipient', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_comment_type' => new DbIndex( + name: 'idx_comment_type', + columns: [ + 'comment_type(8)', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogDigest.php b/Sources/Db/Schema/v2_1/LogDigest.php new file mode 100644 index 00000000000..f437c1a636d --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogDigest.php @@ -0,0 +1,86 @@ +name = 'log_digest'; + + $this->columns = [ + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'note_type' => new Column( + name: 'note_type', + type: 'varchar', + size: 10, + not_null: true, + default: 'post', + ), + 'daily' => new Column( + name: 'daily', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'exclude' => new Column( + name: 'exclude', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogErrors.php b/Sources/Db/Schema/v2_1/LogErrors.php new file mode 100644 index 00000000000..b5451d4d547 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogErrors.php @@ -0,0 +1,149 @@ +name = 'log_errors'; + + $this->columns = [ + 'id_error' => new Column( + name: 'id_error', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'url' => new Column( + name: 'url', + type: 'text', + not_null: true, + ), + 'message' => new Column( + name: 'message', + type: 'text', + not_null: true, + ), + 'session' => new Column( + name: 'session', + type: 'varchar', + size: 128, + not_null: true, + default: '', + ), + 'error_type' => new Column( + name: 'error_type', + type: 'char', + size: 15, + not_null: true, + default: 'general', + ), + 'file' => new Column( + name: 'file', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'line' => new Column( + name: 'line', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'backtrace' => new Column( + name: 'backtrace', + type: 'varchar', + size: 10000, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_error', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_ip' => new DbIndex( + name: 'idx_ip', + columns: [ + 'ip', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogFloodcontrol.php b/Sources/Db/Schema/v2_1/LogFloodcontrol.php new file mode 100644 index 00000000000..046b0bc061d --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogFloodcontrol.php @@ -0,0 +1,93 @@ +name = 'log_floodcontrol'; + + $this->columns = [ + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'log_type' => new Column( + name: 'log_type', + type: 'varchar', + size: 30, + default: 'post', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'ip', + 'log_type', + ], + ), + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_request', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + 'id_group', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogGroupRequests.php b/Sources/Db/Schema/v2_1/LogGroupRequests.php new file mode 100644 index 00000000000..b9a986eb3b2 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogGroupRequests.php @@ -0,0 +1,140 @@ +name = 'log_group_requests'; + + $this->columns = [ + 'id_request' => new Column( + name: 'id_request', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'time_applied' => new Column( + name: 'time_applied', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'reason' => new Column( + name: 'reason', + type: 'text', + not_null: true, + ), + 'comment_type' => new Column( + name: 'comment_type', + type: 'varchar', + size: 8, + not_null: true, + default: 'warning', + ), + 'status' => new Column( + name: 'status', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_acted' => new Column( + name: 'id_member_acted', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_name_acted' => new Column( + name: 'member_name_acted', + type: 'varchar', + size: 255, + not_null: true, + default: 0, + ), + 'time_acted' => new Column( + name: 'time_acted', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'act_reason' => new Column( + name: 'act_reason', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_request', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + 'id_group', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogMarkRead.php b/Sources/Db/Schema/v2_1/LogMarkRead.php new file mode 100644 index 00000000000..ca60766a537 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogMarkRead.php @@ -0,0 +1,81 @@ +name = 'log_mark_read'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_board', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogMemberNotices.php b/Sources/Db/Schema/v2_1/LogMemberNotices.php new file mode 100644 index 00000000000..7c4a7cd9d41 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogMemberNotices.php @@ -0,0 +1,79 @@ +name = 'log_member_notices'; + + $this->columns = [ + 'id_notice' => new Column( + name: 'id_notice', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_notice', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogNotify.php b/Sources/Db/Schema/v2_1/LogNotify.php new file mode 100644 index 00000000000..38e8ef08ec2 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogNotify.php @@ -0,0 +1,95 @@ +name = 'log_notify'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'sent' => new Column( + name: 'sent', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_topic', + 'id_board', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogOnline.php b/Sources/Db/Schema/v2_1/LogOnline.php new file mode 100644 index 00000000000..e05d228087b --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogOnline.php @@ -0,0 +1,123 @@ +name = 'log_online'; + + $this->columns = [ + 'session' => new Column( + name: 'session', + type: 'varchar', + size: 128, + default: '', + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'url' => new Column( + name: 'url', + type: 'varchar', + size: 2048, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'session', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_install', + ], + ), + 'idx_filename' => new DbIndex( + name: 'idx_filename', + columns: [ + 'filename(15)', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogPackages.php b/Sources/Db/Schema/v2_1/LogPackages.php new file mode 100644 index 00000000000..780b4410069 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogPackages.php @@ -0,0 +1,183 @@ +name = 'log_packages'; + + $this->columns = [ + 'id_install' => new Column( + name: 'id_install', + type: 'int', + unsigned: true, + auto: true, + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'package_id' => new Column( + name: 'package_id', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'version' => new Column( + name: 'version', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'id_member_installed' => new Column( + name: 'id_member_installed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'member_installed' => new Column( + name: 'member_installed', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'time_installed' => new Column( + name: 'time_installed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'id_member_removed' => new Column( + name: 'id_member_removed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'member_removed' => new Column( + name: 'member_removed', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'time_removed' => new Column( + name: 'time_removed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'install_state' => new Column( + name: 'install_state', + type: 'mediumint', + not_null: true, + default: 1, + ), + 'failed_steps' => new Column( + name: 'failed_steps', + type: 'text', + not_null: true, + default: false, + ), + 'themes_installed' => new Column( + name: 'themes_installed', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'db_changes' => new Column( + name: 'db_changes', + type: 'text', + not_null: true, + default: false, + ), + 'credits' => new Column( + name: 'credits', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'sha256_hash' => new Column( + name: 'sha256_hash', + type: 'text', + not_null: false, + default: null, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_install', + ], + ), + 'filename' => new DbIndex( + name: 'filename', + columns: [ + 'filename', + ], + ), + 'id_hash' => new DbIndex( + name: 'id_hash', + columns: [ + 'id_hash', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogPolls.php b/Sources/Db/Schema/v2_1/LogPolls.php new file mode 100644 index 00000000000..d0bc70cba2a --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogPolls.php @@ -0,0 +1,84 @@ +name = 'log_polls'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_choice' => new Column( + name: 'id_choice', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'idx_id_poll' => new DbIndex( + name: 'idx_id_poll', + columns: [ + 'id_poll', + 'id_member', + 'id_choice', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogReported.php b/Sources/Db/Schema/v2_1/LogReported.php new file mode 100644 index 00000000000..813db0e197a --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogReported.php @@ -0,0 +1,174 @@ +name = 'log_reported'; + + $this->columns = [ + 'id_report' => new Column( + name: 'id_report', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'membername' => new Column( + name: 'membername', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'mediumtext', + not_null: true, + ), + 'time_started' => new Column( + name: 'time_started', + type: 'int', + not_null: true, + default: 0, + ), + 'time_updated' => new Column( + name: 'time_updated', + type: 'int', + not_null: true, + default: 0, + ), + 'num_reports' => new Column( + name: 'num_reports', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'closed' => new Column( + name: 'closed', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'ignore_all' => new Column( + name: 'ignore_all', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_report', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + ], + ), + 'idx_closed' => new DbIndex( + name: 'idx_closed', + columns: [ + 'closed', + ], + ), + 'idx_time_started' => new DbIndex( + name: 'idx_time_started', + columns: [ + 'time_started', + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + 'id_msg', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogReportedComments.php b/Sources/Db/Schema/v2_1/LogReportedComments.php new file mode 100644 index 00000000000..dfa7a7045d3 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogReportedComments.php @@ -0,0 +1,120 @@ +name = 'log_reported_comments'; + + $this->columns = [ + 'id_comment' => new Column( + name: 'id_comment', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_report' => new Column( + name: 'id_report', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + not_null: true, + ), + 'membername' => new Column( + name: 'membername', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'member_ip' => new Column( + name: 'member_ip', + type: 'inet', + size: 16, + ), + 'comment' => new Column( + name: 'comment', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'time_sent' => new Column( + name: 'time_sent', + type: 'int', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_comment', + ], + ), + 'idx_id_report' => new DbIndex( + name: 'idx_id_report', + columns: [ + 'id_report', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + 'time_sent', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogScheduledTasks.php b/Sources/Db/Schema/v2_1/LogScheduledTasks.php new file mode 100644 index 00000000000..6e53995a3fb --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogScheduledTasks.php @@ -0,0 +1,84 @@ +name = 'log_scheduled_tasks'; + + $this->columns = [ + 'id_log' => new Column( + name: 'id_log', + type: 'mediumint', + auto: true, + ), + 'id_task' => new Column( + name: 'id_task', + type: 'smallint', + not_null: true, + default: 0, + ), + 'time_run' => new Column( + name: 'time_run', + type: 'int', + not_null: true, + default: 0, + ), + 'time_taken' => new Column( + name: 'time_taken', + type: 'float', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_log', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchMessages.php b/Sources/Db/Schema/v2_1/LogSearchMessages.php new file mode 100644 index 00000000000..8ad2df893a9 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchMessages.php @@ -0,0 +1,74 @@ +name = 'log_search_messages'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_search', + 'id_msg', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchResults.php b/Sources/Db/Schema/v2_1/LogSearchResults.php new file mode 100644 index 00000000000..c05e9c2fa22 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchResults.php @@ -0,0 +1,95 @@ +name = 'log_search_results'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'relevance' => new Column( + name: 'relevance', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_matches' => new Column( + name: 'num_matches', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_search', + 'id_topic', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchSubjects.php b/Sources/Db/Schema/v2_1/LogSearchSubjects.php new file mode 100644 index 00000000000..101bc519556 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchSubjects.php @@ -0,0 +1,80 @@ +name = 'log_search_subjects'; + + $this->columns = [ + 'word' => new Column( + name: 'word', + type: 'varchar', + size: 20, + default: '', + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'word', + 'id_topic', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogSearchTopics.php b/Sources/Db/Schema/v2_1/LogSearchTopics.php new file mode 100644 index 00000000000..f065f44380f --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSearchTopics.php @@ -0,0 +1,74 @@ +name = 'log_search_topics'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_search', + 'id_topic', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogSpiderHits.php b/Sources/Db/Schema/v2_1/LogSpiderHits.php new file mode 100644 index 00000000000..a6a2a9e724a --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSpiderHits.php @@ -0,0 +1,100 @@ +name = 'log_spider_hits'; + + $this->columns = [ + 'id_hit' => new Column( + name: 'id_hit', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'url' => new Column( + name: 'url', + type: 'varchar', + size: 1024, + not_null: true, + default: '', + ), + 'processed' => new Column( + name: 'processed', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_hit', + ], + ), + 'idx_processed' => new DbIndex( + name: 'idx_processed', + columns: [ + 'processed', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogSpiderStats.php b/Sources/Db/Schema/v2_1/LogSpiderStats.php new file mode 100644 index 00000000000..8de1101b218 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSpiderStats.php @@ -0,0 +1,86 @@ +name = 'log_spider_stats'; + + $this->columns = [ + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'page_hits' => new Column( + name: 'page_hits', + type: 'int', + not_null: true, + default: 0, + ), + 'last_seen' => new Column( + name: 'last_seen', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'stat_date' => new Column( + name: 'stat_date', + type: 'date', + default: '1004-01-01', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'stat_date', + 'id_spider', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogSubscribed.php b/Sources/Db/Schema/v2_1/LogSubscribed.php new file mode 100644 index 00000000000..a82014d6b8f --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogSubscribed.php @@ -0,0 +1,158 @@ +name = 'log_subscribed'; + + $this->columns = [ + 'id_sublog' => new Column( + name: 'id_sublog', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_subscribe' => new Column( + name: 'id_subscribe', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'int', + not_null: true, + default: 0, + ), + 'old_id_group' => new Column( + name: 'old_id_group', + type: 'smallint', + not_null: true, + default: 0, + ), + 'start_time' => new Column( + name: 'start_time', + type: 'int', + not_null: true, + default: 0, + ), + 'end_time' => new Column( + name: 'end_time', + type: 'int', + not_null: true, + default: 0, + ), + 'status' => new Column( + name: 'status', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'payments_pending' => new Column( + name: 'payments_pending', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'pending_details' => new Column( + name: 'pending_details', + type: 'text', + not_null: true, + ), + 'reminder_sent' => new Column( + name: 'reminder_sent', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'vendor_ref' => new Column( + name: 'vendor_ref', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_sublog', + ], + ), + 'idx_end_time' => new DbIndex( + name: 'idx_end_time', + columns: [ + 'end_time', + ], + ), + 'idx_reminder_sent' => new DbIndex( + name: 'idx_reminder_sent', + columns: [ + 'reminder_sent', + ], + ), + 'idx_payments_pending' => new DbIndex( + name: 'idx_payments_pending', + columns: [ + 'payments_pending', + ], + ), + 'idx_status' => new DbIndex( + name: 'idx_status', + columns: [ + 'status', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/LogTopics.php b/Sources/Db/Schema/v2_1/LogTopics.php new file mode 100644 index 00000000000..2feae003c88 --- /dev/null +++ b/Sources/Db/Schema/v2_1/LogTopics.php @@ -0,0 +1,93 @@ +name = 'log_topics'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'unwatched' => new Column( + name: 'unwatched', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_topic', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/MailQueue.php b/Sources/Db/Schema/v2_1/MailQueue.php new file mode 100644 index 00000000000..9901afc5e04 --- /dev/null +++ b/Sources/Db/Schema/v2_1/MailQueue.php @@ -0,0 +1,128 @@ +name = 'mail_queue'; + + $this->columns = [ + 'id_mail' => new Column( + name: 'id_mail', + type: 'int', + unsigned: true, + auto: true, + ), + 'time_sent' => new Column( + name: 'time_sent', + type: 'int', + not_null: true, + default: 0, + ), + 'recipient' => new Column( + name: 'recipient', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'mediumtext', + not_null: true, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'headers' => new Column( + name: 'headers', + type: 'text', + not_null: true, + ), + 'send_html' => new Column( + name: 'send_html', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'priority' => new Column( + name: 'priority', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'private' => new Column( + name: 'private', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_mail', + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + 'time_sent', + ], + ), + 'idx_mail_priority' => new DbIndex( + name: 'idx_mail_priority', + columns: [ + 'priority', + 'id_mail', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/MemberLogins.php b/Sources/Db/Schema/v2_1/MemberLogins.php new file mode 100644 index 00000000000..44ec76d2774 --- /dev/null +++ b/Sources/Db/Schema/v2_1/MemberLogins.php @@ -0,0 +1,100 @@ +name = 'member_logins'; + + $this->columns = [ + 'id_login' => new Column( + name: 'id_login', + type: 'int', + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'time' => new Column( + name: 'time', + type: 'int', + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'ip2' => new Column( + name: 'ip2', + type: 'inet', + size: 16, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_login', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_time' => new DbIndex( + name: 'idx_time', + columns: [ + 'time', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Membergroups.php b/Sources/Db/Schema/v2_1/Membergroups.php new file mode 100644 index 00000000000..e4986f6a152 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Membergroups.php @@ -0,0 +1,209 @@ + 1, + 'group_name' => '{$default_administrator_group}', + 'description' => '', + 'online_color' => '#FF0000', + 'min_posts' => -1, + 'icons' => '5#iconadmin.png', + 'group_type' => 1, + ], + [ + 'id_group' => 2, + 'group_name' => '{$default_global_moderator_group}', + 'description' => '', + 'online_color' => '#0000FF', + 'min_posts' => -1, + 'icons' => '5#icongmod.png', + 'group_type' => 0, + ], + [ + 'id_group' => 3, + 'group_name' => '{$default_moderator_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => -1, + 'icons' => '5#iconmod.png', + 'group_type' => 0, + ], + [ + 'id_group' => 4, + 'group_name' => '{$default_newbie_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 0, + 'icons' => '1#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 5, + 'group_name' => '{$default_junior_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 50, + 'icons' => '2#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 6, + 'group_name' => '{$default_full_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 100, + 'icons' => '3#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 7, + 'group_name' => '{$default_senior_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 250, + 'icons' => '4#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 8, + 'group_name' => '{$default_hero_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 500, + 'icons' => '5#icon.png', + 'group_type' => 0, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'membergroups'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'group_name' => new Column( + name: 'group_name', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'text', + not_null: true, + ), + 'online_color' => new Column( + name: 'online_color', + type: 'varchar', + size: 20, + not_null: true, + default: '', + ), + 'min_posts' => new Column( + name: 'min_posts', + type: 'mediumint', + not_null: true, + default: -1, + ), + 'max_messages' => new Column( + name: 'max_messages', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'icons' => new Column( + name: 'icons', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'group_type' => new Column( + name: 'group_type', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'hidden' => new Column( + name: 'hidden', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'id_parent' => new Column( + name: 'id_parent', + type: 'smallint', + not_null: true, + default: -2, + ), + 'tfa_required' => new Column( + name: 'tfa_required', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + ], + ), + 'idx_min_posts' => new DbIndex( + name: 'idx_min_posts', + columns: [ + 'min_posts', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Members.php b/Sources/Db/Schema/v2_1/Members.php new file mode 100644 index 00000000000..7fe48a16743 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Members.php @@ -0,0 +1,459 @@ +name = 'members'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'member_name' => new Column( + name: 'member_name', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'date_registered' => new Column( + name: 'date_registered', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'posts' => new Column( + name: 'posts', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'lngfile' => new Column( + name: 'lngfile', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'last_login' => new Column( + name: 'last_login', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'real_name' => new Column( + name: 'real_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'instant_messages' => new Column( + name: 'instant_messages', + type: 'smallint', + not_null: true, + ), + 'unread_messages' => new Column( + name: 'unread_messages', + type: 'smallint', + not_null: true, + ), + 'new_pm' => new Column( + name: 'new_pm', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'alerts' => new Column( + name: 'alerts', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'buddy_list' => new Column( + name: 'buddy_list', + type: 'text', + not_null: true, + ), + 'pm_ignore_list' => new Column( + name: 'pm_ignore_list', + type: 'text', + ), + 'pm_prefs' => new Column( + name: 'pm_prefs', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'mod_prefs' => new Column( + name: 'mod_prefs', + type: 'varchar', + size: 20, + not_null: true, + default: '', + ), + 'passwd' => new Column( + name: 'passwd', + type: 'varchar', + size: 64, + not_null: true, + default: '', + ), + 'email_address' => new Column( + name: 'email_address', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'personal_text' => new Column( + name: 'personal_text', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'birthdate' => new Column( + name: 'birthdate', + type: 'date', + not_null: true, + default: '1004-01-01', + ), + 'website_title' => new Column( + name: 'website_title', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'website_url' => new Column( + name: 'website_url', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'show_online' => new Column( + name: 'show_online', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'time_format' => new Column( + name: 'time_format', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'signature' => new Column( + name: 'signature', + type: 'text', + not_null: true, + ), + 'time_offset' => new Column( + name: 'time_offset', + type: 'float', + not_null: true, + default: 0, + ), + 'avatar' => new Column( + name: 'avatar', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'usertitle' => new Column( + name: 'usertitle', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'member_ip' => new Column( + name: 'member_ip', + type: 'inet', + size: 16, + ), + 'member_ip2' => new Column( + name: 'member_ip2', + type: 'inet', + size: 16, + ), + 'secret_question' => new Column( + name: 'secret_question', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'secret_answer' => new Column( + name: 'secret_answer', + type: 'varchar', + size: 64, + not_null: true, + default: '', + ), + 'id_theme' => new Column( + name: 'id_theme', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_activated' => new Column( + name: 'is_activated', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'validation_code' => new Column( + name: 'validation_code', + type: 'varchar', + size: 10, + not_null: true, + default: '', + ), + 'id_msg_last_visit' => new Column( + name: 'id_msg_last_visit', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'additional_groups' => new Column( + name: 'additional_groups', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'smiley_set' => new Column( + name: 'smiley_set', + type: 'varchar', + size: 48, + not_null: true, + default: '', + ), + 'id_post_group' => new Column( + name: 'id_post_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'total_time_logged_in' => new Column( + name: 'total_time_logged_in', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'password_salt' => new Column( + name: 'password_salt', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'ignore_boards' => new Column( + name: 'ignore_boards', + type: 'text', + not_null: true, + ), + 'warning' => new Column( + name: 'warning', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'passwd_flood' => new Column( + name: 'passwd_flood', + type: 'varchar', + size: 12, + not_null: true, + default: '', + ), + 'pm_receive_from' => new Column( + name: 'pm_receive_from', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'timezone' => new Column( + name: 'timezone', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'tfa_secret' => new Column( + name: 'tfa_secret', + type: 'varchar', + size: 24, + not_null: true, + default: '', + ), + 'tfa_backup' => new Column( + name: 'tfa_backup', + type: 'varchar', + size: 64, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + ], + ), + 'idx_member_name' => new DbIndex( + name: 'idx_member_name', + columns: [ + 'member_name', + ], + ), + 'idx_real_name' => new DbIndex( + name: 'idx_real_name', + columns: [ + 'real_name', + ], + ), + 'idx_email_address' => new DbIndex( + name: 'idx_email_address', + columns: [ + 'email_address', + ], + ), + 'idx_date_registered' => new DbIndex( + name: 'idx_date_registered', + columns: [ + 'date_registered', + ], + ), + 'idx_id_group' => new DbIndex( + name: 'idx_id_group', + columns: [ + 'id_group', + ], + ), + 'idx_birthdate' => new DbIndex( + name: 'idx_birthdate', + columns: [ + 'birthdate', + ], + ), + 'idx_posts' => new DbIndex( + name: 'idx_posts', + columns: [ + 'posts', + ], + ), + 'idx_last_login' => new DbIndex( + name: 'idx_last_login', + columns: [ + 'last_login', + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + 'lngfile(30)', + ], + ), + 'idx_id_post_group' => new DbIndex( + name: 'idx_id_post_group', + columns: [ + 'id_post_group', + ], + ), + 'idx_warning' => new DbIndex( + name: 'idx_warning', + columns: [ + 'warning', + ], + ), + 'idx_total_time_logged_in' => new DbIndex( + name: 'idx_total_time_logged_in', + columns: [ + 'total_time_logged_in', + ], + ), + 'idx_id_theme' => new DbIndex( + name: 'idx_id_theme', + columns: [ + 'id_theme', + ], + ), + 'idx_active_real_name' => new DbIndex( + name: 'idx_active_real_name', + columns: [ + 'is_activated', + 'real_name', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Mentions.php b/Sources/Db/Schema/v2_1/Mentions.php new file mode 100644 index 00000000000..75fc63c8399 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Mentions.php @@ -0,0 +1,102 @@ +name = 'mentions'; + + $this->columns = [ + 'content_id' => new Column( + name: 'content_id', + type: 'int', + default: 0, + ), + 'content_type' => new Column( + name: 'content_type', + type: 'varchar', + size: 10, + default: '', + ), + 'id_mentioned' => new Column( + name: 'id_mentioned', + type: 'int', + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + ), + 'time' => new Column( + name: 'time', + type: 'int', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'content_id', + 'content_type', + 'id_mentioned', + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + 'content_id', + 'content_type', + ], + ), + 'mentionee' => new DbIndex( + name: 'mentionee', + columns: [ + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/MessageIcons.php b/Sources/Db/Schema/v2_1/MessageIcons.php new file mode 100644 index 00000000000..6bf80dd1f33 --- /dev/null +++ b/Sources/Db/Schema/v2_1/MessageIcons.php @@ -0,0 +1,167 @@ + 'xx', + 'title' => 'Standard', + 'icon_order' => '0', + ], + [ + 'filename' => 'thumbup', + 'title' => 'Thumb Up', + 'icon_order' => '1', + ], + [ + 'filename' => 'thumbdown', + 'title' => 'Thumb Down', + 'icon_order' => '2', + ], + [ + 'filename' => 'exclamation', + 'title' => 'Exclamation point', + 'icon_order' => '3', + ], + [ + 'filename' => 'question', + 'title' => 'Question mark', + 'icon_order' => '4', + ], + [ + 'filename' => 'lamp', + 'title' => 'Lamp', + 'icon_order' => '5', + ], + [ + 'filename' => 'smiley', + 'title' => 'Smiley', + 'icon_order' => '6', + ], + [ + 'filename' => 'angry', + 'title' => 'Angry', + 'icon_order' => '7', + ], + [ + 'filename' => 'cheesy', + 'title' => 'Cheesy', + 'icon_order' => '8', + ], + [ + 'filename' => 'grin', + 'title' => 'Grin', + 'icon_order' => '9', + ], + [ + 'filename' => 'sad', + 'title' => 'Sad', + 'icon_order' => '10', + ], + [ + 'filename' => 'wink', + 'title' => 'Wink', + 'icon_order' => '11', + ], + [ + 'filename' => 'poll', + 'title' => 'Poll', + 'icon_order' => '12', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'message_icons'; + + $this->columns = [ + 'id_icon' => new Column( + name: 'id_icon', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'title' => new Column( + name: 'title', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'icon_order' => new Column( + name: 'icon_order', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_icon', + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + 'id_board', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Messages.php b/Sources/Db/Schema/v2_1/Messages.php new file mode 100644 index 00000000000..d0af51b2dee --- /dev/null +++ b/Sources/Db/Schema/v2_1/Messages.php @@ -0,0 +1,263 @@ + 1, + 'id_msg_modified' => 1, + 'id_topic' => 1, + 'id_board' => 1, + 'poster_time' => '{$current_time}', + 'subject' => '{$default_topic_subject}', + 'poster_name' => 'Simple Machines', + 'poster_email' => 'info@simplemachines.org', + 'modified_name' => '', + 'body' => '{$default_topic_message}', + 'icon' => 'xx', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'messages'; + + $this->columns = [ + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'poster_time' => new Column( + name: 'poster_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg_modified' => new Column( + name: 'id_msg_modified', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'poster_name' => new Column( + name: 'poster_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'poster_email' => new Column( + name: 'poster_email', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'poster_ip' => new Column( + name: 'poster_ip', + type: 'inet', + size: 16, + ), + 'smileys_enabled' => new Column( + name: 'smileys_enabled', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'modified_time' => new Column( + name: 'modified_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'modified_name' => new Column( + name: 'modified_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'modified_reason' => new Column( + name: 'modified_reason', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + 'icon' => new Column( + name: 'icon', + type: 'varchar', + size: 16, + not_null: true, + default: 'xx', + ), + 'approved' => new Column( + name: 'approved', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'likes' => new Column( + name: 'likes', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_msg', + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + type: 'unique', + columns: [ + 'id_board', + 'id_msg', + 'approved', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'id_msg', + ], + ), + 'idx_ip_index' => new DbIndex( + name: 'idx_ip_index', + columns: [ + 'poster_ip', + 'id_topic', + ], + ), + 'idx_participation' => new DbIndex( + name: 'idx_participation', + columns: [ + 'id_member', + 'id_topic', + ], + ), + 'idx_show_posts' => new DbIndex( + name: 'idx_show_posts', + columns: [ + 'id_member', + 'id_board', + ], + ), + 'idx_id_member_msg' => new DbIndex( + name: 'idx_id_member_msg', + columns: [ + 'id_member', + 'approved', + 'id_msg', + ], + ), + 'idx_current_topic' => new DbIndex( + name: 'idx_current_topic', + columns: [ + 'id_topic', + 'id_msg', + 'id_member', + 'approved', + ], + ), + 'idx_related_ip' => new DbIndex( + name: 'idx_related_ip', + columns: [ + 'id_member', + 'poster_ip', + 'id_msg', + ], + ), + 'idx_likes' => new DbIndex( + name: 'idx_likes', + columns: [ + 'likes', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/ModeratorGroups.php b/Sources/Db/Schema/v2_1/ModeratorGroups.php new file mode 100644 index 00000000000..1e5d52a75c2 --- /dev/null +++ b/Sources/Db/Schema/v2_1/ModeratorGroups.php @@ -0,0 +1,74 @@ +name = 'moderator_groups'; + + $this->columns = [ + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_board', + 'id_group', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Moderators.php b/Sources/Db/Schema/v2_1/Moderators.php new file mode 100644 index 00000000000..3105b22a11c --- /dev/null +++ b/Sources/Db/Schema/v2_1/Moderators.php @@ -0,0 +1,74 @@ +name = 'moderators'; + + $this->columns = [ + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_board', + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PackageServers.php b/Sources/Db/Schema/v2_1/PackageServers.php new file mode 100644 index 00000000000..b9765d88b49 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PackageServers.php @@ -0,0 +1,103 @@ + 'Simple Machines Third-party Mod Site', + 'url' => 'https://custom.simplemachines.org/packages/mods', + 'validation_url' => 'https://custom.simplemachines.org/api.php?action=validate;version=v1;smf_version={SMF_VERSION}', + ], + [ + 'name' => 'Simple Machines Downloads Site', + 'url' => 'https://download.simplemachines.org/browse.php?api=v1;smf_version={SMF_VERSION}', + 'validation_url' => 'https://download.simplemachines.org/validate.php?api=v1;smf_version={SMF_VERSION}', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'package_servers'; + + $this->columns = [ + 'id_server' => new Column( + name: 'id_server', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'url' => new Column( + name: 'url', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'validation_url' => new Column( + name: 'validation_url', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'extra' => new Column( + name: 'extra', + type: 'text', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_server', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PermissionProfiles.php b/Sources/Db/Schema/v2_1/PermissionProfiles.php new file mode 100644 index 00000000000..a2f48c9d0c0 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PermissionProfiles.php @@ -0,0 +1,90 @@ + 1, + 'profile_name' => 'default', + ], + [ + 'id_profile' => 2, + 'profile_name' => 'no_polls', + ], + [ + 'id_profile' => 3, + 'profile_name' => 'reply_only', + ], + [ + 'id_profile' => 4, + 'profile_name' => 'read_only', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'permission_profiles'; + + $this->columns = [ + 'id_profile' => new Column( + name: 'id_profile', + type: 'smallint', + auto: true, + ), + 'profile_name' => new Column( + name: 'profile_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_profile', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Permissions.php b/Sources/Db/Schema/v2_1/Permissions.php new file mode 100644 index 00000000000..b3ded0f5b31 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Permissions.php @@ -0,0 +1,284 @@ + -1, + 'permission' => 'search_posts', + ], + [ + 'id_group' => -1, + 'permission' => 'calendar_view', + ], + [ + 'id_group' => -1, + 'permission' => 'view_stats', + ], + [ + 'id_group' => 0, + 'permission' => 'view_mlist', + ], + [ + 'id_group' => 0, + 'permission' => 'search_posts', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_view', + ], + [ + 'id_group' => 0, + 'permission' => 'pm_read', + ], + [ + 'id_group' => 0, + 'permission' => 'pm_send', + ], + [ + 'id_group' => 0, + 'permission' => 'pm_draft', + ], + [ + 'id_group' => 0, + 'permission' => 'calendar_view', + ], + [ + 'id_group' => 0, + 'permission' => 'view_stats', + ], + [ + 'id_group' => 0, + 'permission' => 'who_view', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_identity_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_password_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_blurb_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_displayed_name_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_signature_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_website_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_forum_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_extra_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_remove_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_server_avatar', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_upload_avatar', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_remote_avatar', + ], + [ + 'id_group' => 0, + 'permission' => 'send_email_to_members', + ], + [ + 'id_group' => 2, + 'permission' => 'view_mlist', + ], + [ + 'id_group' => 2, + 'permission' => 'search_posts', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_view', + ], + [ + 'id_group' => 2, + 'permission' => 'pm_read', + ], + [ + 'id_group' => 2, + 'permission' => 'pm_send', + ], + [ + 'id_group' => 2, + 'permission' => 'pm_draft', + ], + [ + 'id_group' => 2, + 'permission' => 'calendar_view', + ], + [ + 'id_group' => 2, + 'permission' => 'view_stats', + ], + [ + 'id_group' => 2, + 'permission' => 'who_view', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_identity_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_password_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_blurb_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_displayed_name_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_signature_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_website_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_forum_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_extra_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_remove_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_server_avatar', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_upload_avatar', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_remote_avatar', + ], + [ + 'id_group' => 2, + 'permission' => 'send_email_to_members', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_title_own', + ], + [ + 'id_group' => 2, + 'permission' => 'calendar_post', + ], + [ + 'id_group' => 2, + 'permission' => 'calendar_edit_any', + ], + [ + 'id_group' => 2, + 'permission' => 'access_mod_center', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'permissions'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + default: 0, + ), + 'permission' => new Column( + name: 'permission', + type: 'varchar', + size: 30, + default: '', + ), + 'add_deny' => new Column( + name: 'add_deny', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'permission', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PersonalMessages.php b/Sources/Db/Schema/v2_1/PersonalMessages.php new file mode 100644 index 00000000000..fe5065b3142 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PersonalMessages.php @@ -0,0 +1,133 @@ +name = 'personal_messages'; + + $this->columns = [ + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_pm_head' => new Column( + name: 'id_pm_head', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_from' => new Column( + name: 'id_member_from', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'deleted_by_sender' => new Column( + name: 'deleted_by_sender', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'from_name' => new Column( + name: 'from_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'msgtime' => new Column( + name: 'msgtime', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_pm', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member_from', + 'deleted_by_sender', + ], + ), + 'idx_msgtime' => new DbIndex( + name: 'idx_msgtime', + columns: [ + 'msgtime', + ], + ), + 'idx_id_pm_head' => new DbIndex( + name: 'idx_id_pm_head', + columns: [ + 'id_pm_head', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PmLabeledMessages.php b/Sources/Db/Schema/v2_1/PmLabeledMessages.php new file mode 100644 index 00000000000..0487e905ab0 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmLabeledMessages.php @@ -0,0 +1,74 @@ +name = 'pm_labeled_messages'; + + $this->columns = [ + 'id_label' => new Column( + name: 'id_label', + type: 'int', + unsigned: true, + default: 0, + ), + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_label', + 'id_pm', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PmLabels.php b/Sources/Db/Schema/v2_1/PmLabels.php new file mode 100644 index 00000000000..82e3ff145e5 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmLabels.php @@ -0,0 +1,81 @@ +name = 'pm_labels'; + + $this->columns = [ + 'id_label' => new Column( + name: 'id_label', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_label', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PmRecipients.php b/Sources/Db/Schema/v2_1/PmRecipients.php new file mode 100644 index 00000000000..d37ca05eaa3 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmRecipients.php @@ -0,0 +1,117 @@ +name = 'pm_recipients'; + + $this->columns = [ + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'bcc' => new Column( + name: 'bcc', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_read' => new Column( + name: 'is_read', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_new' => new Column( + name: 'is_new', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'deleted' => new Column( + name: 'deleted', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'in_inbox' => new Column( + name: 'in_inbox', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_pm', + 'id_member', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'deleted', + 'id_pm', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PmRules.php b/Sources/Db/Schema/v2_1/PmRules.php new file mode 100644 index 00000000000..5465d75d695 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PmRules.php @@ -0,0 +1,116 @@ +name = 'pm_rules'; + + $this->columns = [ + 'id_rule' => new Column( + name: 'id_rule', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'rule_name' => new Column( + name: 'rule_name', + type: 'varchar', + size: 60, + not_null: true, + ), + 'criteria' => new Column( + name: 'criteria', + type: 'text', + not_null: true, + ), + 'actions' => new Column( + name: 'actions', + type: 'text', + not_null: true, + ), + 'delete_pm' => new Column( + name: 'delete_pm', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_or' => new Column( + name: 'is_or', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_rule', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_delete_pm' => new DbIndex( + name: 'idx_delete_pm', + columns: [ + 'delete_pm', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/PollChoices.php b/Sources/Db/Schema/v2_1/PollChoices.php new file mode 100644 index 00000000000..1aca5089ae7 --- /dev/null +++ b/Sources/Db/Schema/v2_1/PollChoices.php @@ -0,0 +1,88 @@ +name = 'poll_choices'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_choice' => new Column( + name: 'id_choice', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'label' => new Column( + name: 'label', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'votes' => new Column( + name: 'votes', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_poll', + 'id_choice', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Polls.php b/Sources/Db/Schema/v2_1/Polls.php new file mode 100644 index 00000000000..e5ac4cfbfb5 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Polls.php @@ -0,0 +1,143 @@ +name = 'polls'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'question' => new Column( + name: 'question', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'voting_locked' => new Column( + name: 'voting_locked', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'max_votes' => new Column( + name: 'max_votes', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'expire_time' => new Column( + name: 'expire_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'hide_results' => new Column( + name: 'hide_results', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'change_vote' => new Column( + name: 'change_vote', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'guest_vote' => new Column( + name: 'guest_vote', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_guest_voters' => new Column( + name: 'num_guest_voters', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'reset_poll' => new Column( + name: 'reset_poll', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'poster_name' => new Column( + name: 'poster_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_poll', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Qanda.php b/Sources/Db/Schema/v2_1/Qanda.php new file mode 100644 index 00000000000..98fbe36cdf7 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Qanda.php @@ -0,0 +1,92 @@ +name = 'qanda'; + + $this->columns = [ + 'id_question' => new Column( + name: 'id_question', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'lngfile' => new Column( + name: 'lngfile', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'question' => new Column( + name: 'question', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'answers' => new Column( + name: 'answers', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_question', + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + 'lngfile', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/ScheduledTasks.php b/Sources/Db/Schema/v2_1/ScheduledTasks.php new file mode 100644 index 00000000000..c992783044e --- /dev/null +++ b/Sources/Db/Schema/v2_1/ScheduledTasks.php @@ -0,0 +1,241 @@ + 3, + 'next_time' => 0, + 'time_offset' => 60, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'daily_maintenance', + 'callable' => '', + ], + [ + 'id_task' => 5, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'daily_digest', + 'callable' => '', + ], + [ + 'id_task' => 6, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'w', + 'disabled' => 0, + 'task' => 'weekly_digest', + 'callable' => '', + ], + [ + 'id_task' => 7, + 'next_time' => 0, + 'time_offset' => '{$sched_task_offset}', + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'fetchSMfiles', + 'callable' => '', + ], + [ + 'id_task' => 8, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 1, + 'task' => 'birthdayemails', + 'callable' => '', + ], + [ + 'id_task' => 9, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'w', + 'disabled' => 0, + 'task' => 'weekly_maintenance', + 'callable' => '', + ], + [ + 'id_task' => 10, + 'next_time' => 0, + 'time_offset' => 120, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 1, + 'task' => 'paid_subscriptions', + 'callable' => '', + ], + [ + 'id_task' => 11, + 'next_time' => 0, + 'time_offset' => 120, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'remove_temp_attachments', + 'callable' => '', + ], + [ + 'id_task' => 12, + 'next_time' => 0, + 'time_offset' => 180, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'remove_topic_redirect', + 'callable' => '', + ], + [ + 'id_task' => 13, + 'next_time' => 0, + 'time_offset' => 240, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'remove_old_drafts', + 'callable' => '', + ], + [ + 'id_task' => 14, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'w', + 'disabled' => 1, + 'task' => 'prune_log_topics', + 'callable' => '', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'scheduled_tasks'; + + $this->columns = [ + 'id_task' => new Column( + name: 'id_task', + type: 'smallint', + auto: true, + ), + 'next_time' => new Column( + name: 'next_time', + type: 'int', + not_null: true, + default: 0, + ), + 'time_offset' => new Column( + name: 'time_offset', + type: 'int', + not_null: true, + default: 0, + ), + 'time_regularity' => new Column( + name: 'time_regularity', + type: 'smallint', + not_null: true, + default: 0, + ), + 'time_unit' => new Column( + name: 'time_unit', + type: 'varchar', + size: 1, + not_null: true, + default: 'h', + ), + 'disabled' => new Column( + name: 'disabled', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'task' => new Column( + name: 'task', + type: 'varchar', + size: 24, + not_null: true, + default: '', + ), + 'callable' => new Column( + name: 'callable', + type: 'varchar', + size: 60, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_task', + ], + ), + 'idx_next_time' => new DbIndex( + name: 'idx_next_time', + columns: [ + 'next_time', + ], + ), + 'idx_disabled' => new DbIndex( + name: 'idx_disabled', + columns: [ + 'disabled', + ], + ), + 'idx_task' => new DbIndex( + name: 'idx_task', + type: 'unique', + columns: [ + 'task', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Sessions.php b/Sources/Db/Schema/v2_1/Sessions.php new file mode 100644 index 00000000000..aacb16ba20f --- /dev/null +++ b/Sources/Db/Schema/v2_1/Sessions.php @@ -0,0 +1,80 @@ +name = 'sessions'; + + $this->columns = [ + 'session_id' => new Column( + name: 'session_id', + type: 'varchar', + size: 128, + not_null: true, + default: '', + ), + 'last_update' => new Column( + name: 'last_update', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'data' => new Column( + name: 'data', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'session_id', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/SmileyFiles.php b/Sources/Db/Schema/v2_1/SmileyFiles.php new file mode 100644 index 00000000000..a700b29014d --- /dev/null +++ b/Sources/Db/Schema/v2_1/SmileyFiles.php @@ -0,0 +1,82 @@ +name = 'smiley_files'; + + $this->columns = [ + 'id_smiley' => new Column( + name: 'id_smiley', + type: 'smallint', + not_null: true, + default: 0, + ), + 'smiley_set' => new Column( + name: 'smiley_set', + type: 'varchar', + size: 48, + not_null: true, + default: '', + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 48, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_smiley', + 'smiley_set', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Smileys.php b/Sources/Db/Schema/v2_1/Smileys.php new file mode 100644 index 00000000000..173ffd40096 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Smileys.php @@ -0,0 +1,102 @@ +name = 'smileys'; + + $this->columns = [ + 'id_smiley' => new Column( + name: 'id_smiley', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'code' => new Column( + name: 'code', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'smiley_row' => new Column( + name: 'smiley_row', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'smiley_order' => new Column( + name: 'smiley_order', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'hidden' => new Column( + name: 'hidden', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_smiley', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Spiders.php b/Sources/Db/Schema/v2_1/Spiders.php new file mode 100644 index 00000000000..8f466c397ed --- /dev/null +++ b/Sources/Db/Schema/v2_1/Spiders.php @@ -0,0 +1,189 @@ + 'Google', + 'user_agent' => 'googlebot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yahoo!', + 'user_agent' => 'slurp', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing', + 'user_agent' => 'bingbot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (Mobile)', + 'user_agent' => 'Googlebot-Mobile', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (Image)', + 'user_agent' => 'Googlebot-Image', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (AdSense)', + 'user_agent' => 'Mediapartners-Google', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (Adwords)', + 'user_agent' => 'AdsBot-Google', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yahoo! (Mobile)', + 'user_agent' => 'YahooSeeker/M1A1-R2D2', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yahoo! (Image)', + 'user_agent' => 'Yahoo-MMCrawler', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (Preview)', + 'user_agent' => 'BingPreview', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (Ads)', + 'user_agent' => 'adidxbot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (MSNBot)', + 'user_agent' => 'msnbot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (Media)', + 'user_agent' => 'msnbot-media', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Cuil', + 'user_agent' => 'twiceler', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Ask', + 'user_agent' => 'Teoma', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Baidu', + 'user_agent' => 'Baiduspider', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Gigablast', + 'user_agent' => 'Gigabot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'InternetArchive', + 'user_agent' => 'ia_archiver-web.archive.org', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Alexa', + 'user_agent' => 'ia_archiver', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Omgili', + 'user_agent' => 'omgilibot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'EntireWeb', + 'user_agent' => 'Speedy Spider', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yandex', + 'user_agent' => 'yandex', + 'ip_info' => '', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'spiders'; + + $this->columns = [ + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'spider_name' => new Column( + name: 'spider_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'user_agent' => new Column( + name: 'user_agent', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'ip_info' => new Column( + name: 'ip_info', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Subscriptions.php b/Sources/Db/Schema/v2_1/Subscriptions.php new file mode 100644 index 00000000000..c0527fc5aa3 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Subscriptions.php @@ -0,0 +1,141 @@ +name = 'subscriptions'; + + $this->columns = [ + 'id_subscribe' => new Column( + name: 'id_subscribe', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 60, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'cost' => new Column( + name: 'cost', + type: 'text', + not_null: true, + ), + 'length' => new Column( + name: 'length', + type: 'varchar', + size: 6, + not_null: true, + default: '', + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + not_null: true, + default: 0, + ), + 'add_groups' => new Column( + name: 'add_groups', + type: 'varchar', + size: 40, + not_null: true, + default: '', + ), + 'active' => new Column( + name: 'active', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'repeatable' => new Column( + name: 'repeatable', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'allow_partial' => new Column( + name: 'allow_partial', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'reminder' => new Column( + name: 'reminder', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'email_complete' => new Column( + name: 'email_complete', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_subscribe', + ], + ), + 'idx_active' => new DbIndex( + name: 'idx_active', + columns: [ + 'active', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Themes.php b/Sources/Db/Schema/v2_1/Themes.php new file mode 100644 index 00000000000..a9410f4b3f6 --- /dev/null +++ b/Sources/Db/Schema/v2_1/Themes.php @@ -0,0 +1,147 @@ + 1, + 'variable' => 'name', + 'value' => '{$default_theme_name}', + ], + [ + 'id_theme' => 1, + 'variable' => 'theme_url', + 'value' => '{$boardurl}/Themes/default', + ], + [ + 'id_theme' => 1, + 'variable' => 'images_url', + 'value' => '{$boardurl}/Themes/default/images', + ], + [ + 'id_theme' => 1, + 'variable' => 'theme_dir', + 'value' => '{$boarddir}/Themes/default', + ], + [ + 'id_theme' => 1, + 'variable' => 'show_latest_member', + 'value' => '1', + ], + [ + 'id_theme' => 1, + 'variable' => 'show_newsfader', + 'value' => '0', + ], + [ + 'id_theme' => 1, + 'variable' => 'number_recent_posts', + 'value' => '0', + ], + [ + 'id_theme' => 1, + 'variable' => 'show_stats_index', + 'value' => '1', + ], + [ + 'id_theme' => 1, + 'variable' => 'newsfader_time', + 'value' => '3000', + ], + [ + 'id_theme' => 1, + 'variable' => 'use_image_buttons', + 'value' => '1', + ], + [ + 'id_theme' => 1, + 'variable' => 'enable_news', + 'value' => '1', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'themes'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + default: 0, + ), + 'id_theme' => new Column( + name: 'id_theme', + type: 'tinyint', + unsigned: true, + default: 1, + ), + 'variable' => new Column( + name: 'variable', + type: 'varchar', + size: 255, + default: '', + ), + 'value' => new Column( + name: 'value', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_theme', + 'id_member', + 'variable(30)', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/Topics.php b/Sources/Db/Schema/v2_1/Topics.php new file mode 100644 index 00000000000..e63ade2240e --- /dev/null +++ b/Sources/Db/Schema/v2_1/Topics.php @@ -0,0 +1,240 @@ + 1, + 'id_board' => 1, + 'id_first_msg' => 1, + 'id_last_msg' => 1, + 'id_member_started' => 0, + 'id_member_updated' => 0, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'topics'; + + $this->columns = [ + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'is_sticky' => new Column( + name: 'is_sticky', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_first_msg' => new Column( + name: 'id_first_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_last_msg' => new Column( + name: 'id_last_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_started' => new Column( + name: 'id_member_started', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_updated' => new Column( + name: 'id_member_updated', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_previous_board' => new Column( + name: 'id_previous_board', + type: 'smallint', + not_null: true, + default: 0, + ), + 'id_previous_topic' => new Column( + name: 'id_previous_topic', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'num_replies' => new Column( + name: 'num_replies', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_views' => new Column( + name: 'num_views', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'locked' => new Column( + name: 'locked', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'redirect_expires' => new Column( + name: 'redirect_expires', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_redirect_topic' => new Column( + name: 'id_redirect_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'unapproved_posts' => new Column( + name: 'unapproved_posts', + type: 'smallint', + not_null: true, + default: 0, + ), + 'approved' => new Column( + name: 'approved', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_topic', + ], + ), + 'idx_last_message' => new DbIndex( + name: 'idx_last_message', + type: 'unique', + columns: [ + 'id_last_msg', + 'id_board', + ], + ), + 'idx_first_message' => new DbIndex( + name: 'idx_first_message', + type: 'unique', + columns: [ + 'id_first_msg', + 'id_board', + ], + ), + 'idx_poll' => new DbIndex( + name: 'idx_poll', + type: 'unique', + columns: [ + 'id_poll', + 'id_topic', + ], + ), + 'idx_is_sticky' => new DbIndex( + name: 'idx_is_sticky', + columns: [ + 'is_sticky', + ], + ), + 'idx_approved' => new DbIndex( + name: 'idx_approved', + columns: [ + 'approved', + ], + ), + 'idx_member_started' => new DbIndex( + name: 'idx_member_started', + columns: [ + 'id_member_started', + 'id_board', + ], + ), + 'idx_last_message_sticky' => new DbIndex( + name: 'idx_last_message_sticky', + columns: [ + 'id_board', + 'is_sticky', + 'id_last_msg', + ], + ), + 'idx_board_news' => new DbIndex( + name: 'idx_board_news', + columns: [ + 'id_board', + 'id_first_msg', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/UserAlerts.php b/Sources/Db/Schema/v2_1/UserAlerts.php new file mode 100644 index 00000000000..2e863b44052 --- /dev/null +++ b/Sources/Db/Schema/v2_1/UserAlerts.php @@ -0,0 +1,140 @@ +name = 'user_alerts'; + + $this->columns = [ + 'id_alert' => new Column( + name: 'id_alert', + type: 'int', + unsigned: true, + auto: true, + ), + 'alert_time' => new Column( + name: 'alert_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_started' => new Column( + name: 'id_member_started', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_name' => new Column( + name: 'member_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'content_type' => new Column( + name: 'content_type', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'content_id' => new Column( + name: 'content_id', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'content_action' => new Column( + name: 'content_action', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'is_read' => new Column( + name: 'is_read', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'extra' => new Column( + name: 'extra', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_alert', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_alert_time' => new DbIndex( + name: 'idx_alert_time', + columns: [ + 'alert_time', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/UserAlertsPrefs.php b/Sources/Db/Schema/v2_1/UserAlertsPrefs.php new file mode 100644 index 00000000000..ed2ee31a595 --- /dev/null +++ b/Sources/Db/Schema/v2_1/UserAlertsPrefs.php @@ -0,0 +1,226 @@ + 0, + 'alert_pref' => 'alert_timeout', + 'alert_value' => 10, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'announcements', + 'alert_value' => 0, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'birthday', + 'alert_value' => 2, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'board_notify', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'buddy_request', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'groupr_approved', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'groupr_rejected', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_group_request', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_register', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_report', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_report_reply', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_auto_notify', + 'alert_value' => 0, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_like', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_mention', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_notify_pref', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_notify_type', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_quote', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_receive_body', + 'alert_value' => 0, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_report', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_report_reply', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'pm_new', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'pm_notify', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'pm_reply', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'request_group', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'topic_notify', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'unapproved_attachment', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'unapproved_reply', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'unapproved_post', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'warn_any', + 'alert_value' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'user_alerts_prefs'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'alert_pref' => new Column( + name: 'alert_pref', + type: 'varchar', + size: 32, + default: '', + ), + 'alert_value' => new Column( + name: 'alert_value', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'alert_pref', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/UserDrafts.php b/Sources/Db/Schema/v2_1/UserDrafts.php new file mode 100644 index 00000000000..9054fbf9df0 --- /dev/null +++ b/Sources/Db/Schema/v2_1/UserDrafts.php @@ -0,0 +1,161 @@ +name = 'user_drafts'; + + $this->columns = [ + 'id_draft' => new Column( + name: 'id_draft', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_reply' => new Column( + name: 'id_reply', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'type' => new Column( + name: 'type', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'poster_time' => new Column( + name: 'poster_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'smileys_enabled' => new Column( + name: 'smileys_enabled', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'body' => new Column( + name: 'body', + type: 'mediumtext', + not_null: true, + ), + 'icon' => new Column( + name: 'icon', + type: 'varchar', + size: 16, + not_null: true, + default: 'xx', + ), + 'locked' => new Column( + name: 'locked', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'is_sticky' => new Column( + name: 'is_sticky', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'to_list' => new Column( + name: 'to_list', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_draft', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'id_draft', + 'type', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/UserLikes.php b/Sources/Db/Schema/v2_1/UserLikes.php new file mode 100644 index 00000000000..40a2aacc428 --- /dev/null +++ b/Sources/Db/Schema/v2_1/UserLikes.php @@ -0,0 +1,101 @@ +name = 'user_likes'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'content_type' => new Column( + name: 'content_type', + type: 'char', + size: 6, + default: '', + ), + 'content_id' => new Column( + name: 'content_id', + type: 'int', + unsigned: true, + default: 0, + ), + 'like_time' => new Column( + name: 'like_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'content_id', + 'content_type', + 'id_member', + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + 'content_id', + 'content_type', + ], + ), + 'liker' => new DbIndex( + name: 'liker', + columns: [ + 'id_member', + ], + ), + ]; + } +} diff --git a/Sources/Db/Schema/v2_1/index.php b/Sources/Db/Schema/v2_1/index.php new file mode 100644 index 00000000000..cc9dd085708 --- /dev/null +++ b/Sources/Db/Schema/v2_1/index.php @@ -0,0 +1,8 @@ + 1, + 'filename' => 'current-version.js', + 'path' => '/smf/', + 'parameters' => 'version=%3$s', + 'data' => '', + 'filetype' => 'text/javascript', + ], + [ + 'id_file' => 2, + 'filename' => 'detailed-version.js', + 'path' => '/smf/', + 'parameters' => 'language=%1$s&version=%3$s', + 'data' => '', + 'filetype' => 'text/javascript', + ], + [ + 'id_file' => 3, + 'filename' => 'latest-news.js', + 'path' => '/smf/', + 'parameters' => 'language=%1$s&format=%2$s', + 'data' => '', + 'filetype' => 'text/javascript', + ], + [ + 'id_file' => 4, + 'filename' => 'latest-versions.txt', + 'path' => '/smf/', + 'parameters' => 'version=%3$s', + 'data' => '', + 'filetype' => 'text/plain', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'admin_info_files'; + + $this->columns = [ + 'id_file' => new Column( + name: 'id_file', + type: 'tinyint', + unsigned: true, + auto: true, + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'path' => new Column( + name: 'path', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'parameters' => new Column( + name: 'parameters', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'data' => new Column( + name: 'data', + type: 'text', + not_null: true, + ), + 'filetype' => new Column( + name: 'filetype', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_file', + ], + ), + 'idx_filename' => new DbIndex( + name: 'idx_filename', + columns: [ + 'filename(30)', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/ApprovalQueue.php b/Sources/Db/Schema/v3_0/ApprovalQueue.php new file mode 100644 index 00000000000..370c1d0a9c5 --- /dev/null +++ b/Sources/Db/Schema/v3_0/ApprovalQueue.php @@ -0,0 +1,74 @@ +name = 'approval_queue'; + + $this->columns = [ + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_attach' => new Column( + name: 'id_attach', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_event' => new Column( + name: 'id_event', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Attachments.php b/Sources/Db/Schema/v3_0/Attachments.php new file mode 100644 index 00000000000..d63ba2a709a --- /dev/null +++ b/Sources/Db/Schema/v3_0/Attachments.php @@ -0,0 +1,191 @@ +name = 'attachments'; + + $this->columns = [ + 'id_attach' => new Column( + name: 'id_attach', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_thumb' => new Column( + name: 'id_thumb', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_folder' => new Column( + name: 'id_folder', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'attachment_type' => new Column( + name: 'attachment_type', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'file_hash' => new Column( + name: 'file_hash', + type: 'varchar', + size: 40, + not_null: true, + default: '', + ), + 'fileext' => new Column( + name: 'fileext', + type: 'varchar', + size: 8, + not_null: true, + default: '', + ), + 'size' => new Column( + name: 'size', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'downloads' => new Column( + name: 'downloads', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'width' => new Column( + name: 'width', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'height' => new Column( + name: 'height', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'mime_type' => new Column( + name: 'mime_type', + type: 'varchar', + size: 128, + not_null: true, + default: '', + ), + 'approved' => new Column( + name: 'approved', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_attach', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'id_attach', + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + 'id_msg', + ], + ), + 'idx_attachment_type' => new DbIndex( + name: 'idx_attachment_type', + columns: [ + 'attachment_type', + ], + ), + 'idx_id_thumb' => new DbIndex( + name: 'idx_id_thumb', + columns: [ + 'id_thumb', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/BackgroundTasks.php b/Sources/Db/Schema/v3_0/BackgroundTasks.php new file mode 100644 index 00000000000..d9fb9a2c24c --- /dev/null +++ b/Sources/Db/Schema/v3_0/BackgroundTasks.php @@ -0,0 +1,95 @@ +name = 'background_tasks'; + + $this->columns = [ + 'id_task' => new Column( + name: 'id_task', + type: 'int', + unsigned: true, + auto: true, + ), + 'task_file' => new Column( + name: 'task_file', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'task_class' => new Column( + name: 'task_class', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'task_data' => new Column( + name: 'task_data', + type: 'mediumtext', + not_null: true, + ), + 'claimed_time' => new Column( + name: 'claimed_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_task', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/BanGroups.php b/Sources/Db/Schema/v3_0/BanGroups.php new file mode 100644 index 00000000000..38df3fb06bd --- /dev/null +++ b/Sources/Db/Schema/v3_0/BanGroups.php @@ -0,0 +1,128 @@ +name = 'ban_groups'; + + $this->columns = [ + 'id_ban_group' => new Column( + name: 'id_ban_group', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 20, + not_null: true, + default: '', + ), + 'ban_time' => new Column( + name: 'ban_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'expire_time' => new Column( + name: 'expire_time', + type: 'int', + unsigned: true, + ), + 'cannot_access' => new Column( + name: 'cannot_access', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'cannot_register' => new Column( + name: 'cannot_register', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'cannot_post' => new Column( + name: 'cannot_post', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'cannot_login' => new Column( + name: 'cannot_login', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'reason' => new Column( + name: 'reason', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'notes' => new Column( + name: 'notes', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_ban_group', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/BanItems.php b/Sources/Db/Schema/v3_0/BanItems.php new file mode 100644 index 00000000000..61f6fb7763b --- /dev/null +++ b/Sources/Db/Schema/v3_0/BanItems.php @@ -0,0 +1,127 @@ +name = 'ban_items'; + + $this->columns = [ + 'id_ban' => new Column( + name: 'id_ban', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_ban_group' => new Column( + name: 'id_ban_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip_low' => new Column( + name: 'ip_low', + type: 'inet', + size: 16, + ), + 'ip_high' => new Column( + name: 'ip_high', + type: 'inet', + size: 16, + ), + 'hostname' => new Column( + name: 'hostname', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'email_address' => new Column( + name: 'email_address', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'hits' => new Column( + name: 'hits', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_ban', + ], + ), + 'idx_id_ban_group' => new DbIndex( + name: 'idx_id_ban_group', + columns: [ + 'id_ban_group', + ], + ), + 'idx_id_ban_ip' => new DbIndex( + name: 'idx_id_ban_ip', + columns: [ + 'ip_low', + 'ip_high', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/BoardPermissions.php b/Sources/Db/Schema/v3_0/BoardPermissions.php new file mode 100644 index 00000000000..0f1751ccc7b --- /dev/null +++ b/Sources/Db/Schema/v3_0/BoardPermissions.php @@ -0,0 +1,1629 @@ + -1, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'remove_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_add_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_edit_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_new', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 1, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 1, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 1, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => -1, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'remove_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_new', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 2, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 2, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 2, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => -1, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'remove_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 3, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 3, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 3, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => -1, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'report_any', + ], + [ + 'id_group' => 0, + 'id_profile' => 4, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_new', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'report_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'move_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'split_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 2, + 'id_profile' => 4, + 'permission' => 'view_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'moderate_board', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_new', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_draft', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_reply_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_reply_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_topics', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_replies_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_unapproved_attachments', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_post', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_add_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_view', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_vote', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'poll_edit_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'report_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'lock_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'delete_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'modify_own', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'make_sticky', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'lock_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'remove_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'move_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'merge_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'split_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'delete_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'modify_any', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'approve_posts', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'post_attachment', + ], + [ + 'id_group' => 3, + 'id_profile' => 4, + 'permission' => 'view_attachments', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'board_permissions'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + default: 0, + ), + 'id_profile' => new Column( + name: 'id_profile', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'permission' => new Column( + name: 'permission', + type: 'varchar', + size: 30, + default: '', + ), + 'add_deny' => new Column( + name: 'add_deny', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'id_profile', + 'permission', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/BoardPermissionsView.php b/Sources/Db/Schema/v3_0/BoardPermissionsView.php new file mode 100644 index 00000000000..f493e78cdf0 --- /dev/null +++ b/Sources/Db/Schema/v3_0/BoardPermissionsView.php @@ -0,0 +1,98 @@ + -1, + 'id_board' => 1, + 'deny' => 0, + ], + [ + 'id_group' => 0, + 'id_board' => 1, + 'deny' => 0, + ], + [ + 'id_group' => 2, + 'id_board' => 1, + 'deny' => 0, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'board_permissions_view'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + ), + 'deny' => new Column( + name: 'deny', + type: 'smallint', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'id_board', + 'deny', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Boards.php b/Sources/Db/Schema/v3_0/Boards.php new file mode 100644 index 00000000000..cda3e454e02 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Boards.php @@ -0,0 +1,235 @@ + 1, + 'id_cat' => 1, + 'board_order' => 1, + 'id_last_msg' => 1, + 'id_msg_updated' => 1, + 'name' => '{$default_board_name}', + 'description' => '{$default_board_description}', + 'num_topics' => 1, + 'num_posts' => 1, + 'member_groups' => '-1,0,2', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'boards'; + + $this->columns = [ + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'id_cat' => new Column( + name: 'id_cat', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'child_level' => new Column( + name: 'child_level', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_parent' => new Column( + name: 'id_parent', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'board_order' => new Column( + name: 'board_order', + type: 'smallint', + not_null: true, + default: 0, + ), + 'id_last_msg' => new Column( + name: 'id_last_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg_updated' => new Column( + name: 'id_msg_updated', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_groups' => new Column( + name: 'member_groups', + type: 'varchar', + size: 255, + not_null: true, + default: '-1,0', + ), + 'id_profile' => new Column( + name: 'id_profile', + type: 'smallint', + unsigned: true, + not_null: true, + default: 1, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'text', + not_null: true, + ), + 'num_topics' => new Column( + name: 'num_topics', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_posts' => new Column( + name: 'num_posts', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'count_posts' => new Column( + name: 'count_posts', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'id_theme' => new Column( + name: 'id_theme', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'override_theme' => new Column( + name: 'override_theme', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'unapproved_posts' => new Column( + name: 'unapproved_posts', + type: 'smallint', + not_null: true, + default: 0, + ), + 'unapproved_topics' => new Column( + name: 'unapproved_topics', + type: 'smallint', + not_null: true, + default: 0, + ), + 'redirect' => new Column( + name: 'redirect', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'deny_member_groups' => new Column( + name: 'deny_member_groups', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_board', + ], + ), + 'idx_categories' => new DbIndex( + name: 'idx_categories', + type: 'unique', + columns: [ + 'id_cat', + 'id_board', + ], + ), + 'idx_id_parent' => new DbIndex( + name: 'idx_id_parent', + columns: [ + 'id_parent', + ], + ), + 'idx_id_msg_updated' => new DbIndex( + name: 'idx_id_msg_updated', + columns: [ + 'id_msg_updated', + ], + ), + 'idx_member_groups' => new DbIndex( + name: 'idx_member_groups', + columns: [ + 'member_groups(48)', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Calendar.php b/Sources/Db/Schema/v3_0/Calendar.php new file mode 100644 index 00000000000..7a3b4dfecc0 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Calendar.php @@ -0,0 +1,586 @@ + 'April Fools\' Day', + 'start_date' => '2000-04-01', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Christmas', + 'start_date' => '2000-12-25', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Cinco de Mayo', + 'start_date' => '2000-05-05', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'Mexico, USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => 'FREQ=YEARLY', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'D-Day', + 'start_date' => '2000-06-06', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Easter', + 'start_date' => '2000-04-23', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'EASTER_W', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Earth Day', + 'start_date' => '2000-04-22', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Father\'s Day', + 'start_date' => '2000-06-19', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'Canada, USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY;BYMONTH=6;BYDAY=3SU', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Flag Day', + 'start_date' => '2000-06-14', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Good Friday', + 'start_date' => '2000-04-21', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'EASTER_W-P2D', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Groundhog Day', + 'start_date' => '2000-02-02', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'Canada, USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => 'FREQ=YEARLY', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Halloween', + 'start_date' => '2000-10-31', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Independence Day', + 'start_date' => '2000-07-04', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Labor Day', + 'start_date' => '2000-09-03', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Labour Day', + 'start_date' => '2000-09-03', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'Canada', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Memorial Day', + 'start_date' => '2000-05-31', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY;BYMONTH=5;BYDAY=-1MO', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Mother\'s Day', + 'start_date' => '2000-05-08', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'Canada, USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY;BYMONTH=5;BYDAY=2SU', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'New Year\'s Day', + 'start_date' => '2000-01-01', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Remembrance Day', + 'start_date' => '2000-11-11', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'St. Patrick\'s Day', + 'start_date' => '2000-03-17', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Thanksgiving', + 'start_date' => '2000-11-26', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY;BYMONTH=11;BYDAY=4TH', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'United Nations Day', + 'start_date' => '2000-10-24', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Valentine\'s Day', + 'start_date' => '2000-02-14', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => '', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Veterans Day', + 'start_date' => '2000-11-11', + 'end_date' => '9999-12-31', + 'start_time' => null, + 'timezone' => null, + 'location' => 'USA', + 'duration' => 'P1D', + 'rrule' => 'FREQ=YEARLY', + 'rdates' => '', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Vernal Equinox', + 'start_date' => '2000-03-20', + 'end_date' => '9999-12-31', + 'start_time' => '07:30:00', + 'timezone' => 'UTC', + 'location' => '', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => '20000320T073000Z,20010320T131900Z,20020320T190800Z,20030321T005800Z,20040320T064700Z,20050320T123600Z,20060320T182500Z,20070321T001400Z,20080320T060400Z,20090320T115300Z,20100320T174200Z,20110320T233100Z,20120320T052000Z,20130320T111000Z,20140320T165900Z,20150320T224800Z,20160320T043700Z,20170320T102600Z,20180320T161600Z,20190320T220500Z,20200320T035400Z,20210320T094300Z,20220320T153200Z,20230320T212200Z,20240320T031100Z,20250320T090000Z,20260320T144900Z,20270320T203800Z,20280320T022800Z,20290320T081700Z,20300320T140600Z,20310320T195500Z,20320320T014400Z,20330320T073400Z,20340320T132300Z,20350320T191200Z,20360320T010100Z,20370320T065000Z,20380320T124000Z,20390320T182900Z,20400320T001800Z,20410320T060700Z,20420320T115600Z,20430320T174600Z,20440319T233500Z,20450320T052400Z,20460320T111300Z,20470320T170200Z,20480319T225200Z,20490320T044100Z,20500320T103000Z,20510320T161900Z,20520319T220800Z,20530320T035800Z,20540320T094700Z,20550320T153600Z,20560319T212500Z,20570320T031400Z,20580320T090400Z,20590320T145300Z,20600319T204200Z,20610320T023100Z,20620320T082000Z,20630320T141000Z,20640319T195900Z,20650320T014800Z,20660320T073700Z,20670320T132600Z,20680319T191600Z,20690320T010500Z,20700320T065400Z,20710320T124300Z,20720319T183200Z,20730320T002200Z,20740320T061100Z,20750320T120000Z,20760319T174900Z,20770319T233800Z,20780320T052800Z,20790320T111700Z,20800319T170600Z,20810319T225500Z,20820320T044400Z,20830320T103400Z,20840319T162300Z,20850319T221200Z,20860320T040100Z,20870320T095000Z,20880319T154000Z,20890319T212900Z,20900320T031800Z,20910320T090700Z,20920319T145600Z,20930319T204600Z,20940320T023500Z,20950320T082400Z,20960319T141300Z,20970319T200200Z,20980320T015200Z,20990320T074100Z', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Summer Solstice', + 'start_date' => '2000-06-21', + 'end_date' => '9999-12-31', + 'start_time' => '01:44:00', + 'timezone' => 'UTC', + 'location' => '', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => '20000621T014400Z,20010621T073200Z,20020621T132000Z,20030621T190800Z,20040621T005600Z,20050621T064400Z,20060621T123200Z,20070621T182100Z,20080621T000900Z,20090621T055700Z,20100621T114500Z,20110621T173300Z,20120620T232100Z,20130621T050900Z,20140621T105700Z,20150621T164600Z,20160620T223400Z,20170621T042200Z,20180621T101000Z,20190621T155800Z,20200620T214600Z,20210621T033400Z,20220621T092300Z,20230621T151100Z,20240620T205900Z,20250621T024700Z,20260621T083500Z,20270621T142300Z,20280620T201100Z,20290621T015900Z,20300621T074800Z,20310621T133600Z,20320620T192400Z,20330621T011200Z,20340621T070000Z,20350621T124800Z,20360620T183600Z,20370621T002400Z,20380621T061300Z,20390621T120100Z,20400620T174900Z,20410620T233700Z,20420621T052500Z,20430621T111300Z,20440620T170100Z,20450620T224900Z,20460621T043700Z,20470621T102600Z,20480620T161400Z,20490620T220200Z,20500621T035000Z,20510621T093800Z,20520620T152600Z,20530620T211400Z,20540621T030200Z,20550621T085100Z,20560620T143900Z,20570620T202700Z,20580621T021500Z,20590621T080300Z,20600620T135100Z,20610620T193900Z,20620621T012700Z,20630621T071600Z,20640620T130400Z,20650620T185200Z,20660621T004000Z,20670621T062800Z,20680620T121600Z,20690620T180400Z,20700620T235200Z,20710621T054100Z,20720620T112900Z,20730620T171700Z,20740620T230500Z,20750621T045300Z,20760620T104100Z,20770620T162900Z,20780620T221700Z,20790621T040500Z,20800620T095400Z,20810620T154200Z,20820620T213000Z,20830621T031800Z,20840620T090600Z,20850620T145400Z,20860620T204200Z,20870621T023000Z,20880620T081900Z,20890620T140700Z,20900620T195500Z,20910621T014300Z,20920620T073100Z,20930620T131900Z,20940620T190700Z,20950621T005500Z,20960620T064300Z,20970620T123200Z,20980620T182000Z,20990621T000800Z', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Autumnal Equinox', + 'start_date' => '2000-09-22', + 'end_date' => '9999-12-31', + 'start_time' => '17:16:00', + 'timezone' => 'UTC', + 'location' => '', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => '20000922T171600Z,20010922T230500Z,20020923T045400Z,20030923T104200Z,20040922T163100Z,20050922T222000Z,20060923T040800Z,20070923T095700Z,20080922T154600Z,20090922T213400Z,20100923T032300Z,20110923T091200Z,20120922T150100Z,20130922T204900Z,20140923T023800Z,20150923T082700Z,20160922T141500Z,20170922T200400Z,20180923T015300Z,20190923T074100Z,20200922T133000Z,20210922T191900Z,20220923T010700Z,20230923T065600Z,20240922T124500Z,20250922T183300Z,20260923T002200Z,20270923T061100Z,20280922T115900Z,20290922T174800Z,20300922T233700Z,20310923T052600Z,20320922T111400Z,20330922T170300Z,20340922T225200Z,20350923T044000Z,20360922T102900Z,20370922T161800Z,20380922T220600Z,20390923T035500Z,20400922T094400Z,20410922T153200Z,20420922T212100Z,20430923T031000Z,20440922T085800Z,20450922T144700Z,20460922T203600Z,20470923T022400Z,20480922T081300Z,20490922T140200Z,20500922T195000Z,20510923T013900Z,20520922T072800Z,20530922T131600Z,20540922T190500Z,20550923T005400Z,20560922T064200Z,20570922T123100Z,20580922T182000Z,20590923T000800Z,20600922T055700Z,20610922T114600Z,20620922T173400Z,20630922T232300Z,20640922T051200Z,20650922T110000Z,20660922T164900Z,20670922T223800Z,20680922T042600Z,20690922T101500Z,20700922T160400Z,20710922T215200Z,20720922T034100Z,20730922T093000Z,20740922T151800Z,20750922T210700Z,20760922T025600Z,20770922T084400Z,20780922T143300Z,20790922T202200Z,20800922T021000Z,20810922T075900Z,20820922T134800Z,20830922T193600Z,20840922T012500Z,20850922T071400Z,20860922T130200Z,20870922T185100Z,20880922T003900Z,20890922T062800Z,20900922T121700Z,20910922T180500Z,20920921T235400Z,20930922T054300Z,20940922T113100Z,20950922T172000Z,20960921T230900Z,20970922T045700Z,20980922T104600Z,20990922T163500Z', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + [ + 'title' => 'Winter Solstice', + 'start_date' => '2000-12-21', + 'end_date' => '9999-12-31', + 'start_time' => '13:27:00', + 'timezone' => 'UTC', + 'location' => '', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => '20001221T132700Z,20011221T191600Z,20021222T010600Z,20031222T065600Z,20041221T124600Z,20051221T183500Z,20061222T002500Z,20071222T061500Z,20081221T120400Z,20091221T175400Z,20101221T234400Z,20111222T053400Z,20121221T112300Z,20131221T171300Z,20141221T230300Z,20151222T045300Z,20161221T104200Z,20171221T163200Z,20181221T222200Z,20191222T041100Z,20201221T100100Z,20211221T155100Z,20221221T214100Z,20231222T033000Z,20241221T092000Z,20251221T151000Z,20261221T205900Z,20271222T024900Z,20281221T083900Z,20291221T142900Z,20301221T201800Z,20311222T020800Z,20321221T075800Z,20331221T134800Z,20341221T193700Z,20351222T012700Z,20361221T071700Z,20371221T130600Z,20381221T185600Z,20391222T004600Z,20401221T063600Z,20411221T122500Z,20421221T181500Z,20431222T000500Z,20441221T055400Z,20451221T114400Z,20461221T173400Z,20471221T232400Z,20481221T051300Z,20491221T110300Z,20501221T165300Z,20511221T224200Z,20521221T043200Z,20531221T102200Z,20541221T161200Z,20551221T220100Z,20561221T035100Z,20571221T094100Z,20581221T153000Z,20591221T212000Z,20601221T031000Z,20611221T090000Z,20621221T144900Z,20631221T203900Z,20641221T022900Z,20651221T081800Z,20661221T140800Z,20671221T195800Z,20681221T014700Z,20691221T073700Z,20701221T132700Z,20711221T191700Z,20721221T010600Z,20731221T065600Z,20741221T124600Z,20751221T183500Z,20761221T002500Z,20771221T061500Z,20781221T120500Z,20791221T175400Z,20801220T234400Z,20811221T053400Z,20821221T112300Z,20831221T171300Z,20841220T230300Z,20851221T045200Z,20861221T104200Z,20871221T163200Z,20881220T222200Z,20891221T041100Z,20901221T100100Z,20911221T155100Z,20921220T214000Z,20931221T033000Z,20941221T092000Z,20951221T150900Z,20961220T205900Z,20971221T024900Z,20981221T083900Z,20991221T142800Z', + 'exdates' => '', + 'type' => 1, + 'enabled' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'calendar'; + + $this->columns = [ + 'id_event' => new Column( + name: 'id_event', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'title' => new Column( + name: 'title', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'start_date' => new Column( + name: 'start_date', + type: 'date', + not_null: true, + default: '1004-01-01', + ), + 'end_date' => new Column( + name: 'end_date', + type: 'date', + not_null: true, + default: '1004-01-01', + ), + 'start_time' => new Column( + name: 'start_time', + type: 'time', + ), + 'end_time' => new Column( + name: 'end_time', + type: 'time', + ), + 'timezone' => new Column( + name: 'timezone', + type: 'varchar', + size: 80, + ), + 'location' => new Column( + name: 'location', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'duration' => new Column( + name: 'duration', + type: 'varchar', + size: 32, + not_null: true, + default: '', + ), + 'rrule' => new Column( + name: 'rrule', + type: 'varchar', + size: 1024, + not_null: true, + default: 'FREQ=YEARLY;COUNT=1', + ), + 'rdates' => new Column( + name: 'rdates', + type: 'text', + not_null: true, + default: null, + ), + 'exdates' => new Column( + name: 'exdates', + type: 'text', + not_null: true, + default: null, + ), + 'adjustments' => new Column( + name: 'adjustments', + type: 'json', + not_null: true, + ), + 'sequence' => new Column( + name: 'sequence', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'uid' => new Column( + name: 'uid', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'type' => new Column( + name: 'type', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'enabled' => new Column( + name: 'enabled', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_event', + ], + ), + 'idx_start_date' => new DbIndex( + name: 'idx_start_date', + columns: [ + 'start_date', + ], + ), + 'idx_end_date' => new DbIndex( + name: 'idx_end_date', + columns: [ + 'end_date', + ], + ), + 'idx_topic' => new DbIndex( + name: 'idx_topic', + columns: [ + 'id_topic', + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Categories.php b/Sources/Db/Schema/v3_0/Categories.php new file mode 100644 index 00000000000..75e855afc25 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Categories.php @@ -0,0 +1,101 @@ + 1, + 'cat_order' => 0, + 'name' => '{$default_category_name}', + 'description' => '', + 'can_collapse' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'categories'; + + $this->columns = [ + 'id_cat' => new Column( + name: 'id_cat', + type: 'tinyint', + unsigned: true, + auto: true, + ), + 'cat_order' => new Column( + name: 'cat_order', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'text', + not_null: true, + ), + 'can_collapse' => new Column( + name: 'can_collapse', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_cat', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/CustomFields.php b/Sources/Db/Schema/v3_0/CustomFields.php new file mode 100644 index 00000000000..584ea3cd7f0 --- /dev/null +++ b/Sources/Db/Schema/v3_0/CustomFields.php @@ -0,0 +1,279 @@ + 'cust_icq', + 'field_name' => '{icq}', + 'field_desc' => '{icq_desc}', + 'field_type' => 'text', + 'field_length' => 12, + 'field_options' => '', + 'field_order' => 1, + 'mask' => 'regex~[1-9][0-9]{4,9}~i', + 'show_reg' => 0, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '', + 'enclose' => 'ICQ - {INPUT}', + 'placement' => 1, + ], + [ + 'col_name' => 'cust_skype', + 'field_name' => '{skype}', + 'field_desc' => '{skype_desc}', + 'field_type' => 'text', + 'field_length' => 32, + 'field_options' => '', + 'field_order' => 2, + 'mask' => 'nohtml', + 'show_reg' => 0, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '', + 'enclose' => '{INPUT} ', + 'placement' => 1, + ], + [ + 'col_name' => 'cust_loca', + 'field_name' => '{location}', + 'field_desc' => '{location_desc}', + 'field_type' => 'text', + 'field_length' => 50, + 'field_options' => '', + 'field_order' => 4, + 'mask' => 'nohtml', + 'show_reg' => 0, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '', + 'enclose' => '', + 'placement' => 0, + ], + [ + 'col_name' => 'cust_gender', + 'field_name' => '{gender}', + 'field_desc' => '{gender_desc}', + 'field_type' => 'radio', + 'field_length' => 255, + 'field_options' => '{gender_0},{gender_1},{gender_2}', + 'field_order' => 5, + 'mask' => 'nohtml', + 'show_reg' => 1, + 'show_display' => 1, + 'show_mlist' => 0, + 'show_profile' => 'forumprofile', + 'private' => 0, + 'active' => 1, + 'bbc' => 0, + 'can_search' => 0, + 'default_value' => '{gender_0}', + 'enclose' => '', + 'placement' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'custom_fields'; + + $this->columns = [ + 'id_field' => new Column( + name: 'id_field', + type: 'smallint', + auto: true, + ), + 'col_name' => new Column( + name: 'col_name', + type: 'varchar', + size: 12, + not_null: true, + default: '', + ), + 'field_name' => new Column( + name: 'field_name', + type: 'varchar', + size: 40, + not_null: true, + default: '', + ), + 'field_desc' => new Column( + name: 'field_desc', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'field_type' => new Column( + name: 'field_type', + type: 'varchar', + size: 8, + not_null: true, + default: 'text', + ), + 'field_length' => new Column( + name: 'field_length', + type: 'smallint', + not_null: true, + default: 255, + ), + 'field_options' => new Column( + name: 'field_options', + type: 'text', + not_null: true, + ), + 'field_order' => new Column( + name: 'field_order', + type: 'smallint', + not_null: true, + default: 0, + ), + 'mask' => new Column( + name: 'mask', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'show_reg' => new Column( + name: 'show_reg', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'show_display' => new Column( + name: 'show_display', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'show_mlist' => new Column( + name: 'show_mlist', + type: 'smallint', + not_null: true, + default: 0, + ), + 'show_profile' => new Column( + name: 'show_profile', + type: 'varchar', + size: 20, + not_null: true, + default: 'forumprofile', + ), + 'private' => new Column( + name: 'private', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'active' => new Column( + name: 'active', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'bbc' => new Column( + name: 'bbc', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'can_search' => new Column( + name: 'can_search', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'default_value' => new Column( + name: 'default_value', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'enclose' => new Column( + name: 'enclose', + type: 'text', + not_null: true, + ), + 'placement' => new Column( + name: 'placement', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_field', + ], + ), + 'idx_col_name' => new DbIndex( + name: 'idx_col_name', + type: 'unique', + columns: [ + 'col_name', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/GroupModerators.php b/Sources/Db/Schema/v3_0/GroupModerators.php new file mode 100644 index 00000000000..8af3c5d880e --- /dev/null +++ b/Sources/Db/Schema/v3_0/GroupModerators.php @@ -0,0 +1,76 @@ +name = 'group_moderators'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogActions.php b/Sources/Db/Schema/v3_0/LogActions.php new file mode 100644 index 00000000000..bd3e7359935 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogActions.php @@ -0,0 +1,165 @@ +name = 'log_actions'; + + $this->columns = [ + 'id_action' => new Column( + name: 'id_action', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_log' => new Column( + name: 'id_log', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'action' => new Column( + name: 'action', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'extra' => new Column( + name: 'extra', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_action', + ], + ), + 'idx_id_log' => new DbIndex( + name: 'idx_id_log', + columns: [ + 'id_log', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + 'id_board', + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + 'id_msg', + ], + ), + 'idx_id_topic_id_log' => new DbIndex( + name: 'idx_id_topic_id_log', + columns: [ + 'id_topic', + 'id_log', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogActivity.php b/Sources/Db/Schema/v3_0/LogActivity.php new file mode 100644 index 00000000000..b9f0b737789 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogActivity.php @@ -0,0 +1,103 @@ +name = 'log_activity'; + + $this->columns = [ + 'date' => new Column( + name: 'date', + type: 'date', + not_null: true, + ), + 'hits' => new Column( + name: 'hits', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'topics' => new Column( + name: 'topics', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'posts' => new Column( + name: 'posts', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'registers' => new Column( + name: 'registers', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'most_on' => new Column( + name: 'most_on', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'date', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogBanned.php b/Sources/Db/Schema/v3_0/LogBanned.php new file mode 100644 index 00000000000..b526c1f7c74 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogBanned.php @@ -0,0 +1,101 @@ +name = 'log_banned'; + + $this->columns = [ + 'id_ban_log' => new Column( + name: 'id_ban_log', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'email' => new Column( + name: 'email', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_ban_log', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogBoards.php b/Sources/Db/Schema/v3_0/LogBoards.php new file mode 100644 index 00000000000..e852f4e64c4 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogBoards.php @@ -0,0 +1,83 @@ +name = 'log_boards'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_board', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogComments.php b/Sources/Db/Schema/v3_0/LogComments.php new file mode 100644 index 00000000000..3e227669a31 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogComments.php @@ -0,0 +1,146 @@ +name = 'log_comments'; + + $this->columns = [ + 'id_comment' => new Column( + name: 'id_comment', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_name' => new Column( + name: 'member_name', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'comment_type' => new Column( + name: 'comment_type', + type: 'varchar', + size: 8, + not_null: true, + default: 'warning', + ), + 'id_recipient' => new Column( + name: 'id_recipient', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'recipient_name' => new Column( + name: 'recipient_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + not_null: true, + default: 0, + ), + 'id_notice' => new Column( + name: 'id_notice', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'counter' => new Column( + name: 'counter', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_comment', + ], + ), + 'idx_id_recipient' => new DbIndex( + name: 'idx_id_recipient', + columns: [ + 'id_recipient', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_comment_type' => new DbIndex( + name: 'idx_comment_type', + columns: [ + 'comment_type(8)', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogDigest.php b/Sources/Db/Schema/v3_0/LogDigest.php new file mode 100644 index 00000000000..5d04e1e90e2 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogDigest.php @@ -0,0 +1,88 @@ +name = 'log_digest'; + + $this->columns = [ + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'note_type' => new Column( + name: 'note_type', + type: 'varchar', + size: 10, + not_null: true, + default: 'post', + ), + 'daily' => new Column( + name: 'daily', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'exclude' => new Column( + name: 'exclude', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogErrors.php b/Sources/Db/Schema/v3_0/LogErrors.php new file mode 100644 index 00000000000..8744aed1158 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogErrors.php @@ -0,0 +1,151 @@ +name = 'log_errors'; + + $this->columns = [ + 'id_error' => new Column( + name: 'id_error', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'url' => new Column( + name: 'url', + type: 'text', + not_null: true, + ), + 'message' => new Column( + name: 'message', + type: 'text', + not_null: true, + ), + 'session' => new Column( + name: 'session', + type: 'varchar', + size: 128, + not_null: true, + default: '', + ), + 'error_type' => new Column( + name: 'error_type', + type: 'char', + size: 15, + not_null: true, + default: 'general', + ), + 'file' => new Column( + name: 'file', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'line' => new Column( + name: 'line', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'backtrace' => new Column( + name: 'backtrace', + type: 'varchar', + size: 10000, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_error', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_ip' => new DbIndex( + name: 'idx_ip', + columns: [ + 'ip', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogFloodcontrol.php b/Sources/Db/Schema/v3_0/LogFloodcontrol.php new file mode 100644 index 00000000000..d4a16aea6ee --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogFloodcontrol.php @@ -0,0 +1,82 @@ +name = 'log_floodcontrol'; + + $this->columns = [ + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'log_type' => new Column( + name: 'log_type', + type: 'varchar', + size: 30, + default: 'post', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'ip', + 'log_type', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogGroupRequests.php b/Sources/Db/Schema/v3_0/LogGroupRequests.php new file mode 100644 index 00000000000..2338020be96 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogGroupRequests.php @@ -0,0 +1,142 @@ +name = 'log_group_requests'; + + $this->columns = [ + 'id_request' => new Column( + name: 'id_request', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'time_applied' => new Column( + name: 'time_applied', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'reason' => new Column( + name: 'reason', + type: 'text', + not_null: true, + ), + 'comment_type' => new Column( + name: 'comment_type', + type: 'varchar', + size: 8, + not_null: true, + default: 'warning', + ), + 'status' => new Column( + name: 'status', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_acted' => new Column( + name: 'id_member_acted', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_name_acted' => new Column( + name: 'member_name_acted', + type: 'varchar', + size: 255, + not_null: true, + default: 0, + ), + 'time_acted' => new Column( + name: 'time_acted', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'act_reason' => new Column( + name: 'act_reason', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_request', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + 'id_group', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogMarkRead.php b/Sources/Db/Schema/v3_0/LogMarkRead.php new file mode 100644 index 00000000000..f8fa59b4c13 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogMarkRead.php @@ -0,0 +1,83 @@ +name = 'log_mark_read'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_board', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogMemberNotices.php b/Sources/Db/Schema/v3_0/LogMemberNotices.php new file mode 100644 index 00000000000..2ceaf8bf31c --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogMemberNotices.php @@ -0,0 +1,81 @@ +name = 'log_member_notices'; + + $this->columns = [ + 'id_notice' => new Column( + name: 'id_notice', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_notice', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogNotify.php b/Sources/Db/Schema/v3_0/LogNotify.php new file mode 100644 index 00000000000..03e245d648a --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogNotify.php @@ -0,0 +1,103 @@ +name = 'log_notify'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'sent' => new Column( + name: 'sent', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_topic', + 'id_board', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + 'id_member', + ], + ), + 'id_board' => new DbIndex( + name: 'id_board', + columns: [ + 'id_board', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogOnline.php b/Sources/Db/Schema/v3_0/LogOnline.php new file mode 100644 index 00000000000..73fdcfac09b --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogOnline.php @@ -0,0 +1,113 @@ +name = 'log_online'; + + $this->columns = [ + 'session' => new Column( + name: 'session', + type: 'varchar', + size: 128, + default: '', + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'url' => new Column( + name: 'url', + type: 'varchar', + size: 2048, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'session', + ], + ), + 'idx_log_time' => new DbIndex( + name: 'idx_log_time', + columns: [ + 'log_time', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogPackages.php b/Sources/Db/Schema/v3_0/LogPackages.php new file mode 100644 index 00000000000..f71141a676f --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogPackages.php @@ -0,0 +1,192 @@ +name = 'log_packages'; + + $this->columns = [ + 'id_install' => new Column( + name: 'id_install', + type: 'int', + unsigned: true, + auto: true, + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'package_id' => new Column( + name: 'package_id', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'version' => new Column( + name: 'version', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'id_member_installed' => new Column( + name: 'id_member_installed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'member_installed' => new Column( + name: 'member_installed', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'time_installed' => new Column( + name: 'time_installed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'id_member_removed' => new Column( + name: 'id_member_removed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'member_removed' => new Column( + name: 'member_removed', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'time_removed' => new Column( + name: 'time_removed', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'install_state' => new Column( + name: 'install_state', + type: 'mediumint', + not_null: true, + default: 1, + ), + 'failed_steps' => new Column( + name: 'failed_steps', + type: 'text', + not_null: true, + default: false, + ), + 'themes_installed' => new Column( + name: 'themes_installed', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'db_changes' => new Column( + name: 'db_changes', + type: 'text', + not_null: true, + default: false, + ), + 'credits' => new Column( + name: 'credits', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'sha256_hash' => new Column( + name: 'sha256_hash', + type: 'text', + not_null: false, + default: null, + ), + 'smf_version' => new Column( + name: 'smf_version', + type: 'varchar', + size: 5, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_install', + ], + ), + 'filename' => new DbIndex( + name: 'filename', + columns: [ + 'filename', + ], + ), + 'id_hash' => new DbIndex( + name: 'id_hash', + columns: [ + 'id_hash', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogPolls.php b/Sources/Db/Schema/v3_0/LogPolls.php new file mode 100644 index 00000000000..951ee22c86b --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogPolls.php @@ -0,0 +1,86 @@ +name = 'log_polls'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_choice' => new Column( + name: 'id_choice', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'idx_id_poll' => new DbIndex( + name: 'idx_id_poll', + columns: [ + 'id_poll', + 'id_member', + 'id_choice', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogReported.php b/Sources/Db/Schema/v3_0/LogReported.php new file mode 100644 index 00000000000..3a83b5975e5 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogReported.php @@ -0,0 +1,176 @@ +name = 'log_reported'; + + $this->columns = [ + 'id_report' => new Column( + name: 'id_report', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'membername' => new Column( + name: 'membername', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'mediumtext', + not_null: true, + ), + 'time_started' => new Column( + name: 'time_started', + type: 'int', + not_null: true, + default: 0, + ), + 'time_updated' => new Column( + name: 'time_updated', + type: 'int', + not_null: true, + default: 0, + ), + 'num_reports' => new Column( + name: 'num_reports', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'closed' => new Column( + name: 'closed', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'ignore_all' => new Column( + name: 'ignore_all', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_report', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + ], + ), + 'idx_closed' => new DbIndex( + name: 'idx_closed', + columns: [ + 'closed', + ], + ), + 'idx_time_started' => new DbIndex( + name: 'idx_time_started', + columns: [ + 'time_started', + ], + ), + 'idx_id_msg' => new DbIndex( + name: 'idx_id_msg', + columns: [ + 'id_msg', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogReportedComments.php b/Sources/Db/Schema/v3_0/LogReportedComments.php new file mode 100644 index 00000000000..f9a6567e86f --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogReportedComments.php @@ -0,0 +1,122 @@ +name = 'log_reported_comments'; + + $this->columns = [ + 'id_comment' => new Column( + name: 'id_comment', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'id_report' => new Column( + name: 'id_report', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + not_null: true, + ), + 'membername' => new Column( + name: 'membername', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'member_ip' => new Column( + name: 'member_ip', + type: 'inet', + size: 16, + ), + 'comment' => new Column( + name: 'comment', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'time_sent' => new Column( + name: 'time_sent', + type: 'int', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_comment', + ], + ), + 'idx_id_report' => new DbIndex( + name: 'idx_id_report', + columns: [ + 'id_report', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + 'time_sent', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogScheduledTasks.php b/Sources/Db/Schema/v3_0/LogScheduledTasks.php new file mode 100644 index 00000000000..dccfe7101ed --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogScheduledTasks.php @@ -0,0 +1,86 @@ +name = 'log_scheduled_tasks'; + + $this->columns = [ + 'id_log' => new Column( + name: 'id_log', + type: 'mediumint', + auto: true, + ), + 'id_task' => new Column( + name: 'id_task', + type: 'smallint', + not_null: true, + default: 0, + ), + 'time_run' => new Column( + name: 'time_run', + type: 'int', + not_null: true, + default: 0, + ), + 'time_taken' => new Column( + name: 'time_taken', + type: 'float', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_log', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogSearchMessages.php b/Sources/Db/Schema/v3_0/LogSearchMessages.php new file mode 100644 index 00000000000..58011f3da62 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchMessages.php @@ -0,0 +1,76 @@ +name = 'log_search_messages'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_search', + 'id_msg', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogSearchResults.php b/Sources/Db/Schema/v3_0/LogSearchResults.php new file mode 100644 index 00000000000..9d8123aabb5 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchResults.php @@ -0,0 +1,98 @@ +name = 'log_search_results'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'relevance' => new Column( + name: 'relevance', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_matches' => new Column( + name: 'num_matches', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_search', + 'id_topic', + 'id_msg', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogSearchSubjects.php b/Sources/Db/Schema/v3_0/LogSearchSubjects.php new file mode 100644 index 00000000000..fbdd98a30f1 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchSubjects.php @@ -0,0 +1,82 @@ +name = 'log_search_subjects'; + + $this->columns = [ + 'word' => new Column( + name: 'word', + type: 'varchar', + size: 20, + default: '', + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'word', + 'id_topic', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogSearchTopics.php b/Sources/Db/Schema/v3_0/LogSearchTopics.php new file mode 100644 index 00000000000..f76a6ea4b33 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSearchTopics.php @@ -0,0 +1,76 @@ +name = 'log_search_topics'; + + $this->columns = [ + 'id_search' => new Column( + name: 'id_search', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_search', + 'id_topic', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogSpiderHits.php b/Sources/Db/Schema/v3_0/LogSpiderHits.php new file mode 100644 index 00000000000..421e8907647 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSpiderHits.php @@ -0,0 +1,102 @@ +name = 'log_spider_hits'; + + $this->columns = [ + 'id_hit' => new Column( + name: 'id_hit', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'log_time' => new Column( + name: 'log_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'url' => new Column( + name: 'url', + type: 'varchar', + size: 1024, + not_null: true, + default: '', + ), + 'processed' => new Column( + name: 'processed', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_hit', + ], + ), + 'idx_processed' => new DbIndex( + name: 'idx_processed', + columns: [ + 'processed', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogSpiderStats.php b/Sources/Db/Schema/v3_0/LogSpiderStats.php new file mode 100644 index 00000000000..e29515e7b4e --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSpiderStats.php @@ -0,0 +1,88 @@ +name = 'log_spider_stats'; + + $this->columns = [ + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'page_hits' => new Column( + name: 'page_hits', + type: 'int', + not_null: true, + default: 0, + ), + 'last_seen' => new Column( + name: 'last_seen', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'stat_date' => new Column( + name: 'stat_date', + type: 'date', + default: '1004-01-01', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'stat_date', + 'id_spider', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogSubscribed.php b/Sources/Db/Schema/v3_0/LogSubscribed.php new file mode 100644 index 00000000000..db752aac163 --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogSubscribed.php @@ -0,0 +1,160 @@ +name = 'log_subscribed'; + + $this->columns = [ + 'id_sublog' => new Column( + name: 'id_sublog', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_subscribe' => new Column( + name: 'id_subscribe', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'int', + not_null: true, + default: 0, + ), + 'old_id_group' => new Column( + name: 'old_id_group', + type: 'smallint', + not_null: true, + default: 0, + ), + 'start_time' => new Column( + name: 'start_time', + type: 'int', + not_null: true, + default: 0, + ), + 'end_time' => new Column( + name: 'end_time', + type: 'int', + not_null: true, + default: 0, + ), + 'status' => new Column( + name: 'status', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'payments_pending' => new Column( + name: 'payments_pending', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'pending_details' => new Column( + name: 'pending_details', + type: 'text', + not_null: true, + ), + 'reminder_sent' => new Column( + name: 'reminder_sent', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'vendor_ref' => new Column( + name: 'vendor_ref', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_sublog', + ], + ), + 'idx_end_time' => new DbIndex( + name: 'idx_end_time', + columns: [ + 'end_time', + ], + ), + 'idx_reminder_sent' => new DbIndex( + name: 'idx_reminder_sent', + columns: [ + 'reminder_sent', + ], + ), + 'idx_payments_pending' => new DbIndex( + name: 'idx_payments_pending', + columns: [ + 'payments_pending', + ], + ), + 'idx_status' => new DbIndex( + name: 'idx_status', + columns: [ + 'status', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/LogTopics.php b/Sources/Db/Schema/v3_0/LogTopics.php new file mode 100644 index 00000000000..fc3a13c258a --- /dev/null +++ b/Sources/Db/Schema/v3_0/LogTopics.php @@ -0,0 +1,95 @@ +name = 'log_topics'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'unwatched' => new Column( + name: 'unwatched', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'id_topic', + ], + ), + 'idx_id_topic' => new DbIndex( + name: 'idx_id_topic', + columns: [ + 'id_topic', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/MailQueue.php b/Sources/Db/Schema/v3_0/MailQueue.php new file mode 100644 index 00000000000..432ce1a5431 --- /dev/null +++ b/Sources/Db/Schema/v3_0/MailQueue.php @@ -0,0 +1,130 @@ +name = 'mail_queue'; + + $this->columns = [ + 'id_mail' => new Column( + name: 'id_mail', + type: 'int', + unsigned: true, + auto: true, + ), + 'time_sent' => new Column( + name: 'time_sent', + type: 'int', + not_null: true, + default: 0, + ), + 'recipient' => new Column( + name: 'recipient', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'mediumtext', + not_null: true, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'headers' => new Column( + name: 'headers', + type: 'text', + not_null: true, + ), + 'send_html' => new Column( + name: 'send_html', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'priority' => new Column( + name: 'priority', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'private' => new Column( + name: 'private', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_mail', + ], + ), + 'idx_time_sent' => new DbIndex( + name: 'idx_time_sent', + columns: [ + 'time_sent', + ], + ), + 'idx_mail_priority' => new DbIndex( + name: 'idx_mail_priority', + columns: [ + 'priority', + 'id_mail', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/MemberLogins.php b/Sources/Db/Schema/v3_0/MemberLogins.php new file mode 100644 index 00000000000..637b191ca81 --- /dev/null +++ b/Sources/Db/Schema/v3_0/MemberLogins.php @@ -0,0 +1,102 @@ +name = 'member_logins'; + + $this->columns = [ + 'id_login' => new Column( + name: 'id_login', + type: 'int', + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'time' => new Column( + name: 'time', + type: 'int', + not_null: true, + default: 0, + ), + 'ip' => new Column( + name: 'ip', + type: 'inet', + size: 16, + ), + 'ip2' => new Column( + name: 'ip2', + type: 'inet', + size: 16, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_login', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_time' => new DbIndex( + name: 'idx_time', + columns: [ + 'time', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Membergroups.php b/Sources/Db/Schema/v3_0/Membergroups.php new file mode 100644 index 00000000000..a7f4c790b92 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Membergroups.php @@ -0,0 +1,211 @@ + 1, + 'group_name' => '{$default_administrator_group}', + 'description' => '', + 'online_color' => '#FF0000', + 'min_posts' => -1, + 'icons' => '5#iconadmin.png', + 'group_type' => 1, + ], + [ + 'id_group' => 2, + 'group_name' => '{$default_global_moderator_group}', + 'description' => '', + 'online_color' => '#0000FF', + 'min_posts' => -1, + 'icons' => '5#icongmod.png', + 'group_type' => 0, + ], + [ + 'id_group' => 3, + 'group_name' => '{$default_moderator_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => -1, + 'icons' => '5#iconmod.png', + 'group_type' => 0, + ], + [ + 'id_group' => 4, + 'group_name' => '{$default_newbie_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 0, + 'icons' => '1#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 5, + 'group_name' => '{$default_junior_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 50, + 'icons' => '2#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 6, + 'group_name' => '{$default_full_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 100, + 'icons' => '3#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 7, + 'group_name' => '{$default_senior_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 250, + 'icons' => '4#icon.png', + 'group_type' => 0, + ], + [ + 'id_group' => 8, + 'group_name' => '{$default_hero_group}', + 'description' => '', + 'online_color' => '', + 'min_posts' => 500, + 'icons' => '5#icon.png', + 'group_type' => 0, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'membergroups'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'group_name' => new Column( + name: 'group_name', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'text', + not_null: true, + ), + 'online_color' => new Column( + name: 'online_color', + type: 'varchar', + size: 20, + not_null: true, + default: '', + ), + 'min_posts' => new Column( + name: 'min_posts', + type: 'mediumint', + not_null: true, + default: -1, + ), + 'max_messages' => new Column( + name: 'max_messages', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'icons' => new Column( + name: 'icons', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'group_type' => new Column( + name: 'group_type', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'hidden' => new Column( + name: 'hidden', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'id_parent' => new Column( + name: 'id_parent', + type: 'smallint', + not_null: true, + default: -2, + ), + 'tfa_required' => new Column( + name: 'tfa_required', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + ], + ), + 'idx_min_posts' => new DbIndex( + name: 'idx_min_posts', + columns: [ + 'min_posts', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Members.php b/Sources/Db/Schema/v3_0/Members.php new file mode 100644 index 00000000000..619ef35c745 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Members.php @@ -0,0 +1,483 @@ +name = 'members'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'member_name' => new Column( + name: 'member_name', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'date_registered' => new Column( + name: 'date_registered', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'posts' => new Column( + name: 'posts', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'lngfile' => new Column( + name: 'lngfile', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'last_login' => new Column( + name: 'last_login', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'real_name' => new Column( + name: 'real_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'instant_messages' => new Column( + name: 'instant_messages', + type: 'smallint', + not_null: true, + default: 0, + ), + 'unread_messages' => new Column( + name: 'unread_messages', + type: 'smallint', + not_null: true, + default: 0, + ), + 'new_pm' => new Column( + name: 'new_pm', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'alerts' => new Column( + name: 'alerts', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'buddy_list' => new Column( + name: 'buddy_list', + type: 'text', + not_null: true, + ), + 'pm_ignore_list' => new Column( + name: 'pm_ignore_list', + type: 'text', + ), + 'pm_prefs' => new Column( + name: 'pm_prefs', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'mod_prefs' => new Column( + name: 'mod_prefs', + type: 'varchar', + size: 20, + not_null: true, + default: '', + ), + 'passwd' => new Column( + name: 'passwd', + type: 'varchar', + size: 64, + not_null: true, + default: '', + ), + 'email_address' => new Column( + name: 'email_address', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'personal_text' => new Column( + name: 'personal_text', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'birthdate' => new Column( + name: 'birthdate', + type: 'date', + not_null: true, + default: '1004-01-01', + ), + 'website_title' => new Column( + name: 'website_title', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'website_url' => new Column( + name: 'website_url', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'show_online' => new Column( + name: 'show_online', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'time_format' => new Column( + name: 'time_format', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'signature' => new Column( + name: 'signature', + type: 'text', + not_null: true, + ), + 'time_offset' => new Column( + name: 'time_offset', + type: 'float', + not_null: true, + default: 0, + ), + 'avatar' => new Column( + name: 'avatar', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'usertitle' => new Column( + name: 'usertitle', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'member_ip' => new Column( + name: 'member_ip', + type: 'inet', + size: 16, + ), + 'member_ip2' => new Column( + name: 'member_ip2', + type: 'inet', + size: 16, + ), + 'secret_question' => new Column( + name: 'secret_question', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'secret_answer' => new Column( + name: 'secret_answer', + type: 'varchar', + size: 64, + not_null: true, + default: '', + ), + 'id_theme' => new Column( + name: 'id_theme', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_activated' => new Column( + name: 'is_activated', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'validation_code' => new Column( + name: 'validation_code', + type: 'varchar', + size: 10, + not_null: true, + default: '', + ), + 'id_msg_last_visit' => new Column( + name: 'id_msg_last_visit', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'additional_groups' => new Column( + name: 'additional_groups', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'smiley_set' => new Column( + name: 'smiley_set', + type: 'varchar', + size: 48, + not_null: true, + default: '', + ), + 'id_post_group' => new Column( + name: 'id_post_group', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'total_time_logged_in' => new Column( + name: 'total_time_logged_in', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'password_salt' => new Column( + name: 'password_salt', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'ignore_boards' => new Column( + name: 'ignore_boards', + type: 'text', + not_null: true, + ), + 'warning' => new Column( + name: 'warning', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'passwd_flood' => new Column( + name: 'passwd_flood', + type: 'varchar', + size: 12, + not_null: true, + default: '', + ), + 'pm_receive_from' => new Column( + name: 'pm_receive_from', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'timezone' => new Column( + name: 'timezone', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'tfa_secret' => new Column( + name: 'tfa_secret', + type: 'varchar', + size: 24, + not_null: true, + default: '', + ), + 'tfa_backup' => new Column( + name: 'tfa_backup', + type: 'varchar', + size: 64, + not_null: true, + default: '', + ), + 'spoofdetector_name' => new Column( + name: 'spoofdetector_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + ], + ), + 'idx_member_name' => new DbIndex( + name: 'idx_member_name', + columns: [ + 'member_name', + ], + ), + 'idx_real_name' => new DbIndex( + name: 'idx_real_name', + columns: [ + 'real_name', + ], + ), + 'idx_email_address' => new DbIndex( + name: 'idx_email_address', + columns: [ + 'email_address', + ], + ), + 'idx_date_registered' => new DbIndex( + name: 'idx_date_registered', + columns: [ + 'date_registered', + ], + ), + 'idx_id_group' => new DbIndex( + name: 'idx_id_group', + columns: [ + 'id_group', + ], + ), + 'idx_birthdate' => new DbIndex( + name: 'idx_birthdate', + columns: [ + 'birthdate', + ], + ), + 'idx_posts' => new DbIndex( + name: 'idx_posts', + columns: [ + 'posts', + ], + ), + 'idx_last_login' => new DbIndex( + name: 'idx_last_login', + columns: [ + 'last_login', + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + 'lngfile(30)', + ], + ), + 'idx_id_post_group' => new DbIndex( + name: 'idx_id_post_group', + columns: [ + 'id_post_group', + ], + ), + 'idx_warning' => new DbIndex( + name: 'idx_warning', + columns: [ + 'warning', + ], + ), + 'idx_total_time_logged_in' => new DbIndex( + name: 'idx_total_time_logged_in', + columns: [ + 'total_time_logged_in', + ], + ), + 'idx_id_theme' => new DbIndex( + name: 'idx_id_theme', + columns: [ + 'id_theme', + ], + ), + 'idx_active_real_name' => new DbIndex( + name: 'idx_active_real_name', + columns: [ + 'is_activated', + 'real_name', + ], + ), + 'idx_spoofdetector_name' => new DbIndex( + name: 'idx_spoofdetector_name', + columns: [ + 'spoofdetector_name', + ], + ), + 'idx_spoofdetector_name_id' => new DbIndex( + name: 'idx_spoofdetector_name_id', + columns: [ + 'spoofdetector_name', + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Mentions.php b/Sources/Db/Schema/v3_0/Mentions.php new file mode 100644 index 00000000000..5fe9e21db3a --- /dev/null +++ b/Sources/Db/Schema/v3_0/Mentions.php @@ -0,0 +1,104 @@ +name = 'mentions'; + + $this->columns = [ + 'content_id' => new Column( + name: 'content_id', + type: 'int', + default: 0, + ), + 'content_type' => new Column( + name: 'content_type', + type: 'varchar', + size: 10, + default: '', + ), + 'id_mentioned' => new Column( + name: 'id_mentioned', + type: 'int', + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + ), + 'time' => new Column( + name: 'time', + type: 'int', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'content_id', + 'content_type', + 'id_mentioned', + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + 'content_id', + 'content_type', + ], + ), + 'mentionee' => new DbIndex( + name: 'mentionee', + columns: [ + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/MessageIcons.php b/Sources/Db/Schema/v3_0/MessageIcons.php new file mode 100644 index 00000000000..c7c127a8484 --- /dev/null +++ b/Sources/Db/Schema/v3_0/MessageIcons.php @@ -0,0 +1,169 @@ + 'xx', + 'title' => 'Standard', + 'icon_order' => 0, + ], + [ + 'filename' => 'thumbup', + 'title' => 'Thumb Up', + 'icon_order' => 1, + ], + [ + 'filename' => 'thumbdown', + 'title' => 'Thumb Down', + 'icon_order' => 2, + ], + [ + 'filename' => 'exclamation', + 'title' => 'Exclamation point', + 'icon_order' => 3, + ], + [ + 'filename' => 'question', + 'title' => 'Question mark', + 'icon_order' => 4, + ], + [ + 'filename' => 'lamp', + 'title' => 'Lamp', + 'icon_order' => 5, + ], + [ + 'filename' => 'smiley', + 'title' => 'Smiley', + 'icon_order' => 6, + ], + [ + 'filename' => 'angry', + 'title' => 'Angry', + 'icon_order' => 7, + ], + [ + 'filename' => 'cheesy', + 'title' => 'Cheesy', + 'icon_order' => 8, + ], + [ + 'filename' => 'grin', + 'title' => 'Grin', + 'icon_order' => 9, + ], + [ + 'filename' => 'sad', + 'title' => 'Sad', + 'icon_order' => 10, + ], + [ + 'filename' => 'wink', + 'title' => 'Wink', + 'icon_order' => 11, + ], + [ + 'filename' => 'poll', + 'title' => 'Poll', + 'icon_order' => 12, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'message_icons'; + + $this->columns = [ + 'id_icon' => new Column( + name: 'id_icon', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'title' => new Column( + name: 'title', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'icon_order' => new Column( + name: 'icon_order', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_icon', + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + columns: [ + 'id_board', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Messages.php b/Sources/Db/Schema/v3_0/Messages.php new file mode 100644 index 00000000000..a86685d9c33 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Messages.php @@ -0,0 +1,272 @@ + 1, + 'id_msg_modified' => 1, + 'id_topic' => 1, + 'id_board' => 1, + 'poster_time' => '{$current_time}', + 'subject' => '{$default_topic_subject}', + 'poster_name' => 'Simple Machines', + 'poster_email' => 'info@simplemachines.org', + 'modified_name' => '', + 'body' => '{$default_topic_message}', + 'icon' => 'xx', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'messages'; + + $this->columns = [ + 'id_msg' => new Column( + name: 'id_msg', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'poster_time' => new Column( + name: 'poster_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_msg_modified' => new Column( + name: 'id_msg_modified', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'poster_name' => new Column( + name: 'poster_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'poster_email' => new Column( + name: 'poster_email', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'poster_ip' => new Column( + name: 'poster_ip', + type: 'inet', + size: 16, + ), + 'smileys_enabled' => new Column( + name: 'smileys_enabled', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'modified_time' => new Column( + name: 'modified_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'modified_name' => new Column( + name: 'modified_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'modified_reason' => new Column( + name: 'modified_reason', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + 'icon' => new Column( + name: 'icon', + type: 'varchar', + size: 16, + not_null: true, + default: 'xx', + ), + 'approved' => new Column( + name: 'approved', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'likes' => new Column( + name: 'likes', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'version' => new Column( + name: 'version', + type: 'varchar', + size: 5, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_msg', + ], + ), + 'idx_id_board' => new DbIndex( + name: 'idx_id_board', + type: 'unique', + columns: [ + 'id_board', + 'id_msg', + 'approved', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'id_msg', + ], + ), + 'idx_ip_index' => new DbIndex( + name: 'idx_ip_index', + columns: [ + 'poster_ip', + 'id_topic', + ], + ), + 'idx_participation' => new DbIndex( + name: 'idx_participation', + columns: [ + 'id_member', + 'id_topic', + ], + ), + 'idx_show_posts' => new DbIndex( + name: 'idx_show_posts', + columns: [ + 'id_member', + 'id_board', + ], + ), + 'idx_id_member_msg' => new DbIndex( + name: 'idx_id_member_msg', + columns: [ + 'id_member', + 'approved', + 'id_msg', + ], + ), + 'idx_current_topic' => new DbIndex( + name: 'idx_current_topic', + columns: [ + 'id_topic', + 'id_msg', + 'id_member', + 'approved', + ], + ), + 'idx_related_ip' => new DbIndex( + name: 'idx_related_ip', + columns: [ + 'id_member', + 'poster_ip', + 'id_msg', + ], + ), + 'idx_likes' => new DbIndex( + name: 'idx_likes', + columns: [ + 'likes', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/ModeratorGroups.php b/Sources/Db/Schema/v3_0/ModeratorGroups.php new file mode 100644 index 00000000000..56f84dc6443 --- /dev/null +++ b/Sources/Db/Schema/v3_0/ModeratorGroups.php @@ -0,0 +1,76 @@ +name = 'moderator_groups'; + + $this->columns = [ + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_board', + 'id_group', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Moderators.php b/Sources/Db/Schema/v3_0/Moderators.php new file mode 100644 index 00000000000..0807d170d82 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Moderators.php @@ -0,0 +1,76 @@ +name = 'moderators'; + + $this->columns = [ + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_board', + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PackageServers.php b/Sources/Db/Schema/v3_0/PackageServers.php new file mode 100644 index 00000000000..2a8297fe4e8 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PackageServers.php @@ -0,0 +1,105 @@ + 'Simple Machines Third-party Mod Site', + 'url' => 'https://custom.simplemachines.org/packages/mods', + 'validation_url' => 'https://custom.simplemachines.org/api.php?action=validate;version=v1;smf_version={SMF_VERSION}', + ], + [ + 'name' => 'Simple Machines Downloads Site', + 'url' => 'https://download.simplemachines.org/browse.php?api=v1;smf_version={SMF_VERSION}', + 'validation_url' => 'https://download.simplemachines.org/validate.php?api=v1;smf_version={SMF_VERSION}', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'package_servers'; + + $this->columns = [ + 'id_server' => new Column( + name: 'id_server', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'url' => new Column( + name: 'url', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'validation_url' => new Column( + name: 'validation_url', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'extra' => new Column( + name: 'extra', + type: 'text', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_server', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PermissionProfiles.php b/Sources/Db/Schema/v3_0/PermissionProfiles.php new file mode 100644 index 00000000000..fa15debda59 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PermissionProfiles.php @@ -0,0 +1,92 @@ + 1, + 'profile_name' => 'default', + ], + [ + 'id_profile' => 2, + 'profile_name' => 'no_polls', + ], + [ + 'id_profile' => 3, + 'profile_name' => 'reply_only', + ], + [ + 'id_profile' => 4, + 'profile_name' => 'read_only', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'permission_profiles'; + + $this->columns = [ + 'id_profile' => new Column( + name: 'id_profile', + type: 'smallint', + auto: true, + ), + 'profile_name' => new Column( + name: 'profile_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_profile', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Permissions.php b/Sources/Db/Schema/v3_0/Permissions.php new file mode 100644 index 00000000000..a5f9d892619 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Permissions.php @@ -0,0 +1,286 @@ + -1, + 'permission' => 'search_posts', + ], + [ + 'id_group' => -1, + 'permission' => 'calendar_view', + ], + [ + 'id_group' => -1, + 'permission' => 'view_stats', + ], + [ + 'id_group' => 0, + 'permission' => 'view_mlist', + ], + [ + 'id_group' => 0, + 'permission' => 'search_posts', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_view', + ], + [ + 'id_group' => 0, + 'permission' => 'pm_read', + ], + [ + 'id_group' => 0, + 'permission' => 'pm_send', + ], + [ + 'id_group' => 0, + 'permission' => 'pm_draft', + ], + [ + 'id_group' => 0, + 'permission' => 'calendar_view', + ], + [ + 'id_group' => 0, + 'permission' => 'view_stats', + ], + [ + 'id_group' => 0, + 'permission' => 'who_view', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_identity_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_password_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_blurb_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_displayed_name_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_signature_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_website_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_forum_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_extra_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_remove_own', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_server_avatar', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_upload_avatar', + ], + [ + 'id_group' => 0, + 'permission' => 'profile_remote_avatar', + ], + [ + 'id_group' => 0, + 'permission' => 'send_email_to_members', + ], + [ + 'id_group' => 2, + 'permission' => 'view_mlist', + ], + [ + 'id_group' => 2, + 'permission' => 'search_posts', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_view', + ], + [ + 'id_group' => 2, + 'permission' => 'pm_read', + ], + [ + 'id_group' => 2, + 'permission' => 'pm_send', + ], + [ + 'id_group' => 2, + 'permission' => 'pm_draft', + ], + [ + 'id_group' => 2, + 'permission' => 'calendar_view', + ], + [ + 'id_group' => 2, + 'permission' => 'view_stats', + ], + [ + 'id_group' => 2, + 'permission' => 'who_view', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_identity_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_password_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_blurb_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_displayed_name_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_signature_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_website_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_forum_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_extra_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_remove_own', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_server_avatar', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_upload_avatar', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_remote_avatar', + ], + [ + 'id_group' => 2, + 'permission' => 'send_email_to_members', + ], + [ + 'id_group' => 2, + 'permission' => 'profile_title_own', + ], + [ + 'id_group' => 2, + 'permission' => 'calendar_post', + ], + [ + 'id_group' => 2, + 'permission' => 'calendar_edit_any', + ], + [ + 'id_group' => 2, + 'permission' => 'access_mod_center', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'permissions'; + + $this->columns = [ + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + default: 0, + ), + 'permission' => new Column( + name: 'permission', + type: 'varchar', + size: 30, + default: '', + ), + 'add_deny' => new Column( + name: 'add_deny', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_group', + 'permission', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PersonalMessages.php b/Sources/Db/Schema/v3_0/PersonalMessages.php new file mode 100644 index 00000000000..ceb9340f7ce --- /dev/null +++ b/Sources/Db/Schema/v3_0/PersonalMessages.php @@ -0,0 +1,142 @@ +name = 'personal_messages'; + + $this->columns = [ + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_pm_head' => new Column( + name: 'id_pm_head', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_from' => new Column( + name: 'id_member_from', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'deleted_by_sender' => new Column( + name: 'deleted_by_sender', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'from_name' => new Column( + name: 'from_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'msgtime' => new Column( + name: 'msgtime', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'body' => new Column( + name: 'body', + type: 'text', + not_null: true, + ), + 'version' => new Column( + name: 'version', + type: 'varchar', + size: 5, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_pm', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member_from', + 'deleted_by_sender', + ], + ), + 'idx_msgtime' => new DbIndex( + name: 'idx_msgtime', + columns: [ + 'msgtime', + ], + ), + 'idx_id_pm_head' => new DbIndex( + name: 'idx_id_pm_head', + columns: [ + 'id_pm_head', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PmLabeledMessages.php b/Sources/Db/Schema/v3_0/PmLabeledMessages.php new file mode 100644 index 00000000000..9345543a797 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmLabeledMessages.php @@ -0,0 +1,76 @@ +name = 'pm_labeled_messages'; + + $this->columns = [ + 'id_label' => new Column( + name: 'id_label', + type: 'int', + unsigned: true, + default: 0, + ), + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_label', + 'id_pm', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PmLabels.php b/Sources/Db/Schema/v3_0/PmLabels.php new file mode 100644 index 00000000000..02fa039e1c1 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmLabels.php @@ -0,0 +1,83 @@ +name = 'pm_labels'; + + $this->columns = [ + 'id_label' => new Column( + name: 'id_label', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_label', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PmRecipients.php b/Sources/Db/Schema/v3_0/PmRecipients.php new file mode 100644 index 00000000000..0b3a0312470 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmRecipients.php @@ -0,0 +1,119 @@ +name = 'pm_recipients'; + + $this->columns = [ + 'id_pm' => new Column( + name: 'id_pm', + type: 'int', + unsigned: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'bcc' => new Column( + name: 'bcc', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_read' => new Column( + name: 'is_read', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_new' => new Column( + name: 'is_new', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'deleted' => new Column( + name: 'deleted', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'in_inbox' => new Column( + name: 'in_inbox', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_pm', + 'id_member', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'deleted', + 'id_pm', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PmRules.php b/Sources/Db/Schema/v3_0/PmRules.php new file mode 100644 index 00000000000..792e5aa94a4 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PmRules.php @@ -0,0 +1,118 @@ +name = 'pm_rules'; + + $this->columns = [ + 'id_rule' => new Column( + name: 'id_rule', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'rule_name' => new Column( + name: 'rule_name', + type: 'varchar', + size: 60, + not_null: true, + ), + 'criteria' => new Column( + name: 'criteria', + type: 'text', + not_null: true, + ), + 'actions' => new Column( + name: 'actions', + type: 'text', + not_null: true, + ), + 'delete_pm' => new Column( + name: 'delete_pm', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'is_or' => new Column( + name: 'is_or', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_rule', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_delete_pm' => new DbIndex( + name: 'idx_delete_pm', + columns: [ + 'delete_pm', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/PollChoices.php b/Sources/Db/Schema/v3_0/PollChoices.php new file mode 100644 index 00000000000..fc11ca029a0 --- /dev/null +++ b/Sources/Db/Schema/v3_0/PollChoices.php @@ -0,0 +1,90 @@ +name = 'poll_choices'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'id_choice' => new Column( + name: 'id_choice', + type: 'tinyint', + unsigned: true, + default: 0, + ), + 'label' => new Column( + name: 'label', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'votes' => new Column( + name: 'votes', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_poll', + 'id_choice', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Polls.php b/Sources/Db/Schema/v3_0/Polls.php new file mode 100644 index 00000000000..f92ee82d95b --- /dev/null +++ b/Sources/Db/Schema/v3_0/Polls.php @@ -0,0 +1,145 @@ +name = 'polls'; + + $this->columns = [ + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'question' => new Column( + name: 'question', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'voting_locked' => new Column( + name: 'voting_locked', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'max_votes' => new Column( + name: 'max_votes', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 1, + ), + 'expire_time' => new Column( + name: 'expire_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'hide_results' => new Column( + name: 'hide_results', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'change_vote' => new Column( + name: 'change_vote', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'guest_vote' => new Column( + name: 'guest_vote', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_guest_voters' => new Column( + name: 'num_guest_voters', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'reset_poll' => new Column( + name: 'reset_poll', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'poster_name' => new Column( + name: 'poster_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_poll', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Qanda.php b/Sources/Db/Schema/v3_0/Qanda.php new file mode 100644 index 00000000000..031cb5e7456 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Qanda.php @@ -0,0 +1,94 @@ +name = 'qanda'; + + $this->columns = [ + 'id_question' => new Column( + name: 'id_question', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'lngfile' => new Column( + name: 'lngfile', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'question' => new Column( + name: 'question', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'answers' => new Column( + name: 'answers', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_question', + ], + ), + 'idx_lngfile' => new DbIndex( + name: 'idx_lngfile', + columns: [ + 'lngfile', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/ScheduledTasks.php b/Sources/Db/Schema/v3_0/ScheduledTasks.php new file mode 100644 index 00000000000..2a42e2f5f26 --- /dev/null +++ b/Sources/Db/Schema/v3_0/ScheduledTasks.php @@ -0,0 +1,243 @@ + 3, + 'next_time' => 0, + 'time_offset' => 60, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'daily_maintenance', + 'callable' => '', + ], + [ + 'id_task' => 5, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'daily_digest', + 'callable' => '', + ], + [ + 'id_task' => 6, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'w', + 'disabled' => 0, + 'task' => 'weekly_digest', + 'callable' => '', + ], + [ + 'id_task' => 7, + 'next_time' => 0, + 'time_offset' => '{$sched_task_offset}', + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'fetchSMfiles', + 'callable' => '', + ], + [ + 'id_task' => 8, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 1, + 'task' => 'birthdayemails', + 'callable' => '', + ], + [ + 'id_task' => 9, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'w', + 'disabled' => 0, + 'task' => 'weekly_maintenance', + 'callable' => '', + ], + [ + 'id_task' => 10, + 'next_time' => 0, + 'time_offset' => 120, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 1, + 'task' => 'paid_subscriptions', + 'callable' => '', + ], + [ + 'id_task' => 11, + 'next_time' => 0, + 'time_offset' => 120, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'remove_temp_attachments', + 'callable' => '', + ], + [ + 'id_task' => 12, + 'next_time' => 0, + 'time_offset' => 180, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'remove_topic_redirect', + 'callable' => '', + ], + [ + 'id_task' => 13, + 'next_time' => 0, + 'time_offset' => 240, + 'time_regularity' => 1, + 'time_unit' => 'd', + 'disabled' => 0, + 'task' => 'remove_old_drafts', + 'callable' => '', + ], + [ + 'id_task' => 14, + 'next_time' => 0, + 'time_offset' => 0, + 'time_regularity' => 1, + 'time_unit' => 'w', + 'disabled' => 1, + 'task' => 'prune_log_topics', + 'callable' => '', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'scheduled_tasks'; + + $this->columns = [ + 'id_task' => new Column( + name: 'id_task', + type: 'smallint', + auto: true, + ), + 'next_time' => new Column( + name: 'next_time', + type: 'int', + not_null: true, + default: 0, + ), + 'time_offset' => new Column( + name: 'time_offset', + type: 'int', + not_null: true, + default: 0, + ), + 'time_regularity' => new Column( + name: 'time_regularity', + type: 'smallint', + not_null: true, + default: 0, + ), + 'time_unit' => new Column( + name: 'time_unit', + type: 'varchar', + size: 1, + not_null: true, + default: 'h', + ), + 'disabled' => new Column( + name: 'disabled', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'task' => new Column( + name: 'task', + type: 'varchar', + size: 24, + not_null: true, + default: '', + ), + 'callable' => new Column( + name: 'callable', + type: 'varchar', + size: 60, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_task', + ], + ), + 'idx_next_time' => new DbIndex( + name: 'idx_next_time', + columns: [ + 'next_time', + ], + ), + 'idx_disabled' => new DbIndex( + name: 'idx_disabled', + columns: [ + 'disabled', + ], + ), + 'idx_task' => new DbIndex( + name: 'idx_task', + type: 'unique', + columns: [ + 'task', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Sessions.php b/Sources/Db/Schema/v3_0/Sessions.php new file mode 100644 index 00000000000..2f2a982c427 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Sessions.php @@ -0,0 +1,82 @@ +name = 'sessions'; + + $this->columns = [ + 'session_id' => new Column( + name: 'session_id', + type: 'varchar', + size: 128, + not_null: true, + default: '', + ), + 'last_update' => new Column( + name: 'last_update', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'data' => new Column( + name: 'data', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'session_id', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Settings.php b/Sources/Db/Schema/v3_0/Settings.php new file mode 100644 index 00000000000..9652057c352 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Settings.php @@ -0,0 +1,893 @@ + 'additional_options_collapsable', + 'value' => '1', + ], + [ + 'variable' => 'adminlog_enabled', + 'value' => '1', + ], + [ + 'variable' => 'alerts_auto_purge', + 'value' => '30', + ], + [ + 'variable' => 'allow_editDisplayName', + 'value' => '1', + ], + [ + 'variable' => 'allow_expire_redirect', + 'value' => '1', + ], + [ + 'variable' => 'allow_guestAccess', + 'value' => '1', + ], + [ + 'variable' => 'allow_hideOnline', + 'value' => '1', + ], + [ + 'variable' => 'attachmentCheckExtensions', + 'value' => '0', + ], + [ + 'variable' => 'attachmentDirFileLimit', + 'value' => '1000', + ], + [ + 'variable' => 'attachmentDirSizeLimit', + 'value' => '10240', + ], + [ + 'variable' => 'attachmentEnable', + 'value' => '1', + ], + [ + 'variable' => 'attachmentExtensions', + 'value' => 'doc,gif,jpg,mpg,pdf,png,txt,zip', + ], + [ + 'variable' => 'attachmentNumPerPostLimit', + 'value' => '4', + ], + [ + 'variable' => 'attachmentPostLimit', + 'value' => '192', + ], + [ + 'variable' => 'attachmentShowImages', + 'value' => '1', + ], + [ + 'variable' => 'attachmentSizeLimit', + 'value' => '128', + ], + [ + 'variable' => 'attachmentThumbHeight', + 'value' => '150', + ], + [ + 'variable' => 'attachmentThumbWidth', + 'value' => '150', + ], + [ + 'variable' => 'attachmentThumbnails', + 'value' => '1', + ], + [ + 'variable' => 'attachmentUploadDir', + 'value' => '{$attachdir}', + ], + [ + 'variable' => 'attachment_image_paranoid', + 'value' => '0', + ], + [ + 'variable' => 'attachment_image_reencode', + 'value' => '1', + ], + [ + 'variable' => 'attachment_thumb_png', + 'value' => '1', + ], + [ + 'variable' => 'attachments_21_done', + 'value' => '1', + ], + [ + 'variable' => 'autoFixDatabase', + 'value' => '1', + ], + [ + 'variable' => 'autoLinkUrls', + 'value' => '1', + ], + [ + 'variable' => 'avatar_action_too_large', + 'value' => 'option_css_resize', + ], + [ + 'variable' => 'avatar_directory', + 'value' => '{$boarddir}/avatars', + ], + [ + 'variable' => 'avatar_download_png', + 'value' => '1', + ], + [ + 'variable' => 'avatar_max_height_external', + 'value' => '65', + ], + [ + 'variable' => 'avatar_max_height_upload', + 'value' => '65', + ], + [ + 'variable' => 'avatar_max_width_external', + 'value' => '65', + ], + [ + 'variable' => 'avatar_max_width_upload', + 'value' => '65', + ], + [ + 'variable' => 'avatar_paranoid', + 'value' => '0', + ], + [ + 'variable' => 'avatar_reencode', + 'value' => '1', + ], + [ + 'variable' => 'avatar_resize_upload', + 'value' => '1', + ], + [ + 'variable' => 'avatar_url', + 'value' => '{$boardurl}/avatars', + ], + [ + 'variable' => 'banLastUpdated', + 'value' => '0', + ], + [ + 'variable' => 'birthday_email', + 'value' => 'happy_birthday', + ], + [ + 'variable' => 'boardindex_max_depth', + 'value' => '5', + ], + [ + 'variable' => 'cal_days_for_index', + 'value' => '7', + ], + [ + 'variable' => 'cal_daysaslink', + 'value' => '0', + ], + [ + 'variable' => 'cal_defaultboard', + 'value' => '', + ], + [ + 'variable' => 'cal_disable_prev_next', + 'value' => '0', + ], + [ + 'variable' => 'cal_display_type', + 'value' => '0', + ], + [ + 'variable' => 'cal_enabled', + 'value' => '0', + ], + [ + 'variable' => 'cal_maxspan', + 'value' => '0', + ], + [ + 'variable' => 'cal_maxyear', + 'value' => '2030', + ], + [ + 'variable' => 'cal_minyear', + 'value' => '2008', + ], + [ + 'variable' => 'cal_prev_next_links', + 'value' => '1', + ], + [ + 'variable' => 'cal_short_days', + 'value' => '0', + ], + [ + 'variable' => 'cal_short_months', + 'value' => '0', + ], + [ + 'variable' => 'cal_showInTopic', + 'value' => '1', + ], + [ + 'variable' => 'cal_showbdays', + 'value' => '1', + ], + [ + 'variable' => 'cal_showevents', + 'value' => '1', + ], + [ + 'variable' => 'cal_showholidays', + 'value' => '1', + ], + [ + 'variable' => 'cal_week_links', + 'value' => '2', + ], + [ + 'variable' => 'censorIgnoreCase', + 'value' => '1', + ], + [ + 'variable' => 'censor_proper', + 'value' => '', + ], + [ + 'variable' => 'censor_vulgar', + 'value' => '', + ], + [ + 'variable' => 'compactTopicPagesContiguous', + 'value' => '5', + ], + [ + 'variable' => 'compactTopicPagesEnable', + 'value' => '1', + ], + [ + 'variable' => 'cookieTime', + 'value' => '3153600', + ], + [ + 'variable' => 'currentAttachmentUploadDir', + 'value' => 1, + ], + [ + 'variable' => 'custom_avatar_dir', + 'value' => '{$boarddir}/custom_avatar', + ], + [ + 'variable' => 'custom_avatar_url', + 'value' => '{$boardurl}/custom_avatar', + ], + [ + 'variable' => 'databaseSession_enable', + 'value' => '{$databaseSession_enable}', + ], + [ + 'variable' => 'databaseSession_lifetime', + 'value' => '2880', + ], + [ + 'variable' => 'databaseSession_loose', + 'value' => '1', + ], + [ + 'variable' => 'defaultMaxListItems', + 'value' => '15', + ], + [ + 'variable' => 'defaultMaxMembers', + 'value' => '30', + ], + [ + 'variable' => 'defaultMaxMessages', + 'value' => '15', + ], + [ + 'variable' => 'defaultMaxTopics', + 'value' => '20', + ], + [ + 'variable' => 'default_personal_text', + 'value' => '', + ], + [ + 'variable' => 'displayFields', + 'value' => '[{"col_name":"cust_icq","title":"ICQ","type":"text","order":"1","bbc":"0","placement":"1","enclose":"\\"ICQ<\\/a>","mlist":"0"},{"col_name":"cust_skype","title":"Skype","type":"text","order":"2","bbc":"0","placement":"1","enclose":"\\"{INPUT}\\"<\\/a> ","mlist":"0"},{"col_name":"cust_loca","title":"Location","type":"text","order":"4","bbc":"0","placement":"0","enclose":"","mlist":"0"},{"col_name":"cust_gender","title":"Gender","type":"radio","order":"5","bbc":"0","placement":"1","enclose":"<\\/span>","mlist":"0","options":["None","Male","Female"]}]', + ], + [ + 'variable' => 'dont_repeat_buddylists', + 'value' => '1', + ], + [ + 'variable' => 'dont_repeat_smileys_20', + 'value' => '1', + ], + [ + 'variable' => 'dont_repeat_theme_core', + 'value' => '1', + ], + [ + 'variable' => 'drafts_autosave_enabled', + 'value' => '1', + ], + [ + 'variable' => 'drafts_keep_days', + 'value' => '7', + ], + [ + 'variable' => 'drafts_pm_enabled', + 'value' => '1', + ], + [ + 'variable' => 'drafts_post_enabled', + 'value' => '1', + ], + [ + 'variable' => 'drafts_show_saved_enabled', + 'value' => '1', + ], + [ + 'variable' => 'edit_disable_time', + 'value' => '0', + ], + [ + 'variable' => 'edit_wait_time', + 'value' => '90', + ], + [ + 'variable' => 'enableAllMessages', + 'value' => '0', + ], + [ + 'variable' => 'enableBBC', + 'value' => '1', + ], + [ + 'variable' => 'enableCompressedOutput', + 'value' => '{$enableCompressedOutput}', + ], + [ + 'variable' => 'enableErrorLogging', + 'value' => '1', + ], + [ + 'variable' => 'enableParticipation', + 'value' => '1', + ], + [ + 'variable' => 'enablePostHTML', + 'value' => '0', + ], + [ + 'variable' => 'enablePreviousNext', + 'value' => '1', + ], + [ + 'variable' => 'enableThemes', + 'value' => '1', + ], + [ + 'variable' => 'enable_ajax_alerts', + 'value' => '1', + ], + [ + 'variable' => 'enable_buddylist', + 'value' => '1', + ], + [ + 'variable' => 'export_dir', + 'value' => '{$boarddir}/exports', + ], + [ + 'variable' => 'export_expiry', + 'value' => '7', + ], + [ + 'variable' => 'export_min_diskspace_pct', + 'value' => '5', + ], + [ + 'variable' => 'export_rate', + 'value' => '250', + ], + [ + 'variable' => 'failed_login_threshold', + 'value' => '3', + ], + [ + 'variable' => 'gravatarAllowExtraEmail', + 'value' => '1', + ], + [ + 'variable' => 'gravatarEnabled', + 'value' => '1', + ], + [ + 'variable' => 'gravatarMaxRating', + 'value' => 'PG', + ], + [ + 'variable' => 'gravatarOverride', + 'value' => '0', + ], + [ + 'variable' => 'httponlyCookies', + 'value' => '1', + ], + [ + 'variable' => 'json_done', + 'value' => '1', + ], + [ + 'variable' => 'knownThemes', + 'value' => '1', + ], + [ + 'variable' => 'lastActive', + 'value' => '15', + ], + [ + 'variable' => 'last_mod_report_action', + 'value' => '0', + ], + [ + 'variable' => 'loginHistoryDays', + 'value' => '30', + ], + [ + 'variable' => 'mail_limit', + 'value' => '5', + ], + [ + 'variable' => 'mail_next_send', + 'value' => '0', + ], + [ + 'variable' => 'mail_quantity', + 'value' => '5', + ], + [ + 'variable' => 'mail_recent', + 'value' => '0000000000|0', + ], + [ + 'variable' => 'mail_type', + 'value' => '0', + ], + [ + 'variable' => 'mark_read_beyond', + 'value' => '90', + ], + [ + 'variable' => 'mark_read_delete_beyond', + 'value' => '365', + ], + [ + 'variable' => 'mark_read_max_users', + 'value' => '500', + ], + [ + 'variable' => 'maxMsgID', + 'value' => '1', + ], + [ + 'variable' => 'max_image_height', + 'value' => '0', + ], + [ + 'variable' => 'max_image_width', + 'value' => '0', + ], + [ + 'variable' => 'max_messageLength', + 'value' => '20000', + ], + [ + 'variable' => 'minimize_files', + 'value' => '1', + ], + [ + 'variable' => 'modlog_enabled', + 'value' => '1', + ], + [ + 'variable' => 'mostDate', + 'value' => '{$current_time}', + ], + [ + 'variable' => 'mostOnline', + 'value' => '1', + ], + [ + 'variable' => 'mostOnlineToday', + 'value' => '1', + ], + [ + 'variable' => 'news', + 'value' => '{$default_news}', + ], + [ + 'variable' => 'next_task_time', + 'value' => '1', + ], + [ + 'variable' => 'number_format', + 'value' => '1234.00', + ], + [ + 'variable' => 'oldTopicDays', + 'value' => '120', + ], + [ + 'variable' => 'onlineEnable', + 'value' => '0', + ], + [ + 'variable' => 'package_make_backups', + 'value' => '1', + ], + [ + 'variable' => 'permission_enable_deny', + 'value' => '0', + ], + [ + 'variable' => 'permission_enable_postgroups', + 'value' => '0', + ], + [ + 'variable' => 'pm_spam_settings', + 'value' => '10,5,20', + ], + [ + 'variable' => 'pollMode', + 'value' => '1', + ], + [ + 'variable' => 'pruningOptions', + 'value' => '30,180,180,180,30,0', + ], + [ + 'variable' => 'recycle_board', + 'value' => '0', + ], + [ + 'variable' => 'recycle_enable', + 'value' => '0', + ], + [ + 'variable' => 'reg_verification', + 'value' => '1', + ], + [ + 'variable' => 'registration_method', + 'value' => '{$registration_method}', + ], + [ + 'variable' => 'requireAgreement', + 'value' => '1', + ], + [ + 'variable' => 'requirePolicyAgreement', + 'value' => '0', + ], + [ + 'variable' => 'reserveCase', + 'value' => '1', + ], + [ + 'variable' => 'reserveName', + 'value' => '1', + ], + [ + 'variable' => 'reserveNames', + 'value' => '{$default_reserved_names}', + ], + [ + 'variable' => 'reserveUser', + 'value' => '1', + ], + [ + 'variable' => 'reserveWord', + 'value' => '0', + ], + [ + 'variable' => 'samesiteCookies', + 'value' => 'lax', + ], + [ + 'variable' => 'search_cache_size', + 'value' => '50', + ], + [ + 'variable' => 'search_floodcontrol_time', + 'value' => '5', + ], + [ + 'variable' => 'search_max_results', + 'value' => '1200', + ], + [ + 'variable' => 'search_results_per_page', + 'value' => '30', + ], + [ + 'variable' => 'search_weight_age', + 'value' => '25', + ], + [ + 'variable' => 'search_weight_first_message', + 'value' => '10', + ], + [ + 'variable' => 'search_weight_frequency', + 'value' => '30', + ], + [ + 'variable' => 'search_weight_length', + 'value' => '20', + ], + [ + 'variable' => 'search_weight_subject', + 'value' => '15', + ], + [ + 'variable' => 'securityDisable_moderate', + 'value' => '1', + ], + [ + 'variable' => 'send_validation_onChange', + 'value' => '0', + ], + [ + 'variable' => 'send_welcomeEmail', + 'value' => '1', + ], + [ + 'variable' => 'settings_updated', + 'value' => '0', + ], + [ + 'variable' => 'show_blurb', + 'value' => '1', + ], + [ + 'variable' => 'show_modify', + 'value' => '1', + ], + [ + 'variable' => 'show_profile_buttons', + 'value' => '1', + ], + [ + 'variable' => 'show_user_images', + 'value' => '1', + ], + [ + 'variable' => 'signature_settings', + 'value' => '1,300,0,0,0,0,0,0:', + ], + [ + 'variable' => 'smfVersion', + 'value' => '{$smf_version}', + ], + [ + 'variable' => 'smiley_sets_default', + 'value' => 'fugue', + ], + [ + 'variable' => 'smiley_sets_known', + 'value' => 'fugue,alienine', + ], + [ + 'variable' => 'smiley_sets_names', + 'value' => '{$default_fugue_smileyset_name}\n{$default_alienine_smileyset_name}', + ], + [ + 'variable' => 'smileys_dir', + 'value' => '{$boarddir}/Smileys', + ], + [ + 'variable' => 'smileys_url', + 'value' => '{$boardurl}/Smileys', + ], + [ + 'variable' => 'smtp_host', + 'value' => '', + ], + [ + 'variable' => 'smtp_password', + 'value' => '', + ], + [ + 'variable' => 'smtp_port', + 'value' => '25', + ], + [ + 'variable' => 'smtp_username', + 'value' => '', + ], + [ + 'variable' => 'spamWaitTime', + 'value' => '5', + ], + [ + 'variable' => 'tfa_mode', + 'value' => '1', + ], + [ + 'variable' => 'theme_allow', + 'value' => '1', + ], + [ + 'variable' => 'theme_default', + 'value' => '1', + ], + [ + 'variable' => 'theme_guests', + 'value' => '1', + ], + [ + 'variable' => 'timeLoadPageEnable', + 'value' => '0', + ], + [ + 'variable' => 'time_format', + 'value' => '{$default_time_format}', + ], + [ + 'variable' => 'titlesEnable', + 'value' => '1', + ], + [ + 'variable' => 'todayMod', + 'value' => '1', + ], + [ + 'variable' => 'topicSummaryPosts', + 'value' => '15', + ], + [ + 'variable' => 'topic_move_any', + 'value' => '0', + ], + [ + 'variable' => 'totalMembers', + 'value' => '0', + ], + [ + 'variable' => 'totalMessages', + 'value' => '1', + ], + [ + 'variable' => 'totalTopics', + 'value' => '1', + ], + [ + 'variable' => 'trackStats', + 'value' => '1', + ], + [ + 'variable' => 'unapprovedMembers', + 'value' => '0', + ], + [ + 'variable' => 'use_subdirectories_for_attachments', + 'value' => '1', + ], + [ + 'variable' => 'userLanguage', + 'value' => '1', + ], + [ + 'variable' => 'visual_verification_type', + 'value' => '3', + ], + [ + 'variable' => 'warning_moderate', + 'value' => '35', + ], + [ + 'variable' => 'warning_mute', + 'value' => '60', + ], + [ + 'variable' => 'warning_settings', + 'value' => '1,20,0', + ], + [ + 'variable' => 'warning_watch', + 'value' => '10', + ], + [ + 'variable' => 'who_enabled', + 'value' => '1', + ], + [ + 'variable' => 'xmlnews_enable', + 'value' => '1', + ], + [ + 'variable' => 'xmlnews_maxlen', + 'value' => '255', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'settings'; + + $this->columns = [ + 'variable' => new Column( + name: 'variable', + type: 'varchar', + size: 255, + default: '', + ), + 'value' => new Column( + name: 'value', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'variable(30)', + ], + ), + ]; + + parent::__construct(); + } +} + +?> \ No newline at end of file diff --git a/Sources/Db/Schema/v3_0/SmileyFiles.php b/Sources/Db/Schema/v3_0/SmileyFiles.php new file mode 100644 index 00000000000..54cde4c2a3c --- /dev/null +++ b/Sources/Db/Schema/v3_0/SmileyFiles.php @@ -0,0 +1,84 @@ +name = 'smiley_files'; + + $this->columns = [ + 'id_smiley' => new Column( + name: 'id_smiley', + type: 'smallint', + not_null: true, + default: 0, + ), + 'smiley_set' => new Column( + name: 'smiley_set', + type: 'varchar', + size: 48, + not_null: true, + default: '', + ), + 'filename' => new Column( + name: 'filename', + type: 'varchar', + size: 48, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_smiley', + 'smiley_set', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Smileys.php b/Sources/Db/Schema/v3_0/Smileys.php new file mode 100644 index 00000000000..5ca81844449 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Smileys.php @@ -0,0 +1,104 @@ +name = 'smileys'; + + $this->columns = [ + 'id_smiley' => new Column( + name: 'id_smiley', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'code' => new Column( + name: 'code', + type: 'varchar', + size: 30, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'varchar', + size: 80, + not_null: true, + default: '', + ), + 'smiley_row' => new Column( + name: 'smiley_row', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + 'smiley_order' => new Column( + name: 'smiley_order', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'hidden' => new Column( + name: 'hidden', + type: 'tinyint', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_smiley', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Spiders.php b/Sources/Db/Schema/v3_0/Spiders.php new file mode 100644 index 00000000000..34c72d543f5 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Spiders.php @@ -0,0 +1,201 @@ + 'Google', + 'user_agent' => 'googlebot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yahoo!', + 'user_agent' => 'slurp', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing', + 'user_agent' => 'bingbot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (Mobile)', + 'user_agent' => 'Googlebot-Mobile', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (Image)', + 'user_agent' => 'Googlebot-Image', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (AdSense)', + 'user_agent' => 'Mediapartners-Google', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Google (Adwords)', + 'user_agent' => 'AdsBot-Google', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yahoo! (Mobile)', + 'user_agent' => 'YahooSeeker/M1A1-R2D2', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yahoo! (Image)', + 'user_agent' => 'Yahoo-MMCrawler', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (Preview)', + 'user_agent' => 'BingPreview', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (Ads)', + 'user_agent' => 'adidxbot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (MSNBot)', + 'user_agent' => 'msnbot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Bing (Media)', + 'user_agent' => 'msnbot-media', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Cuil', + 'user_agent' => 'twiceler', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Ask', + 'user_agent' => 'Teoma', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Baidu', + 'user_agent' => 'Baiduspider', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Gigablast', + 'user_agent' => 'Gigabot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'InternetArchive', + 'user_agent' => 'ia_archiver-web.archive.org', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Alexa', + 'user_agent' => 'ia_archiver', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Omgili', + 'user_agent' => 'omgilibot', + 'ip_info' => '', + ], + [ + 'spider_name' => 'EntireWeb', + 'user_agent' => 'Speedy Spider', + 'ip_info' => '', + ], + [ + 'spider_name' => 'Yandex', + 'user_agent' => 'yandex', + 'ip_info' => '', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'spiders'; + + $this->columns = [ + 'id_spider' => new Column( + name: 'id_spider', + type: 'smallint', + unsigned: true, + auto: true, + ), + 'spider_name' => new Column( + name: 'spider_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'user_agent' => new Column( + name: 'user_agent', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'ip_info' => new Column( + name: 'ip_info', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_spider', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Subscriptions.php b/Sources/Db/Schema/v3_0/Subscriptions.php new file mode 100644 index 00000000000..1aa4370a605 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Subscriptions.php @@ -0,0 +1,143 @@ +name = 'subscriptions'; + + $this->columns = [ + 'id_subscribe' => new Column( + name: 'id_subscribe', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'name' => new Column( + name: 'name', + type: 'varchar', + size: 60, + not_null: true, + default: '', + ), + 'description' => new Column( + name: 'description', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'cost' => new Column( + name: 'cost', + type: 'text', + not_null: true, + ), + 'length' => new Column( + name: 'length', + type: 'varchar', + size: 6, + not_null: true, + default: '', + ), + 'id_group' => new Column( + name: 'id_group', + type: 'smallint', + not_null: true, + default: 0, + ), + 'add_groups' => new Column( + name: 'add_groups', + type: 'varchar', + size: 40, + not_null: true, + default: '', + ), + 'active' => new Column( + name: 'active', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'repeatable' => new Column( + name: 'repeatable', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'allow_partial' => new Column( + name: 'allow_partial', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'reminder' => new Column( + name: 'reminder', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'email_complete' => new Column( + name: 'email_complete', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_subscribe', + ], + ), + 'idx_active' => new DbIndex( + name: 'idx_active', + columns: [ + 'active', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Themes.php b/Sources/Db/Schema/v3_0/Themes.php new file mode 100644 index 00000000000..bb9244fb18b --- /dev/null +++ b/Sources/Db/Schema/v3_0/Themes.php @@ -0,0 +1,149 @@ + 1, + 'variable' => 'name', + 'value' => '{$default_theme_name}', + ], + [ + 'id_theme' => 1, + 'variable' => 'theme_url', + 'value' => '{$boardurl}/Themes/default', + ], + [ + 'id_theme' => 1, + 'variable' => 'images_url', + 'value' => '{$boardurl}/Themes/default/images', + ], + [ + 'id_theme' => 1, + 'variable' => 'theme_dir', + 'value' => '{$boarddir}/Themes/default', + ], + [ + 'id_theme' => 1, + 'variable' => 'show_latest_member', + 'value' => '1', + ], + [ + 'id_theme' => 1, + 'variable' => 'show_newsfader', + 'value' => '0', + ], + [ + 'id_theme' => 1, + 'variable' => 'number_recent_posts', + 'value' => '0', + ], + [ + 'id_theme' => 1, + 'variable' => 'show_stats_index', + 'value' => '1', + ], + [ + 'id_theme' => 1, + 'variable' => 'newsfader_time', + 'value' => '3000', + ], + [ + 'id_theme' => 1, + 'variable' => 'use_image_buttons', + 'value' => '1', + ], + [ + 'id_theme' => 1, + 'variable' => 'enable_news', + 'value' => '1', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'themes'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + default: 0, + ), + 'id_theme' => new Column( + name: 'id_theme', + type: 'tinyint', + unsigned: true, + default: 1, + ), + 'variable' => new Column( + name: 'variable', + type: 'varchar', + size: 255, + default: '', + ), + 'value' => new Column( + name: 'value', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_theme', + 'id_member', + 'variable(30)', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/Topics.php b/Sources/Db/Schema/v3_0/Topics.php new file mode 100644 index 00000000000..7acf5b439b1 --- /dev/null +++ b/Sources/Db/Schema/v3_0/Topics.php @@ -0,0 +1,242 @@ + 1, + 'id_board' => 1, + 'id_first_msg' => 1, + 'id_last_msg' => 1, + 'id_member_started' => 0, + 'id_member_updated' => 0, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'topics'; + + $this->columns = [ + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + auto: true, + ), + 'is_sticky' => new Column( + name: 'is_sticky', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_first_msg' => new Column( + name: 'id_first_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_last_msg' => new Column( + name: 'id_last_msg', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_started' => new Column( + name: 'id_member_started', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_updated' => new Column( + name: 'id_member_updated', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_poll' => new Column( + name: 'id_poll', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_previous_board' => new Column( + name: 'id_previous_board', + type: 'smallint', + not_null: true, + default: 0, + ), + 'id_previous_topic' => new Column( + name: 'id_previous_topic', + type: 'mediumint', + not_null: true, + default: 0, + ), + 'num_replies' => new Column( + name: 'num_replies', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'num_views' => new Column( + name: 'num_views', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'locked' => new Column( + name: 'locked', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'redirect_expires' => new Column( + name: 'redirect_expires', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_redirect_topic' => new Column( + name: 'id_redirect_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'unapproved_posts' => new Column( + name: 'unapproved_posts', + type: 'smallint', + not_null: true, + default: 0, + ), + 'approved' => new Column( + name: 'approved', + type: 'tinyint', + not_null: true, + default: 1, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_topic', + ], + ), + 'idx_last_message' => new DbIndex( + name: 'idx_last_message', + type: 'unique', + columns: [ + 'id_last_msg', + 'id_board', + ], + ), + 'idx_first_message' => new DbIndex( + name: 'idx_first_message', + type: 'unique', + columns: [ + 'id_first_msg', + 'id_board', + ], + ), + 'idx_poll' => new DbIndex( + name: 'idx_poll', + type: 'unique', + columns: [ + 'id_poll', + 'id_topic', + ], + ), + 'idx_is_sticky' => new DbIndex( + name: 'idx_is_sticky', + columns: [ + 'is_sticky', + ], + ), + 'idx_approved' => new DbIndex( + name: 'idx_approved', + columns: [ + 'approved', + ], + ), + 'idx_member_started' => new DbIndex( + name: 'idx_member_started', + columns: [ + 'id_member_started', + 'id_board', + ], + ), + 'idx_last_message_sticky' => new DbIndex( + name: 'idx_last_message_sticky', + columns: [ + 'id_board', + 'is_sticky', + 'id_last_msg', + ], + ), + 'idx_board_news' => new DbIndex( + name: 'idx_board_news', + columns: [ + 'id_board', + 'id_first_msg', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/UserAlerts.php b/Sources/Db/Schema/v3_0/UserAlerts.php new file mode 100644 index 00000000000..2ad5d131ddf --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserAlerts.php @@ -0,0 +1,142 @@ +name = 'user_alerts'; + + $this->columns = [ + 'id_alert' => new Column( + name: 'id_alert', + type: 'int', + unsigned: true, + auto: true, + ), + 'alert_time' => new Column( + name: 'alert_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member_started' => new Column( + name: 'id_member_started', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'member_name' => new Column( + name: 'member_name', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'content_type' => new Column( + name: 'content_type', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'content_id' => new Column( + name: 'content_id', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'content_action' => new Column( + name: 'content_action', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'is_read' => new Column( + name: 'is_read', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'extra' => new Column( + name: 'extra', + type: 'text', + not_null: true, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_alert', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + columns: [ + 'id_member', + ], + ), + 'idx_alert_time' => new DbIndex( + name: 'idx_alert_time', + columns: [ + 'alert_time', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/UserAlertsPrefs.php b/Sources/Db/Schema/v3_0/UserAlertsPrefs.php new file mode 100644 index 00000000000..6e6ea3134e5 --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserAlertsPrefs.php @@ -0,0 +1,228 @@ + 0, + 'alert_pref' => 'alert_timeout', + 'alert_value' => 10, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'announcements', + 'alert_value' => 0, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'birthday', + 'alert_value' => 2, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'board_notify', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'buddy_request', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'groupr_approved', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'groupr_rejected', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_group_request', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_register', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_report', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'member_report_reply', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_auto_notify', + 'alert_value' => 0, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_like', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_mention', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_notify_pref', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_notify_type', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_quote', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_receive_body', + 'alert_value' => 0, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_report', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'msg_report_reply', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'pm_new', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'pm_notify', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'pm_reply', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'request_group', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'topic_notify', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'unapproved_attachment', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'unapproved_reply', + 'alert_value' => 3, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'unapproved_post', + 'alert_value' => 1, + ], + [ + 'id_member' => 0, + 'alert_pref' => 'warn_any', + 'alert_value' => 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + $this->name = 'user_alerts_prefs'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'alert_pref' => new Column( + name: 'alert_pref', + type: 'varchar', + size: 32, + default: '', + ), + 'alert_value' => new Column( + name: 'alert_value', + type: 'tinyint', + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_member', + 'alert_pref', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/UserDrafts.php b/Sources/Db/Schema/v3_0/UserDrafts.php new file mode 100644 index 00000000000..6041b4fdf80 --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserDrafts.php @@ -0,0 +1,163 @@ +name = 'user_drafts'; + + $this->columns = [ + 'id_draft' => new Column( + name: 'id_draft', + type: 'int', + unsigned: true, + auto: true, + ), + 'id_topic' => new Column( + name: 'id_topic', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_board' => new Column( + name: 'id_board', + type: 'smallint', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_reply' => new Column( + name: 'id_reply', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'type' => new Column( + name: 'type', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'poster_time' => new Column( + name: 'poster_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + not_null: true, + default: 0, + ), + 'subject' => new Column( + name: 'subject', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + 'smileys_enabled' => new Column( + name: 'smileys_enabled', + type: 'tinyint', + not_null: true, + default: 1, + ), + 'body' => new Column( + name: 'body', + type: 'mediumtext', + not_null: true, + ), + 'icon' => new Column( + name: 'icon', + type: 'varchar', + size: 16, + not_null: true, + default: 'xx', + ), + 'locked' => new Column( + name: 'locked', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'is_sticky' => new Column( + name: 'is_sticky', + type: 'tinyint', + not_null: true, + default: 0, + ), + 'to_list' => new Column( + name: 'to_list', + type: 'varchar', + size: 255, + not_null: true, + default: '', + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'id_draft', + ], + ), + 'idx_id_member' => new DbIndex( + name: 'idx_id_member', + type: 'unique', + columns: [ + 'id_member', + 'id_draft', + 'type', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/UserLikes.php b/Sources/Db/Schema/v3_0/UserLikes.php new file mode 100644 index 00000000000..a0097f54369 --- /dev/null +++ b/Sources/Db/Schema/v3_0/UserLikes.php @@ -0,0 +1,103 @@ +name = 'user_likes'; + + $this->columns = [ + 'id_member' => new Column( + name: 'id_member', + type: 'mediumint', + unsigned: true, + default: 0, + ), + 'content_type' => new Column( + name: 'content_type', + type: 'char', + size: 6, + default: '', + ), + 'content_id' => new Column( + name: 'content_id', + type: 'int', + unsigned: true, + default: 0, + ), + 'like_time' => new Column( + name: 'like_time', + type: 'int', + unsigned: true, + not_null: true, + default: 0, + ), + ]; + + $this->indexes = [ + 'primary' => new DbIndex( + type: 'primary', + columns: [ + 'content_id', + 'content_type', + 'id_member', + ], + ), + 'content' => new DbIndex( + name: 'content', + columns: [ + 'content_id', + 'content_type', + ], + ), + 'liker' => new DbIndex( + name: 'liker', + columns: [ + 'id_member', + ], + ), + ]; + + parent::__construct(); + } +} diff --git a/Sources/Db/Schema/v3_0/index.php b/Sources/Db/Schema/v3_0/index.php new file mode 100644 index 00000000000..cc9dd085708 --- /dev/null +++ b/Sources/Db/Schema/v3_0/index.php @@ -0,0 +1,8 @@ + Date: Fri, 25 Apr 2025 14:49:30 -0600 Subject: [PATCH 08/90] Adds new methods to DatabaseApiInterface Signed-off-by: Jon Stovell --- Sources/Db/APIs/MySQL.php | 224 +++++++++++++++++- Sources/Db/APIs/PostgreSQL.php | 168 ++++++++++++- Sources/Db/DatabaseApi.php | 80 ++++--- Sources/Db/DatabaseApiInterface.php | 107 +++++++++ Sources/Db/Schema/Column.php | 2 +- Sources/Db/Schema/DbIndex.php | 2 +- Sources/Db/Schema/Table.php | 6 +- Sources/Db/Schema/v3_0/AdminInfoFiles.php | 8 +- Sources/Db/Schema/v3_0/ApprovalQueue.php | 6 +- Sources/Db/Schema/v3_0/Attachments.php | 8 +- Sources/Db/Schema/v3_0/BackgroundTasks.php | 8 +- Sources/Db/Schema/v3_0/BanGroups.php | 8 +- Sources/Db/Schema/v3_0/BanItems.php | 8 +- Sources/Db/Schema/v3_0/BoardPermissions.php | 8 +- .../Db/Schema/v3_0/BoardPermissionsView.php | 8 +- Sources/Db/Schema/v3_0/Boards.php | 8 +- Sources/Db/Schema/v3_0/Calendar.php | 11 +- Sources/Db/Schema/v3_0/Categories.php | 8 +- Sources/Db/Schema/v3_0/CustomFields.php | 8 +- Sources/Db/Schema/v3_0/GroupModerators.php | 8 +- Sources/Db/Schema/v3_0/LogActions.php | 8 +- Sources/Db/Schema/v3_0/LogActivity.php | 8 +- Sources/Db/Schema/v3_0/LogBanned.php | 8 +- Sources/Db/Schema/v3_0/LogBoards.php | 8 +- Sources/Db/Schema/v3_0/LogComments.php | 8 +- Sources/Db/Schema/v3_0/LogDigest.php | 6 +- Sources/Db/Schema/v3_0/LogErrors.php | 8 +- Sources/Db/Schema/v3_0/LogFloodcontrol.php | 8 +- Sources/Db/Schema/v3_0/LogGroupRequests.php | 8 +- Sources/Db/Schema/v3_0/LogMarkRead.php | 8 +- Sources/Db/Schema/v3_0/LogMemberNotices.php | 8 +- Sources/Db/Schema/v3_0/LogNotify.php | 8 +- Sources/Db/Schema/v3_0/LogOnline.php | 8 +- Sources/Db/Schema/v3_0/LogPackages.php | 18 +- Sources/Db/Schema/v3_0/LogPolls.php | 8 +- Sources/Db/Schema/v3_0/LogReported.php | 8 +- .../Db/Schema/v3_0/LogReportedComments.php | 8 +- Sources/Db/Schema/v3_0/LogScheduledTasks.php | 8 +- Sources/Db/Schema/v3_0/LogSearchMessages.php | 8 +- Sources/Db/Schema/v3_0/LogSearchResults.php | 8 +- Sources/Db/Schema/v3_0/LogSearchSubjects.php | 8 +- Sources/Db/Schema/v3_0/LogSearchTopics.php | 8 +- Sources/Db/Schema/v3_0/LogSpiderHits.php | 8 +- Sources/Db/Schema/v3_0/LogSpiderStats.php | 8 +- Sources/Db/Schema/v3_0/LogSubscribed.php | 8 +- Sources/Db/Schema/v3_0/LogTopics.php | 8 +- Sources/Db/Schema/v3_0/MailQueue.php | 8 +- Sources/Db/Schema/v3_0/MemberLogins.php | 8 +- Sources/Db/Schema/v3_0/Membergroups.php | 8 +- Sources/Db/Schema/v3_0/Members.php | 8 +- Sources/Db/Schema/v3_0/Mentions.php | 8 +- Sources/Db/Schema/v3_0/MessageIcons.php | 8 +- Sources/Db/Schema/v3_0/Messages.php | 8 +- Sources/Db/Schema/v3_0/ModeratorGroups.php | 8 +- Sources/Db/Schema/v3_0/Moderators.php | 8 +- Sources/Db/Schema/v3_0/PackageServers.php | 8 +- Sources/Db/Schema/v3_0/PermissionProfiles.php | 8 +- Sources/Db/Schema/v3_0/Permissions.php | 16 +- Sources/Db/Schema/v3_0/PersonalMessages.php | 8 +- Sources/Db/Schema/v3_0/PmLabeledMessages.php | 8 +- Sources/Db/Schema/v3_0/PmLabels.php | 8 +- Sources/Db/Schema/v3_0/PmRecipients.php | 8 +- Sources/Db/Schema/v3_0/PmRules.php | 8 +- Sources/Db/Schema/v3_0/PollChoices.php | 8 +- Sources/Db/Schema/v3_0/Polls.php | 8 +- Sources/Db/Schema/v3_0/Qanda.php | 8 +- Sources/Db/Schema/v3_0/ScheduledTasks.php | 8 +- Sources/Db/Schema/v3_0/Sessions.php | 8 +- Sources/Db/Schema/v3_0/Settings.php | 10 +- Sources/Db/Schema/v3_0/SmileyFiles.php | 8 +- Sources/Db/Schema/v3_0/Smileys.php | 8 +- Sources/Db/Schema/v3_0/Spiders.php | 8 +- Sources/Db/Schema/v3_0/Subscriptions.php | 8 +- Sources/Db/Schema/v3_0/Themes.php | 8 +- Sources/Db/Schema/v3_0/Topics.php | 8 +- Sources/Db/Schema/v3_0/UserAlerts.php | 8 +- Sources/Db/Schema/v3_0/UserAlertsPrefs.php | 8 +- Sources/Db/Schema/v3_0/UserDrafts.php | 8 +- Sources/Db/Schema/v3_0/UserLikes.php | 8 +- 79 files changed, 823 insertions(+), 361 deletions(-) diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index 5223a838fbc..767cf9724a7 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -1724,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; } @@ -2144,20 +2144,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']); @@ -2168,7 +2378,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. @@ -2177,7 +2387,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. @@ -2231,7 +2441,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; diff --git a/Sources/Db/APIs/PostgreSQL.php b/Sources/Db/APIs/PostgreSQL.php index aa3b7e6989b..4b721dec982 100644 --- a/Sources/Db/APIs/PostgreSQL.php +++ b/Sources/Db/APIs/PostgreSQL.php @@ -1724,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; } @@ -2185,20 +2185,174 @@ public function remove_index(string $table_name, string $index_name, array $para return false; } + /************************************** + * Methods used during installion, etc. + **************************************/ + + /** + * + */ + public function getMinimumVersion(): string + { + return '12.17'; + } + + /** + * + */ + public function isSupported(): bool + { + return function_exists('pg_connect'); + } + + /** + * + */ + public function skipSelectDatabase(): bool + { + return true; + } + + /** + * + */ + public function getDefaultUser(): string + { + return ''; + } + + /** + * + */ + public function getDefaultPassword(): string + { + return ''; + } + + /** + * + */ + public function getDefaultHost(): string + { + return ''; + } + + /** + * + */ + public function getDefaultPort(): int + { + return 5432; + } + + /** + * + */ + public function getDefaultName(): string + { + return 'smf'; + } + + public function checkConfiguration(): bool + { + $result = Db::$db->query( + '', + 'show standard_conforming_strings', + [ + 'db_error_skip' => true, + ], + ); + + if ($result !== false) { + $row = Db::$db->fetch_assoc($result); + + if ($row['standard_conforming_strings'] !== 'on') { + throw new \Exception(Lang::$txt['error_pg_scs']); + } + Db::$db->free_result($result); + } + + return true; + } + + /** + * + */ + public function hasPermissions(): bool + { + return true; + } + + /** + * + */ + public function validatePrefix(&$value): bool + { + $value = preg_replace('~[^A-Za-z0-9_\$]~', '', $value); + + // Is it reserved? + if ($value == 'pg_') { + throw new \Exception(Lang::getTxt('error_db_prefix_reserved', file: 'Maintenance')); + } + + // Is the prefix numeric? + if (preg_match('~^\d~', $value)) { + throw new \Exception(Lang::getTxt('error_db_prefix_numeric', file: 'Maintenance')); + } + + return true; + } + + /** + * + */ + public function alwaysHasDb(): bool + { + return true; + } + + /** + * + */ + public function setSqlMode(string $mode = 'default'): bool + { + return true; + } + + /** + * + */ + public function processError(string $error_msg, string $query): mixed + { + 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']); @@ -2209,7 +2363,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. @@ -2218,7 +2372,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. @@ -2258,7 +2412,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 { // We are not going to make it very far without this. if (!function_exists('pg_pconnect')) { diff --git a/Sources/Db/DatabaseApi.php b/Sources/Db/DatabaseApi.php index 7f19a295a9d..a211879473b 100644 --- a/Sources/Db/DatabaseApi.php +++ b/Sources/Db/DatabaseApi.php @@ -312,6 +312,42 @@ abstract class DatabaseApi * Public methods ****************/ + /** + * Protected constructor to prevent multiple instances. + */ + public function __construct() + { + if (!isset($this->server)) { + $this->server = (string) Config::$db_server; + } + + if (!isset($this->name)) { + $this->name = (string) Config::$db_name; + } + + if (!isset($this->prefix)) { + $this->prefix = (string) Config::$db_prefix; + } + + if (!isset($this->port)) { + $this->port = !empty(Config::$db_port) ? (int) Config::$db_port : 0; + } + + if (!isset($this->persist)) { + $this->persist = !empty(Config::$db_persist); + } + + if (!isset($this->show_debug)) { + $this->show_debug = !empty(Config::$db_show_debug); + } + + if (!isset($this->disableQueryCheck)) { + $this->disableQueryCheck = !empty(Config::$modSettings['disableQueryCheck']); + } + + $this->prefixReservedTables(); + } + /** * Figures out the best type indicators to use in SMF's query placeholder * strings and/or insert column type definitions for a given set of columns. @@ -449,6 +485,11 @@ final public static function load(array $options = []): DatabaseApi ErrorHandler::displayDbError(); } + self::$db->initialize($options); + + // For backward compatibility. + self::$db->mapToSmcFunc(); + return self::$db; } @@ -489,45 +530,6 @@ public static function getClass(string $db_type): string * Internal methods ******************/ - /** - * Protected constructor to prevent multiple instances. - */ - protected function __construct() - { - if (!isset($this->server)) { - $this->server = (string) Config::$db_server; - } - - if (!isset($this->name)) { - $this->name = (string) Config::$db_name; - } - - if (!isset($this->prefix)) { - $this->prefix = (string) Config::$db_prefix; - } - - if (!isset($this->port)) { - $this->port = !empty(Config::$db_port) ? (int) Config::$db_port : 0; - } - - if (!isset($this->persist)) { - $this->persist = !empty(Config::$db_persist); - } - - if (!isset($this->show_debug)) { - $this->show_debug = !empty(Config::$db_show_debug); - } - - if (!isset($this->disableQueryCheck)) { - $this->disableQueryCheck = !empty(Config::$modSettings['disableQueryCheck']); - } - - $this->prefixReservedTables(); - - // For backward compatibility. - $this->mapToSmcFunc(); - } - /** * Appends the correct prefix to the reserved tables' names. */ diff --git a/Sources/Db/DatabaseApiInterface.php b/Sources/Db/DatabaseApiInterface.php index e6ca10fc170..bbeecebd766 100644 --- a/Sources/Db/DatabaseApiInterface.php +++ b/Sources/Db/DatabaseApiInterface.php @@ -580,4 +580,111 @@ public function remove_column(string $table_name, string $column_name, array $pa * @return bool Whether or not the operation was successful */ public function remove_index(string $table_name, string $index_name, array $parameters = [], string $error = 'fatal'): bool; + + /** + * The minimum version that SMF supports for the database. + * + * @return string + */ + public function getMinimumVersion(): string; + + /** + * Is this database supported. + * + * @return bool True if we can use this database, false otherwise. + */ + public function isSupported(): bool; + + /** + * Skip issuing a select database command. + * + * @return bool When true, we do not select a database. + */ + public function skipSelectDatabase(): bool; + + /** + * Default username for a database connection. + * + * @return string + */ + public function getDefaultUser(): string; + + /** + * Default password for a database connection. + * + * @return string + */ + public function getDefaultPassword(): string; + + /** + * Default host for a database connection. + * + * @return string + */ + public function getDefaultHost(): string; + + /** + * Default port for a database connection. + * + * @return int + */ + public function getDefaultPort(): int; + + /** + * Default database name for a database connection. + * + * @return string + */ + public function getDefaultName(): string; + + /** + * Performs checks to ensure the server is in a sane configuration. + * + * @return bool + */ + public function checkConfiguration(): bool; + + /** + * Performs checks to ensure we have proper permissions to the database + * in order to perform operations. + * + * @return bool + */ + public function hasPermissions(): bool; + + /** + * Validate a database prefix. + * When an error occurs, use throw new exception, this will be captured. + * + * @return bool + */ + public function validatePrefix(&$string): bool; + + /** + * Returns whether it is necessary to select the database by name or not. + * + * @return bool False if we must select the database, true if not. + */ + public function alwaysHasDb(): bool; + + /** + * Perform additional changes to our SQL connection in order to perform + * commands that are not strict SQL. + * + * @param string $mode The SQL mode we wish to be in, either 'default' or 'strict'. + * @return bool + */ + public function setSqlMode(string $mode = 'default'): bool; + + /** + * When an error occurs with a query run through a wrapper, we send errors here. + * + * @param string $error_msg as returend by the database interfaces call. + * @param string $query Query we ran + * @return mixed + * False if we should not do anything, + * True if we should stop for error. + * Result from a query can also be returned, if we are able to correct the query. + */ + public function processError(string $error_msg, string $query): mixed; } diff --git a/Sources/Db/Schema/Column.php b/Sources/Db/Schema/Column.php index 277359944a2..6652acb148c 100644 --- a/Sources/Db/Schema/Column.php +++ b/Sources/Db/Schema/Column.php @@ -13,7 +13,7 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema; +namespace SMF\Db\Schema; use SMF\Config; diff --git a/Sources/Db/Schema/DbIndex.php b/Sources/Db/Schema/DbIndex.php index 9cd88364cf5..16b133ff40f 100644 --- a/Sources/Db/Schema/DbIndex.php +++ b/Sources/Db/Schema/DbIndex.php @@ -13,7 +13,7 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema; +namespace SMF\Db\Schema; /** * Represents an index in a database table. diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index cfc7b8475c6..6426f80a3f2 100644 --- a/Sources/Db/Schema/Table.php +++ b/Sources/Db/Schema/Table.php @@ -13,7 +13,7 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema; +namespace SMF\Db\Schema; use SMF\Db\DatabaseApi as Db; @@ -36,14 +36,14 @@ class Table /** * @var array * - * An array of SMF\Maintenance\Database\Schema\Column objects. + * An array of SMF\Db\Schema\Column objects. */ public array $columns; /** * @var array * - * An array of SMF\Maintenance\Database\Schema\DbIndex objects. + * An array of SMF\Db\Schema\DbIndex objects. */ public array $indexes = []; diff --git a/Sources/Db/Schema/v3_0/AdminInfoFiles.php b/Sources/Db/Schema/v3_0/AdminInfoFiles.php index c6b6e37dba8..4bbba454a95 100644 --- a/Sources/Db/Schema/v3_0/AdminInfoFiles.php +++ b/Sources/Db/Schema/v3_0/AdminInfoFiles.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/ApprovalQueue.php b/Sources/Db/Schema/v3_0/ApprovalQueue.php index 370c1d0a9c5..a9ac5dea733 100644 --- a/Sources/Db/Schema/v3_0/ApprovalQueue.php +++ b/Sources/Db/Schema/v3_0/ApprovalQueue.php @@ -13,10 +13,10 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Attachments.php b/Sources/Db/Schema/v3_0/Attachments.php index d63ba2a709a..4d9681a96e8 100644 --- a/Sources/Db/Schema/v3_0/Attachments.php +++ b/Sources/Db/Schema/v3_0/Attachments.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/BackgroundTasks.php b/Sources/Db/Schema/v3_0/BackgroundTasks.php index d9fb9a2c24c..30ecd8e035c 100644 --- a/Sources/Db/Schema/v3_0/BackgroundTasks.php +++ b/Sources/Db/Schema/v3_0/BackgroundTasks.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/BanGroups.php b/Sources/Db/Schema/v3_0/BanGroups.php index 38df3fb06bd..7eac2fdb18a 100644 --- a/Sources/Db/Schema/v3_0/BanGroups.php +++ b/Sources/Db/Schema/v3_0/BanGroups.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/BanItems.php b/Sources/Db/Schema/v3_0/BanItems.php index 61f6fb7763b..566a8c3444b 100644 --- a/Sources/Db/Schema/v3_0/BanItems.php +++ b/Sources/Db/Schema/v3_0/BanItems.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/BoardPermissions.php b/Sources/Db/Schema/v3_0/BoardPermissions.php index 0f1751ccc7b..eec1735fa61 100644 --- a/Sources/Db/Schema/v3_0/BoardPermissions.php +++ b/Sources/Db/Schema/v3_0/BoardPermissions.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/BoardPermissionsView.php b/Sources/Db/Schema/v3_0/BoardPermissionsView.php index f493e78cdf0..b4b35213537 100644 --- a/Sources/Db/Schema/v3_0/BoardPermissionsView.php +++ b/Sources/Db/Schema/v3_0/BoardPermissionsView.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Boards.php b/Sources/Db/Schema/v3_0/Boards.php index cda3e454e02..93d0a61ee40 100644 --- a/Sources/Db/Schema/v3_0/Boards.php +++ b/Sources/Db/Schema/v3_0/Boards.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Calendar.php b/Sources/Db/Schema/v3_0/Calendar.php index 7a3b4dfecc0..2a20f588d57 100644 --- a/Sources/Db/Schema/v3_0/Calendar.php +++ b/Sources/Db/Schema/v3_0/Calendar.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. @@ -521,7 +521,8 @@ public function __construct() 'adjustments' => new Column( name: 'adjustments', type: 'json', - not_null: true, + not_null: false, + default: null, ), 'sequence' => new Column( name: 'sequence', diff --git a/Sources/Db/Schema/v3_0/Categories.php b/Sources/Db/Schema/v3_0/Categories.php index 75e855afc25..d7a24c240c5 100644 --- a/Sources/Db/Schema/v3_0/Categories.php +++ b/Sources/Db/Schema/v3_0/Categories.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/CustomFields.php b/Sources/Db/Schema/v3_0/CustomFields.php index 584ea3cd7f0..a22c3d867b4 100644 --- a/Sources/Db/Schema/v3_0/CustomFields.php +++ b/Sources/Db/Schema/v3_0/CustomFields.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/GroupModerators.php b/Sources/Db/Schema/v3_0/GroupModerators.php index 8af3c5d880e..9b015616c38 100644 --- a/Sources/Db/Schema/v3_0/GroupModerators.php +++ b/Sources/Db/Schema/v3_0/GroupModerators.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogActions.php b/Sources/Db/Schema/v3_0/LogActions.php index bd3e7359935..7e1619e1c70 100644 --- a/Sources/Db/Schema/v3_0/LogActions.php +++ b/Sources/Db/Schema/v3_0/LogActions.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogActivity.php b/Sources/Db/Schema/v3_0/LogActivity.php index b9f0b737789..8a28a67c0da 100644 --- a/Sources/Db/Schema/v3_0/LogActivity.php +++ b/Sources/Db/Schema/v3_0/LogActivity.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogBanned.php b/Sources/Db/Schema/v3_0/LogBanned.php index b526c1f7c74..fd1ca9ef854 100644 --- a/Sources/Db/Schema/v3_0/LogBanned.php +++ b/Sources/Db/Schema/v3_0/LogBanned.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogBoards.php b/Sources/Db/Schema/v3_0/LogBoards.php index e852f4e64c4..166e31de697 100644 --- a/Sources/Db/Schema/v3_0/LogBoards.php +++ b/Sources/Db/Schema/v3_0/LogBoards.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogComments.php b/Sources/Db/Schema/v3_0/LogComments.php index 3e227669a31..cb306d308af 100644 --- a/Sources/Db/Schema/v3_0/LogComments.php +++ b/Sources/Db/Schema/v3_0/LogComments.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogDigest.php b/Sources/Db/Schema/v3_0/LogDigest.php index 5d04e1e90e2..7f1a6a935f2 100644 --- a/Sources/Db/Schema/v3_0/LogDigest.php +++ b/Sources/Db/Schema/v3_0/LogDigest.php @@ -13,10 +13,10 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogErrors.php b/Sources/Db/Schema/v3_0/LogErrors.php index 8744aed1158..1efafc6d7bd 100644 --- a/Sources/Db/Schema/v3_0/LogErrors.php +++ b/Sources/Db/Schema/v3_0/LogErrors.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogFloodcontrol.php b/Sources/Db/Schema/v3_0/LogFloodcontrol.php index d4a16aea6ee..0a5a0c9cfdf 100644 --- a/Sources/Db/Schema/v3_0/LogFloodcontrol.php +++ b/Sources/Db/Schema/v3_0/LogFloodcontrol.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogGroupRequests.php b/Sources/Db/Schema/v3_0/LogGroupRequests.php index 2338020be96..51e42550bde 100644 --- a/Sources/Db/Schema/v3_0/LogGroupRequests.php +++ b/Sources/Db/Schema/v3_0/LogGroupRequests.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogMarkRead.php b/Sources/Db/Schema/v3_0/LogMarkRead.php index f8fa59b4c13..545044d750d 100644 --- a/Sources/Db/Schema/v3_0/LogMarkRead.php +++ b/Sources/Db/Schema/v3_0/LogMarkRead.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogMemberNotices.php b/Sources/Db/Schema/v3_0/LogMemberNotices.php index 2ceaf8bf31c..5ca8a37dff8 100644 --- a/Sources/Db/Schema/v3_0/LogMemberNotices.php +++ b/Sources/Db/Schema/v3_0/LogMemberNotices.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogNotify.php b/Sources/Db/Schema/v3_0/LogNotify.php index 03e245d648a..6766038f8e0 100644 --- a/Sources/Db/Schema/v3_0/LogNotify.php +++ b/Sources/Db/Schema/v3_0/LogNotify.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogOnline.php b/Sources/Db/Schema/v3_0/LogOnline.php index 73fdcfac09b..8211e80ed5a 100644 --- a/Sources/Db/Schema/v3_0/LogOnline.php +++ b/Sources/Db/Schema/v3_0/LogOnline.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogPackages.php b/Sources/Db/Schema/v3_0/LogPackages.php index f71141a676f..58f4dac893e 100644 --- a/Sources/Db/Schema/v3_0/LogPackages.php +++ b/Sources/Db/Schema/v3_0/LogPackages.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. @@ -129,7 +129,6 @@ public function __construct() name: 'failed_steps', type: 'text', not_null: true, - default: false, ), 'themes_installed' => new Column( name: 'themes_installed', @@ -142,7 +141,6 @@ public function __construct() name: 'db_changes', type: 'text', not_null: true, - default: false, ), 'credits' => new Column( name: 'credits', @@ -173,16 +171,16 @@ public function __construct() 'id_install', ], ), - 'filename' => new DbIndex( + 'idx_filename' => new DbIndex( name: 'filename', columns: [ 'filename', ], ), - 'id_hash' => new DbIndex( - name: 'id_hash', + 'idx_hash' => new DbIndex( + name: 'idx_hash', columns: [ - 'id_hash', + 'sha256_hash', ], ), ]; diff --git a/Sources/Db/Schema/v3_0/LogPolls.php b/Sources/Db/Schema/v3_0/LogPolls.php index 951ee22c86b..0dcf77983a9 100644 --- a/Sources/Db/Schema/v3_0/LogPolls.php +++ b/Sources/Db/Schema/v3_0/LogPolls.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogReported.php b/Sources/Db/Schema/v3_0/LogReported.php index 3a83b5975e5..b8c75d36ec8 100644 --- a/Sources/Db/Schema/v3_0/LogReported.php +++ b/Sources/Db/Schema/v3_0/LogReported.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogReportedComments.php b/Sources/Db/Schema/v3_0/LogReportedComments.php index f9a6567e86f..c04eb8d8be2 100644 --- a/Sources/Db/Schema/v3_0/LogReportedComments.php +++ b/Sources/Db/Schema/v3_0/LogReportedComments.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogScheduledTasks.php b/Sources/Db/Schema/v3_0/LogScheduledTasks.php index dccfe7101ed..bdb88e4ad10 100644 --- a/Sources/Db/Schema/v3_0/LogScheduledTasks.php +++ b/Sources/Db/Schema/v3_0/LogScheduledTasks.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogSearchMessages.php b/Sources/Db/Schema/v3_0/LogSearchMessages.php index 58011f3da62..df9f9487e43 100644 --- a/Sources/Db/Schema/v3_0/LogSearchMessages.php +++ b/Sources/Db/Schema/v3_0/LogSearchMessages.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogSearchResults.php b/Sources/Db/Schema/v3_0/LogSearchResults.php index 9d8123aabb5..a652de6b384 100644 --- a/Sources/Db/Schema/v3_0/LogSearchResults.php +++ b/Sources/Db/Schema/v3_0/LogSearchResults.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogSearchSubjects.php b/Sources/Db/Schema/v3_0/LogSearchSubjects.php index fbdd98a30f1..210c29f09b0 100644 --- a/Sources/Db/Schema/v3_0/LogSearchSubjects.php +++ b/Sources/Db/Schema/v3_0/LogSearchSubjects.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogSearchTopics.php b/Sources/Db/Schema/v3_0/LogSearchTopics.php index f76a6ea4b33..c889302b90c 100644 --- a/Sources/Db/Schema/v3_0/LogSearchTopics.php +++ b/Sources/Db/Schema/v3_0/LogSearchTopics.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogSpiderHits.php b/Sources/Db/Schema/v3_0/LogSpiderHits.php index 421e8907647..85c26f0e65d 100644 --- a/Sources/Db/Schema/v3_0/LogSpiderHits.php +++ b/Sources/Db/Schema/v3_0/LogSpiderHits.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogSpiderStats.php b/Sources/Db/Schema/v3_0/LogSpiderStats.php index e29515e7b4e..ef14dfeb122 100644 --- a/Sources/Db/Schema/v3_0/LogSpiderStats.php +++ b/Sources/Db/Schema/v3_0/LogSpiderStats.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogSubscribed.php b/Sources/Db/Schema/v3_0/LogSubscribed.php index db752aac163..1a7eb481500 100644 --- a/Sources/Db/Schema/v3_0/LogSubscribed.php +++ b/Sources/Db/Schema/v3_0/LogSubscribed.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/LogTopics.php b/Sources/Db/Schema/v3_0/LogTopics.php index fc3a13c258a..3da3b93d899 100644 --- a/Sources/Db/Schema/v3_0/LogTopics.php +++ b/Sources/Db/Schema/v3_0/LogTopics.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/MailQueue.php b/Sources/Db/Schema/v3_0/MailQueue.php index 432ce1a5431..2b3c27170f1 100644 --- a/Sources/Db/Schema/v3_0/MailQueue.php +++ b/Sources/Db/Schema/v3_0/MailQueue.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/MemberLogins.php b/Sources/Db/Schema/v3_0/MemberLogins.php index 637b191ca81..af9ca3957ce 100644 --- a/Sources/Db/Schema/v3_0/MemberLogins.php +++ b/Sources/Db/Schema/v3_0/MemberLogins.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Membergroups.php b/Sources/Db/Schema/v3_0/Membergroups.php index a7f4c790b92..1af348fd34f 100644 --- a/Sources/Db/Schema/v3_0/Membergroups.php +++ b/Sources/Db/Schema/v3_0/Membergroups.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Members.php b/Sources/Db/Schema/v3_0/Members.php index 619ef35c745..973f7fbcd87 100644 --- a/Sources/Db/Schema/v3_0/Members.php +++ b/Sources/Db/Schema/v3_0/Members.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Mentions.php b/Sources/Db/Schema/v3_0/Mentions.php index 5fe9e21db3a..54df5dfd099 100644 --- a/Sources/Db/Schema/v3_0/Mentions.php +++ b/Sources/Db/Schema/v3_0/Mentions.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/MessageIcons.php b/Sources/Db/Schema/v3_0/MessageIcons.php index c7c127a8484..a98f6b96dc4 100644 --- a/Sources/Db/Schema/v3_0/MessageIcons.php +++ b/Sources/Db/Schema/v3_0/MessageIcons.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Messages.php b/Sources/Db/Schema/v3_0/Messages.php index a86685d9c33..6fe9254d034 100644 --- a/Sources/Db/Schema/v3_0/Messages.php +++ b/Sources/Db/Schema/v3_0/Messages.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/ModeratorGroups.php b/Sources/Db/Schema/v3_0/ModeratorGroups.php index 56f84dc6443..cdc5d2d3b20 100644 --- a/Sources/Db/Schema/v3_0/ModeratorGroups.php +++ b/Sources/Db/Schema/v3_0/ModeratorGroups.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Moderators.php b/Sources/Db/Schema/v3_0/Moderators.php index 0807d170d82..d40dd470abe 100644 --- a/Sources/Db/Schema/v3_0/Moderators.php +++ b/Sources/Db/Schema/v3_0/Moderators.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/PackageServers.php b/Sources/Db/Schema/v3_0/PackageServers.php index 2a8297fe4e8..3221e1d3107 100644 --- a/Sources/Db/Schema/v3_0/PackageServers.php +++ b/Sources/Db/Schema/v3_0/PackageServers.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/PermissionProfiles.php b/Sources/Db/Schema/v3_0/PermissionProfiles.php index fa15debda59..67556e7bc6a 100644 --- a/Sources/Db/Schema/v3_0/PermissionProfiles.php +++ b/Sources/Db/Schema/v3_0/PermissionProfiles.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Permissions.php b/Sources/Db/Schema/v3_0/Permissions.php index a5f9d892619..05d130404e5 100644 --- a/Sources/Db/Schema/v3_0/Permissions.php +++ b/Sources/Db/Schema/v3_0/Permissions.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. @@ -130,10 +130,6 @@ class Permissions extends Table 'id_group' => 0, 'permission' => 'profile_remote_avatar', ], - [ - 'id_group' => 0, - 'permission' => 'send_email_to_members', - ], [ 'id_group' => 2, 'permission' => 'view_mlist', @@ -218,10 +214,6 @@ class Permissions extends Table 'id_group' => 2, 'permission' => 'profile_remote_avatar', ], - [ - 'id_group' => 2, - 'permission' => 'send_email_to_members', - ], [ 'id_group' => 2, 'permission' => 'profile_title_own', diff --git a/Sources/Db/Schema/v3_0/PersonalMessages.php b/Sources/Db/Schema/v3_0/PersonalMessages.php index ceb9340f7ce..7726f666a84 100644 --- a/Sources/Db/Schema/v3_0/PersonalMessages.php +++ b/Sources/Db/Schema/v3_0/PersonalMessages.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/PmLabeledMessages.php b/Sources/Db/Schema/v3_0/PmLabeledMessages.php index 9345543a797..6b76841067a 100644 --- a/Sources/Db/Schema/v3_0/PmLabeledMessages.php +++ b/Sources/Db/Schema/v3_0/PmLabeledMessages.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/PmLabels.php b/Sources/Db/Schema/v3_0/PmLabels.php index 02fa039e1c1..3a84e730f9e 100644 --- a/Sources/Db/Schema/v3_0/PmLabels.php +++ b/Sources/Db/Schema/v3_0/PmLabels.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/PmRecipients.php b/Sources/Db/Schema/v3_0/PmRecipients.php index 0b3a0312470..9162eb64385 100644 --- a/Sources/Db/Schema/v3_0/PmRecipients.php +++ b/Sources/Db/Schema/v3_0/PmRecipients.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/PmRules.php b/Sources/Db/Schema/v3_0/PmRules.php index 792e5aa94a4..cd91e918984 100644 --- a/Sources/Db/Schema/v3_0/PmRules.php +++ b/Sources/Db/Schema/v3_0/PmRules.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/PollChoices.php b/Sources/Db/Schema/v3_0/PollChoices.php index fc11ca029a0..44014c031fe 100644 --- a/Sources/Db/Schema/v3_0/PollChoices.php +++ b/Sources/Db/Schema/v3_0/PollChoices.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Polls.php b/Sources/Db/Schema/v3_0/Polls.php index f92ee82d95b..dad48c79ac5 100644 --- a/Sources/Db/Schema/v3_0/Polls.php +++ b/Sources/Db/Schema/v3_0/Polls.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Qanda.php b/Sources/Db/Schema/v3_0/Qanda.php index 031cb5e7456..030989d3ed6 100644 --- a/Sources/Db/Schema/v3_0/Qanda.php +++ b/Sources/Db/Schema/v3_0/Qanda.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/ScheduledTasks.php b/Sources/Db/Schema/v3_0/ScheduledTasks.php index 2a42e2f5f26..7c97e27c99d 100644 --- a/Sources/Db/Schema/v3_0/ScheduledTasks.php +++ b/Sources/Db/Schema/v3_0/ScheduledTasks.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Sessions.php b/Sources/Db/Schema/v3_0/Sessions.php index 2f2a982c427..698486b4c09 100644 --- a/Sources/Db/Schema/v3_0/Sessions.php +++ b/Sources/Db/Schema/v3_0/Sessions.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Settings.php b/Sources/Db/Schema/v3_0/Settings.php index 9652057c352..9648283117c 100644 --- a/Sources/Db/Schema/v3_0/Settings.php +++ b/Sources/Db/Schema/v3_0/Settings.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. @@ -889,5 +889,3 @@ public function __construct() parent::__construct(); } } - -?> \ No newline at end of file diff --git a/Sources/Db/Schema/v3_0/SmileyFiles.php b/Sources/Db/Schema/v3_0/SmileyFiles.php index 54cde4c2a3c..b77f723f215 100644 --- a/Sources/Db/Schema/v3_0/SmileyFiles.php +++ b/Sources/Db/Schema/v3_0/SmileyFiles.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Smileys.php b/Sources/Db/Schema/v3_0/Smileys.php index 5ca81844449..928a8b23b0e 100644 --- a/Sources/Db/Schema/v3_0/Smileys.php +++ b/Sources/Db/Schema/v3_0/Smileys.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Spiders.php b/Sources/Db/Schema/v3_0/Spiders.php index 34c72d543f5..b8829da8c63 100644 --- a/Sources/Db/Schema/v3_0/Spiders.php +++ b/Sources/Db/Schema/v3_0/Spiders.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Subscriptions.php b/Sources/Db/Schema/v3_0/Subscriptions.php index 1aa4370a605..3746a99c7fe 100644 --- a/Sources/Db/Schema/v3_0/Subscriptions.php +++ b/Sources/Db/Schema/v3_0/Subscriptions.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Themes.php b/Sources/Db/Schema/v3_0/Themes.php index bb9244fb18b..8f0ead55114 100644 --- a/Sources/Db/Schema/v3_0/Themes.php +++ b/Sources/Db/Schema/v3_0/Themes.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/Topics.php b/Sources/Db/Schema/v3_0/Topics.php index 7acf5b439b1..6de9a6192dd 100644 --- a/Sources/Db/Schema/v3_0/Topics.php +++ b/Sources/Db/Schema/v3_0/Topics.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/UserAlerts.php b/Sources/Db/Schema/v3_0/UserAlerts.php index 2ad5d131ddf..7f4f9cfb4df 100644 --- a/Sources/Db/Schema/v3_0/UserAlerts.php +++ b/Sources/Db/Schema/v3_0/UserAlerts.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/UserAlertsPrefs.php b/Sources/Db/Schema/v3_0/UserAlertsPrefs.php index 6e6ea3134e5..333dd51a2f2 100644 --- a/Sources/Db/Schema/v3_0/UserAlertsPrefs.php +++ b/Sources/Db/Schema/v3_0/UserAlertsPrefs.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/UserDrafts.php b/Sources/Db/Schema/v3_0/UserDrafts.php index 6041b4fdf80..f66f52e1f66 100644 --- a/Sources/Db/Schema/v3_0/UserDrafts.php +++ b/Sources/Db/Schema/v3_0/UserDrafts.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. diff --git a/Sources/Db/Schema/v3_0/UserLikes.php b/Sources/Db/Schema/v3_0/UserLikes.php index a0097f54369..157dd2a14f4 100644 --- a/Sources/Db/Schema/v3_0/UserLikes.php +++ b/Sources/Db/Schema/v3_0/UserLikes.php @@ -13,11 +13,11 @@ declare(strict_types=1); -namespace SMF\Maintenance\Database\Schema\v3_0; +namespace SMF\Db\Schema\v3_0; -use SMF\Maintenance\Database\Schema\Column; -use SMF\Maintenance\Database\Schema\DbIndex; -use SMF\Maintenance\Database\Schema\Table; +use SMF\Db\Schema\Column; +use SMF\Db\Schema\DbIndex; +use SMF\Db\Schema\Table; /** * Defines all the properties for a database table. From 572704635471b46c1a2e38e70a68bab34106633d Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Mon, 28 Apr 2025 13:45:22 -0600 Subject: [PATCH 09/90] Rewrites installer Signed-off-by: Jon Stovell --- Languages/en_US/Maintenance.php | 16 +- Sources/Cookie.php | 2 +- Sources/Maintenance/Maintenance.php | 906 +++++++ Sources/Maintenance/Step.php | 173 ++ Sources/Maintenance/Tools/Install.php | 1725 ++++++++++++ Sources/Maintenance/Tools/ToolsBase.php | 438 +++ Sources/Maintenance/Tools/ToolsInterface.php | 65 + Sources/Maintenance/Tools/index.php | 8 + Sources/Maintenance/index.php | 8 + Themes/default/InstallTemplate.php | 506 ++++ Themes/default/MaintenanceTemplate.php | 266 ++ Themes/default/css/maintenance.css | 180 ++ other/install.php | 2552 +----------------- 13 files changed, 4294 insertions(+), 2551 deletions(-) create mode 100644 Sources/Maintenance/Maintenance.php create mode 100644 Sources/Maintenance/Step.php create mode 100644 Sources/Maintenance/Tools/Install.php create mode 100644 Sources/Maintenance/Tools/ToolsBase.php create mode 100644 Sources/Maintenance/Tools/ToolsInterface.php create mode 100644 Sources/Maintenance/Tools/index.php create mode 100644 Sources/Maintenance/index.php create mode 100644 Themes/default/InstallTemplate.php create mode 100644 Themes/default/MaintenanceTemplate.php create mode 100644 Themes/default/css/maintenance.css diff --git a/Languages/en_US/Maintenance.php b/Languages/en_US/Maintenance.php index bdaf02debed..3c3801a8628 100644 --- a/Languages/en_US/Maintenance.php +++ b/Languages/en_US/Maintenance.php @@ -28,7 +28,7 @@ $txt['warning'] = 'Warning!'; $txt['error_db_queries'] = 'Some of the queries were not executed properly. This could be caused by an unsupported (development or old) version of your database software.

Technical information about the queries:'; $txt['error_php_too_low'] = 'Warning! You do not appear to have a version of PHP installed on your webserver that meets SMF’s minimum installations requirements.

Please ask your host to upgrade.'; -$txt['error_dir_not_writable'] = 'The directory "{dir}" has to be writable to continue the upgrade. Please make sure permissions are correctly set to allow this.'; +$txt['error_dir_not_writable'] = 'The directory "{dir}" has to be writable to continue. Please make sure the file permissions are correctly set to allow this.'; $txt['error_unknown'] = 'Unknown Error!'; $txt['query_unsuccessful'] = 'Unsuccessful!'; $txt['query_failed'] = 'This query: {QUERY_STRING} @@ -48,8 +48,8 @@ }'; // File Permissions. -$txt['chmod_linux_info'] = 'If you have a shell account, the convenient below command can automatically correct permissions on these files'; -$txt['error_windows_chmod'] = 'You are on a windows server and some crucial files are not writable. Please ask your host to give write permissions to the user PHP is running under for the files in your SMF installation. The following files or directories need to be writable:'; +$txt['chmod_linux_info'] = 'If you have a shell account, the following command can automatically correct permissions on these files'; +$txt['error_windows_chmod'] = 'You are on a Windows server and some crucial files are not writable. Please ask your host to give write permissions to the user that PHP is running under for the files in your SMF installation. The following files or directories need to be writable:'; // FTP $txt['ftp_setup_why_info'] = 'Some files need to be writable for SMF to work properly. This step allows you to let the installer make them writable for you. However, in some cases it will not work. In this case, please make the following files 777 (writable, 755 on some hosts):'; @@ -82,13 +82,14 @@ $txt['install_step_databaseset'] = 'Database Settings'; $txt['install_step_databasechange'] = 'Database Population'; $txt['install_step_admin'] = 'Admin account'; -$txt['install_step_delete'] = 'Finalize install'; +$txt['install_step_finalize'] = 'Finalize install'; // Upgrade steps. $txt['upgrade_step_login'] = 'Login'; $txt['upgrade_step_options'] = 'Upgrade Options'; $txt['upgrade_step_backup'] = 'Backup'; $txt['upgrade_step_migration'] = 'Migrations'; +$txt['upgrade_step_convertutf'] = 'Convert to UTF-8'; $txt['upgrade_step_cleanup'] = 'Cleanup'; $txt['upgrade_step_delete'] = 'Finalize Upgrade'; @@ -291,16 +292,16 @@ other {out of # tables} }.'; -// Upgrade - Migrations +// Upgrade - steps and substeps $txt['upgrade_steps'] = 'Steps'; -$txt['upgrade_substeps'] = 'Migrations'; +$txt['upgrade_substeps'] = 'Substeps'; $txt['upgrade_db_changes'] = 'Executing database changes'; $txt['upgrade_db_patient'] = 'Please be patient - this may take some time on large forums. The time elapsed increments from the server to show progress is being made.'; $txt['upgrade_current_step'] = 'Current Step:'; $txt['upgrade_current_substep'] = 'Current Migration:'; $txt['upgrade_completed'] = 'Completed'; $txt['upgrade_outof'] = 'out of'; -$txt['upgrade_db_complete'] = '1 Database Updates Complete! Click Continue to Proceed.'; +$txt['upgrade_db_complete'] = 'Database update complete! Click Continue to proceed.'; $txt['upgrade_completed_migration'] = ' Completed Migration:'; @@ -374,7 +375,6 @@ $txt['upgrade_step_options'] = 'Upgrade Options'; $txt['upgrade_step_backup'] = 'Backup'; $txt['upgrade_step_database'] = 'Database Changes'; -$txt['upgrade_step_convertutf'] = 'Convert to UTF-8'; $txt['upgrade_step_convertjson'] = 'Convert serialized strings to JSON'; $txt['upgrade_step_delete'] = 'Delete Upgrade.php'; $txt['upgrade_step_cleanup'] = 'Cleanup'; diff --git a/Sources/Cookie.php b/Sources/Cookie.php index 8a3ebb8ecfc..ff1fc8aaad2 100644 --- a/Sources/Cookie.php +++ b/Sources/Cookie.php @@ -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(); diff --git a/Sources/Maintenance/Maintenance.php b/Sources/Maintenance/Maintenance.php new file mode 100644 index 00000000000..1f8fd1fd3ed --- /dev/null +++ b/Sources/Maintenance/Maintenance.php @@ -0,0 +1,906 @@ + 'Install', + self::UPGRADE => 'Upgrade', + self::CONVERT => 'Convert', + self::TOOL => 'Tool', + self::SPECIAL => 'Special', + ]; + + /**************** + * Public methods + ****************/ + + public function __construct() + { + Security::frameOptionsHeader('SAMEORIGIN'); + self::$theme_dir = dirname(SMF_SETTINGS_FILE) . '/Themes/default'; + + // This might be overwritten by the tool, but we need a default value. + self::$context['started'] = (int) TIME_START; + self::$script_start = (int) TIME_START; + } + + /** + * This is the main call to get stuff done. + * + * @var int The tool type we are running. + */ + public function execute(int $type): void + { + if (!self::toolIsValid($type)) { + die('Invalid Tool selected'); + } + + // Handle the CLI. + if (Sapi::isCLI()) { + self::parseCliArguments(); + } + + $tool_class = __NAMESPACE__ . '\\Tools\\' . self::$valid_tools[$type]; + self::$tool = new $tool_class(); + + $template_class = '\\SMF\\Themes\\default\\' . self::$valid_tools[$type] . 'Template'; + self::$template = new $template_class(); + + // This is really quite simple; if ?delete is on the URL, delete the + // tool's entry point file (e.g. install.php for the installer). + if (isset($_GET['delete'])) { + self::$tool->deleteTool(); + + exit; + } + + foreach (self::$tool->getSteps() as $num => $step) { + // Skip steps we have done, but count their progress. + if ($num < self::getCurrentStep()) { + self::$overall_percent += (int) $step->getProgress(); + continue; + } + + // The current weight of this step in terms of overall progress. + self::$context['step_weight'] = $step->getProgress(); + + // Make sure we reset the skip button. + self::$context['skip'] = false; + + // What should we call for this step? + if (($callable = Utils::getCallable($step->getFunction(), true)) === false) { + $callable = Utils::getCallable([self::$tool, $step->getFunction()]); + } + + // Call the step and if it returns false that means pause! + if (is_callable($callable) && call_user_func($callable) === false) { + break; + } + + // Time to move on. + self::setCurrentStep(); + self::setCurrentSubStep(0); + self::setCurrentStart(0); + + // No warnings pass on. + self::$context['warning'] = ''; + + self::$overall_percent += (int) $step->getProgress(); + } + + // Last chance to set our template. + if ( + array_key_exists(self::getCurrentStep(), self::$tool->getSteps()) + && self::$sub_template === '' + ) { + self::$sub_template = self::$tool->getSteps()[self::getCurrentStep()]->getTemplate(); + } + + // Make a final call before we are done.. + self::$tool->preExit(); + + self::exit(Sapi::isCLI()); + } + + /*********************** + * Public static methods + ***********************/ + + /** + * See if we think they have already installed SMF? + * + * @return bool Whether we believe SMF has been installed. + */ + public static function isInstalled(): bool + { + $settings_defs = Config::getSettingsDefs(); + + foreach (['image_proxy_secret', 'db_passwd', 'boardurl'] as $var) { + if (Config::${$var} === $settings_defs[$var]['default']) { + return false; + } + } + + return true; + } + + /** + * Is this a json request? + * + * @return bool Whether we believe we are working with a json response. + */ + public static function isJson(): bool + { + return isset($_GET['json']); + } + + /** + * The URL to the script. + * + * @return string The URL to the script. + */ + public static function getSelf(): string + { + return $_SERVER['PHP_SELF']; + } + + /** + * Get the forum's base directory. + * + * @return string The directory name we are in. + */ + public static function getBaseDir(): string + { + if (class_exists('\\SMF\\Config')) { + if (!isset(Config::$boarddir)) { + Config::load(); + } + + if (isset(Config::$boarddir)) { + return Config::$boarddir; + } + } + + // If SMF\Config::$boarddir was not available for some reason, try doing it manually. + if (!in_array(SMF_SETTINGS_FILE, get_included_files())) { + require SMF_SETTINGS_FILE; + } else { + $settingsText = trim(file_get_contents(SMF_SETTINGS_FILE)); + + if (substr($settingsText, 0, 5) == '<' . '?php') { + $settingsText = substr($settingsText, 5); + } + + if (substr($settingsText, -2) == '?' . '>') { + $settingsText = substr($settingsText, 0, -2); + } + + // Since we're using eval, we need to manually replace these with strings. + $settingsText = strtr($settingsText, [ + '__FILE__' => var_export(SMF_SETTINGS_FILE, true), + '__DIR__' => var_export(dirname(SMF_SETTINGS_FILE), true), + ]); + + // Prevents warnings about constants that are already defined. + $settingsText = preg_replace_callback( + '~\bdefine\s*\(\s*(["\'])(\w+)\1~', + function ($matches) { + return 'define(\'' . bin2hex(random_bytes(16)) . '\''; + }, + $settingsText, + ); + + // Handle eval errors gracefully in all PHP versions. + try { + if ($settingsText !== '' && @eval($settingsText) === false) { + throw new \ErrorException('eval error'); + } + } catch (\Throwable $e) { + } catch (\ErrorException $e) { + } + } + + return $boarddir ?? dirname(__DIR__); + } + + /** + * Fetch our current step. + * + * @return int Current Step. + */ + public static function getCurrentStep(): int + { + return isset($_GET['step']) ? (int) $_GET['step'] : 0; + } + + /** + * Fetch our current sub-step. + * + * @return int Current Sub-Step + */ + public static function getCurrentSubStep(): int + { + return isset($_GET['substep']) ? (int) $_GET['substep'] : 0; + } + + /** + * Set our current sub-step. This is public as our tool needs to update this. + * + * @param null|int $substep The sub-step we on. If null is passed, we will auto increment from the current. + */ + public static function setCurrentSubStep(?int $substep = null): void + { + $_GET['substep'] = $substep ?? (self::getCurrentSubStep() + 1); + } + + /** + * Returns a percent indicating the progression through our sub steps. + * + * @return int Int representing a percent out of 100 on completion of sub steps. + */ + public static function getSubStepProgress(): int + { + return empty(self::$total_substeps) ? 100 : (int) (self::getCurrentSubStep() / self::$total_substeps); + } + + /** + * Fetch our current starting position. This is used for loops inside steps. + * + * @return int Current starting position. + */ + public static function getCurrentStart(): int + { + return isset($_GET['start']) ? (int) $_GET['start'] : 0; + } + + /** + * Set our current start. This is public as our tool needs to update this. + * + * @param null|int $substep The starting position we on. If null is passed, we will auto increment from the current. + */ + public static function setCurrentStart(?int $start = null): void + { + $_GET['start'] = $start ?? (self::getCurrentStart() + 1); + } + + /** + * Returns a percent indicating the progression through our sub steps. + * + * @return int Int representing a percent out of 100 on completion of sub steps. + */ + public static function getItemsProgress(): int + { + return self::$total_items === null || self::$total_items === 0 ? 0 : (int) (self::getCurrentStart() / self::$total_items); + } + + /** + * Determine the language file we want to load. + * + * This doesn't validate it exists, just that its a sane value to try. + * + * @return string Language we will load. + */ + public static function getRequestedLanguage(): string + { + if (isset($_GET['lang_file'])) { + $_SESSION['lang_file'] = strtr((string) $_GET['lang_file'], './\\:', '____'); + + return $_SESSION['lang_file']; + } + + if (isset($_SESSION['lang_file'])) { + return $_SESSION['lang_file']; + } + + return 'en_US'; + } + + /** + * Sets the sub template we will use. + * + * A check is made to ensure that we can call it. + * + * @param string $tmpl Template to use. + */ + public static function setSubTemplate(string $tmpl): void + { + if (method_exists(self::$template, $tmpl)) { + self::$sub_template = $tmpl; + } + } + + /** + * Safely start up a database for maintenance actions. + */ + public static function loadDatabase(): void + { + if (!class_exists('SMF\\Db\\APIs\\' . Db::getClass(Config::$db_type))) { + throw new \Exception(Lang::getTxt('error_sourcefile_missing', ['file' => 'Db/APIs/' . Db::getClass(Config::$db_type) . '.php'], file: 'Maintenance')); + } + + // Make the connection... + if (empty(Db::$db) || !(Db::$db instanceof Db)) { + Db::load(['non_fatal' => true]); + } else { + // If we've returned here, ping/reconnect to be safe + Db::$db->ping(); + } + + // Oh dear god!! + if (Db::$db->connection === null) { + // Get error info... Recast just in case we get false or 0... + $error_message = Db::$db->connect_error(); + + if (empty($error_message)) { + $error_message = ''; + } + $error_number = Db::$db->connect_errno(); + + if (empty($error_number)) { + $error_number = ''; + } + $db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message; + + throw new \Exception(Lang::getTxt('error_db_connect_settings', file: 'Maintenance') . '

' . $db_error); + } + } + + /** + * Loads the modSettings data. + */ + public static function loadModSettings(): void + { + // Load the modSettings data... + $request = Db::$db->query( + '', + 'SELECT variable, value + FROM {db_prefix}settings', + [ + 'db_error_skip' => true, + ], + ); + + if ($request === false) { + throw new \Exception(Lang::getTxt('error_db_connect_settings', file: 'Maintenance')); + } + + Config::$modSettings = []; + + while ($row = Db::$db->fetch_assoc($request)) { + Config::$modSettings[$row['variable']] = $row['value']; + } + Db::$db->free_result($request); + } + + /** + * Fetch the theme information for the default theme. + * + * If this can't be loaded, we fall back to a guess. + */ + public static function setThemeData(): void + { + // This only exists if we're on SMF ;) + if (isset(Config::$modSettings['smfVersion'])) { + $request = Db::$db->query( + '', + 'SELECT variable, value + FROM {db_prefix}themes + WHERE id_theme = {int:id_theme} + AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})', + [ + 'id_theme' => 1, + 'theme_url' => 'theme_url', + 'theme_dir' => 'theme_dir', + 'images_url' => 'images_url', + 'db_error_skip' => true, + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + if (!empty($row['value'])) { + self::${$row['variable']} = $row['value']; + } + } + Db::$db->free_result($request); + + if (Sapi::httpsOn()) { + self::$theme_url = strtr(self::$theme_url, ['http://' => 'https://']); + self::$images_url = strtr(self::$images_url, ['http://' => 'https://']); + } + } + + if (!isset(Config::$modSettings['theme_url'])) { + Config::$modSettings['theme_dir'] = empty(self::$theme_dir) ? Config::$boarddir . '/Themes/default' : self::$theme_dir; + Config::$modSettings['theme_url'] = empty(self::$theme_url) ? 'Themes/default' : self::$theme_url; + Config::$modSettings['images_url'] = empty(self::$images_url) ? 'Themes/default/images' : self::$images_url; + } + } + + /** + * Attempts to login an administrator. + * + * If the account does not have admin_forum permission, they are rejected. + * + * This will attempt using the SMF 2.0 method if specified. + * + * @param string $username The admin's username + * @param string $password The admin's password. + * As of PHP 8.2, this will not be included in any stack traces. + * @param bool $use_old_hashing Whether to allow SMF 2.0 hashing. + * @return int The id of the user if they are an admin, 0 otherwise. + */ + public static function loginAdmin( + string $username, + #[\SensitiveParameter] + string $password, + bool $use_old_hashing = false, + ): int { + $id = 0; + + $request = Db::$db->query( + '', + 'SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile + FROM {db_prefix}members + WHERE member_name = {string:member_name}', + [ + 'member_name' => $username, + 'db_error_skip' => true, + ], + ); + + if (Db::$db->num_rows($request) != 0) { + $row = Db::$db->fetch_row($request); + } + + Db::$db->free_result($request); + + if (!empty($row)) { + list($id_member, $name, $password, $id_group, $addGroups, $user_language) = $row; + + $groups = explode(',', $addGroups); + $groups[] = (int) $id_group; + + foreach ($groups as $k => $v) { + $groups[$k] = (int) $v; + } + + if ( + // SMF 3.0+ + Security::hashVerifyPassword($_REQUEST['passwrd'], $password) + // SMF 2.1 prepended the username to the password. + || Security::hashVerifyPassword(Utils::strtolower($name) . $_REQUEST['passwrd'], $password) + // SMF 2.0 used sha1 + || ($use_old_hashing && $password === sha1(strtolower($name) . $_REQUEST['passwrd'])) + ) { + $id = (int) $id_member; + } + + // We have a valid login. + if ($id > 0 && !in_array(1, $groups)) { + $request = Db::$db->query( + '', + 'SELECT permission + FROM {db_prefix}permissions + WHERE id_group IN ({array_int:groups}) + AND permission = {string:admin_forum}', + [ + 'groups' => $groups, + 'admin_forum' => 'admin_forum', + 'db_error_skip' => true, + ], + ); + + if (Db::$db->num_rows($request) == 0) { + $id = 0; + } + Db::$db->free_result($request); + } + } + + if ($id > 0 && !empty($user_language)) { + $_SESSION['lang_file'] = strtr((string) $user_language, './\\:', '____'); + } + + return $id; + } + + /** + * Attempts to login using the database password. + * + * @param string $password The database password. + * As of PHP 8.2, this will not be included in any stack traces. + * @return bool Whether this is valid. + */ + public static function loginWithDatabasePassword( + #[\SensitiveParameter] + string $password, + ): bool { + return Config::$db_passwd === $password; + } + + /** + * Returns a string formated for the current time elasped. + * + * @return string Formatted string. + */ + 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; + + return Lang::getTxt('maintenance_time_elasped_ms', ['m' => $mins, 's' => $seconds]); + } + + /** + * Check if we are out of time, and try to buy some more. + * + * If this is CLI, always returns false. + * + * @return bool Whether we need to exit the script soon. + */ + public static function isOutOfTime(): bool + { + if (Sapi::isCLI()) { + if (time() - self::$context['started'] > 1 && !self::$tool->isDebug()) { + echo '.'; + } + + return false; + } + + Sapi::setTimeLimit(300); + Sapi::resetTimeout(); + + // Still have time left. + return !(time() - self::$script_start <= 3); + } + + /** + * Sets (and returns) the value of self::$query_string; + * + * @return string A copy of self::$query_string. + */ + public static function setQueryString(): string + { + // Always ensure this is updated. + $_GET['step'] = self::getCurrentStep(); + + self::$query_string = http_build_query($_GET, '', ';'); + + return self::$query_string; + } + + /** + * Exit the script. This will wrap the templates. + * + * @param bool $fallthrough If true, we just skip templates and do nothing. + * @return never All execution is stopped here. + */ + public static function exit(bool $fallthrough = false): void + { + // We usually dump our templates out. + if (!$fallthrough) { + // Send character set. + header('content-type: text/html; charset=UTF-8'); + + // The top bit. + call_user_func([self::$template, 'header']); + + if (method_exists(self::$template, 'upper')) { + call_user_func([self::$template, 'upper']); + } + + // Call the template. + if (self::$sub_template !== '') { + self::$context['form_url'] = self::getSelf() . '?step=' . self::getCurrentStep(); + + call_user_func([self::$template, self::$sub_template]); + } + + // Show the footer. + if (method_exists(self::$template, 'lower')) { + call_user_func([self::$template, 'lower']); + } + + call_user_func([self::$template, 'footer']); + } + + // Bang - gone! + die(); + } + + /** + * Handle a response for our JavaScript logic. + * + * This always returns a success header, which is used to handle continues. + * + * @param mixed $data + * @param bool $success Whether the result was successful. + */ + public static function jsonResponse(mixed $data, bool $success = true): void + { + if (Sapi::isCLI()) { + return; + } + + ob_end_clean(); + header('content-type: text/json; charset=UTF-8'); + + // TODO: Improve this, move debug to the root. + $debug_data = []; + + if (Maintenance::$tool->isDebug()) { + $debug_data = $data['debug'] ?? []; + } + unset($data['debug']); + + echo json_encode([ + 'success' => $success, + 'data' => $data, + 'debug' => $debug_data, + ]); + + die; + } + + /************************* + * Internal static methods + *************************/ + + /** + * Handle parsing the CLI inputs. + * + * We push everything into $_REQUEST, which isn't pretty, but we don't + * handle the input any other way currently. + */ + protected static function parseCliArguments(): void + { + if (!Sapi::isCLI()) { + return; + } + + if (!empty($_SERVER['argv']) && Sapi::isCLI()) { + for ($i = 1; $i < count($_SERVER['argv']); $i++) { + if (preg_match('/^--([^=]+)=(.*)/', $_SERVER['argv'][$i], $match)) { + $_REQUEST[$match[1]] = $match[2]; + } + } + } + } + + /** + * Checks that the tool we requested is valid. + * + * @param int $type Tool we are trying to use. + * @return bool Whether it is valid. + */ + private static function toolIsValid(int $type): bool + { + return isset(self::$valid_tools[$type]); + } + + /** + * Set the current step. + * + * Tools do not gain access to this and its protected. + * + * @param null|int $step + */ + private static function setCurrentStep(?int $step = null): void + { + $_GET['step'] = $step ?? (self::getCurrentStep() + 1); + } +} diff --git a/Sources/Maintenance/Step.php b/Sources/Maintenance/Step.php new file mode 100644 index 00000000000..b1e1e19b4b6 --- /dev/null +++ b/Sources/Maintenance/Step.php @@ -0,0 +1,173 @@ +id = $id; + $this->name = $name; + $this->title = $title; + $this->function = $function; + $this->progress = $progress; + + if (isset($template)) { + $this->template = $template; + } elseif (is_array($function)) { + $this->template = $function[1]; + } else { + $this->template = ltrim(substr($function, strrpos($function, '::')), ':'); + } + } + + /** + * Fetches the ID of this step. + * + * @return int ID of the step. Typically this is one higher than the ID + * found in the array. + */ + public function getID(): int + { + return $this->id; + } + + /** + * Fetches the name of this step. + * + * @return string Name of the step. If we are showing steps, this will be + * displayed in the step list. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Fetches the title of this step. + * + * @see $title + * @return string The page title to display for this step. + */ + public function getTitle(): string + { + return $this->title ?? $this->name; + } + + /** + * Fetches the function called by this step. + * + * @return array|string Function to call. This is actually the method inside the + * tool and must be public. + */ + public function getFunction(): array|string + { + return $this->function; + } + + /** + * Fetches the function called by this step. + * + * @return array|string Function to call. This is actually the method inside the + * tool and must be public. + */ + public function getTemplate(): string + { + return $this->template; + } + + /** + * Fetches the progress value of this step. + * + * @return int The amount of progress to be made when this step completes. + */ + public function getProgress(): int + { + return $this->progress; + } +} diff --git a/Sources/Maintenance/Tools/Install.php b/Sources/Maintenance/Tools/Install.php new file mode 100644 index 00000000000..b5363a7e203 --- /dev/null +++ b/Sources/Maintenance/Tools/Install.php @@ -0,0 +1,1725 @@ +detectLanguages(['General', 'Maintenance']); + + if (empty(Maintenance::$languages)) { + if (!Sapi::isCLI()) { + MaintenanceTemplate::missingLanguages(); + } + + throw new \Exception('This script was unable to find this tools\'s language file or files.'); + } else { + $requested_lang = Maintenance::getRequestedLanguage(); + + // Ensure SMF\Lang knows the path to the language directory. + Lang::addDirs(Config::$languagesdir); + + // And now load the language file. + Lang::load('General+Maintenance', $requested_lang); + + // Assume that the admin likes that language. + if ($requested_lang !== 'en_US') { + Config::$language = $requested_lang; + } + } + + $this->getUpgradeData(); + + // Template needs to know about this. + Maintenance::$context['started'] = $this->time_started; + } + + /** + * Get the script name + * + * @return string Page Title + */ + public function getScriptName(): string + { + return Lang::getTxt('smf_installer', file: 'Install'); + } + + /** + * Gets our page title to be sent to the template. + * + * Selection is in the following order: + * 1. A custom page title. + * 2. Step has provided a title. + * 3. Default for the installer tool. + * + * @return string Page Title + */ + public function getPageTitle(): string + { + return $this->page_title ?? $this->getSteps()[Maintenance::getCurrentStep()]->getTitle() ?? $this->getScriptName(); + } + + /** + * If a tool does not contain steps, this should be false, true otherwise. + * + * @return bool Whether or not a tool has steps. + */ + public function hasSteps(): bool + { + return true; + } + + /** + * Installer Steps + * + * @return \SMF\Maintenance\Step[] + */ + public function getSteps(): array + { + return [ + 0 => new Step( + id: 1, + name: Lang::getTxt('install_step_welcome', file: 'Install'), + title: Lang::getTxt('install_welcome', file: 'Install'), + function: 'welcome', + template: 'welcome', + progress: 0, + ), + 1 => new Step( + id: 2, + name: Lang::getTxt('install_step_writable', file: 'Install'), + function: 'checkFilesWritable', + template: 'checkFilesWritable', + progress: 10, + ), + 2 => new Step( + id: 3, + name: Lang::getTxt('install_step_databaseset', file: 'Install'), + title: Lang::getTxt('db_settings', file: 'Install'), + function: 'databaseSettings', + template: 'databaseSettings', + progress: 15, + ), + 3 => new Step( + id: 4, + name: Lang::getTxt('install_step_forum', file: 'Install'), + title: Lang::getTxt('install_settings', file: 'Install'), + function: 'forumSettings', + template: 'forumSettings', + progress: 40, + ), + 4 => new Step( + id: 5, + name: Lang::getTxt('install_step_databasechange', file: 'Install'), + title: Lang::getTxt('db_populate', file: 'Install'), + function: 'databasePopulation', + template: 'databasePopulation', + progress: 15, + ), + 5 => new Step( + id: 6, + name: Lang::getTxt('install_step_admin', file: 'Install'), + title: Lang::getTxt('user_settings', file: 'Install'), + function: 'adminAccount', + template: 'adminAccount', + progress: 20, + ), + 6 => new Step( + id: 7, + name: Lang::getTxt('install_step_finalize', file: 'Install'), + function: 'finalize', + template: 'finalize', + progress: 0, + ), + ]; + } + + /** + * Gets the title for the step we are performing + * + * @return string + */ + public function getStepTitle(): string + { + return $this->getSteps()[Maintenance::getCurrentStep()]->getName(); + } + + /** + * Welcome action. + * + * @return bool True if we can continue, false otherwise. + */ + public function welcome(): bool + { + // Done the submission? + if (isset($_POST['contbutt'])) { + return true; + } + + if (Maintenance::isInstalled()) { + Maintenance::$context['warning'] = Lang::getTxt('error_already_installed', file: 'Install'); + } + + Maintenance::$context['supported_databases'] = $this->supportedDatabases(); + + // Needs to at least meet our miniumn version. + if ((version_compare(Maintenance::PHP_MIN_VERSION, PHP_VERSION, '>'))) { + Maintenance::$fatal_error = Lang::getTxt('error_php_too_low', file: 'Install'); + + return false; + } + + // Make sure we have a supported database + if (empty(Maintenance::$context['supported_databases'])) { + Maintenance::$fatal_error = Lang::getTxt('error_db_missing', file: 'Install'); + + return false; + } + + // How about session support? Some crazy sysadmin remove it? + if (!function_exists('session_start')) { + Maintenance::$errors[] = Lang::getTxt('error_session_missing', file: 'Install'); + } + + // Make sure they uploaded all the files. + if (!file_exists(Config::$boarddir . '/index.php')) { + Maintenance::$errors[] = Lang::getTxt('error_missing_files', file: 'Install'); + } + // Very simple check on the session.save_path for Windows. + // @todo Move this down later if they don't use database-driven sessions? + elseif (@ini_get('session.save_path') == '/tmp' && Sapi::isOS(Sapi::OS_WINDOWS)) { + Maintenance::$errors[] = Lang::getTxt('error_session_save_path', file: 'Install'); + } + + // Mod_security blocks everything that smells funny. Let SMF handle security. + if (!$this->checkAndTryToFixModSecurity() && !isset($_GET['overmodsecurity'])) { + Maintenance::$fatal_error = Lang::getTxt('error_mod_security', file: 'Install') . '

' . Lang::getTxt('error_message_click', file: 'Install') . ' ' . Lang::getTxt('error_message_bad_try_again', file: 'Install'); + } + + // Confirm mbstring is loaded... + if (!extension_loaded('mbstring')) { + Maintenance::$errors[] = Lang::getTxt('install_no_mbstring', file: 'Install'); + } + + // Confirm fileinfo is loaded... + if (!extension_loaded('fileinfo')) { + Maintenance::$errors[] = Lang::getTxt('install_no_fileinfo', file: 'Install'); + } + + // Check for https stream support. + $supported_streams = stream_get_wrappers(); + + if (!in_array('https', $supported_streams)) { + Maintenance::$warnings[] = Lang::getTxt('install_no_https', file: 'Install'); + } + + if (empty(Maintenance::$errors)) { + Maintenance::$context['continue'] = true; + } + + return false; + } + + /** + * Check Files Writable action. + * + * @return bool True if we can continue, false otherwise. + */ + public function checkFilesWritable(): bool + { + $writable_files = [ + 'attachments', + 'avatars', + 'custom_avatar', + 'cache', + 'Packages', + 'Smileys', + 'Themes', + 'Languages/en_US/agreement.txt', + 'Settings.php', + 'Settings_bak.php', + 'cache/db_last_error.php', + ]; + + foreach ($this->detectLanguages() as $lang => $temp) { + $extra_files[] = 'Languages/' . $lang; + } + + // With mod_security installed, we could attempt to fix it with .htaccess. + if (function_exists('apache_get_modules') && in_array('mod_security', apache_get_modules())) { + $writable_files[] = file_exists(Config::$boarddir . '/.htaccess') ? '.htaccess' : '.'; + } + + $failed_files = []; + + // Windows is trickier. Let's try opening for r+... + if (Sapi::isOS(Sapi::OS_WINDOWS)) { + foreach ($writable_files as $file) { + // Folders can't be opened for write... but the index.php in them can ;) + if (is_dir(Config::$boarddir . '/' . $file)) { + $file .= '/index.php'; + } + + // Funny enough, chmod actually does do something on windows - it removes the read only attribute. + @chmod(Config::$boarddir . '/' . $file, 0777); + $fp = @fopen(Config::$boarddir . '/' . $file, 'r+'); + + // Hmm, okay, try just for write in that case... + if (!is_resource($fp)) { + $fp = @fopen(Config::$boarddir . '/' . $file, 'w'); + } + + if (!is_resource($fp)) { + $failed_files[] = $file; + } + + @fclose($fp); + } + + foreach ($extra_files as $file) { + @chmod(Config::$boarddir . (empty($file) ? '' : '/' . $file), 0777); + } + } else { + // On linux, it's easy - just use is_writable! + foreach ($writable_files as $file) { + // Some files won't exist, try to address up front + if (!file_exists(Config::$boarddir . '/' . $file)) { + @touch(Config::$boarddir . '/' . $file); + } + + // NOW do the writable check... + if (!is_writable(Config::$boarddir . '/' . $file)) { + @chmod(Config::$boarddir . '/' . $file, 0755); + + // Well, 755 hopefully worked... if not, try 777. + if (!is_writable(Config::$boarddir . '/' . $file) && !@chmod(Config::$boarddir . '/' . $file, 0777)) { + $failed_files[] = $file; + } + } + } + + foreach ($extra_files as $file) { + @chmod(Config::$boarddir . (empty($file) ? '' : '/' . $file), 0777); + } + } + + $failure = count($failed_files) >= 1; + + if (!isset($_SERVER)) { + return !$failure; + } + + // Put the list into context. + Maintenance::$context['failed_files'] = $failed_files; + + // It's not going to be possible to use FTP on windows to solve the problem... + if ($failure && Sapi::isOS(Sapi::OS_WINDOWS)) { + Maintenance::$fatal_error = Lang::getTxt('error_windows_chmod', file: 'Install') . ' +
    +
  • ' . implode('
  • +
  • ', $failed_files) . '
  • +
'; + + return false; + } + + // We're going to have to use... FTP! + if ($failure) { + // Load any session data we might have... + if (!isset($_POST['ftp']['username']) && isset($_SESSION['ftp'])) { + $_POST['ftp']['server'] = $_SESSION['ftp']['server']; + $_POST['ftp']['port'] = $_SESSION['ftp']['port']; + $_POST['ftp']['username'] = $_SESSION['ftp']['username']; + $_POST['ftp']['password'] = $_SESSION['ftp']['password']; + $_POST['ftp']['path'] = $_SESSION['ftp']['path']; + } + + Maintenance::$context['ftp_errors'] = []; + + if (isset($_POST['ftp_username'])) { + $ftp = new FtpConnection($_POST['ftp']['server'], $_POST['ftp']['port'], $_POST['ftp']['username'], $_POST['ftp']['password']); + + if ($ftp->error === false) { + // Try it without /home/abc just in case they messed up. + if (!$ftp->chdir($_POST['ftp']['path'])) { + Maintenance::$context['ftp_errors'][] = $ftp->last_message; + $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp']['path'])); + } + } + } + + if (!isset($ftp) || $ftp->error !== false) { + if (!isset($ftp)) { + $ftp = new FtpConnection(null); + } + // Save the error so we can mess with listing... + elseif ($ftp->error !== false && empty(Maintenance::$context['ftp_errors']) && !empty($ftp->last_message)) { + Maintenance::$context['ftp_errors'][] = $ftp->last_message; + } + + list($username, $detect_path, $found_path) = $ftp->detect_path(Config::$boarddir); + + if (empty($_POST['ftp']['path']) && $found_path) { + $_POST['ftp']['path'] = $detect_path; + } + + if (!isset($_POST['ftp']['username'])) { + $_POST['ftp']['username'] = $username; + } + + // Set the username etc, into context. + Maintenance::$context['ftp'] = [ + 'server' => $_POST['ftp']['server'] ?? 'localhost', + 'port' => $_POST['ftp']['port'] ?? '21', + 'username' => $_POST['ftp']['username'] ?? '', + 'path' => $_POST['ftp']['path'] ?? '/', + 'path_msg' => !empty($found_path) ? Lang::getTxt('ftp_path_found_info', file: 'Install') : Lang::getTxt('ftp_path_info', file: 'Install'), + ]; + + return false; + } + + $_SESSION['ftp'] = [ + 'server' => $_POST['ftp']['server'], + 'port' => $_POST['ftp']['port'], + 'username' => $_POST['ftp']['username'], + 'password' => $_POST['ftp']['password'], + 'path' => $_POST['ftp']['path'], + ]; + + $failed_files_updated = []; + + foreach ($failed_files as $file) { + if (!is_writable(Config::$boarddir . '/' . $file)) { + $ftp->chmod($file, 0755); + } + + if (!is_writable(Config::$boarddir . '/' . $file)) { + $ftp->chmod($file, 0777); + } + + if (!is_writable(Config::$boarddir . '/' . $file)) { + $failed_files_updated[] = $file; + Maintenance::$context['ftp_errors'][] = rtrim($ftp->last_message) . ' -> ' . $file . "\n"; + } + } + + $ftp->close(); + + // Are there any errors left? + if (count($failed_files_updated) >= 1) { + // Guess there are... + Maintenance::$context['failed_files'] = $failed_files_updated; + + // Set the username etc, into context. + Maintenance::$context['ftp'] = $_SESSION['ftp'] += [ + 'path_msg' => Lang::getTxt('ftp_path_info', file: 'Install'), + ]; + + return false; + } + } + + return true; + } + + /** + * Database Settings action. + * + * @return bool True if we can continue, false otherwise. + */ + public function databaseSettings(): bool + { + Maintenance::$context['continue'] = true; + Maintenance::$context['databases'] = []; + $foundOne = false; + + foreach ($this->supportedDatabases() as $db_type => $db) { + // Not supported, skip. + if (!$db->isSupported()) { + continue; + } + + Maintenance::$context['databases'][$db_type] = $db; + + // If we have not found a one, set some defaults. + if (!$foundOne) { + Maintenance::$context['db'] = [ + 'server' => $db->getDefaultHost(), + 'user' => $db->getDefaultUser(), + 'name' => $db->getDefaultName(), + 'pass' => $db->getDefaultPassword(), + 'port' => '', + 'prefix' => substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 3) . '_', + 'type' => $db_type, + ]; + + $foundOne = true; + } + } + + if (isset($_POST['db_user'])) { + Maintenance::$context['db']['user'] = $_POST['db_user']; + Maintenance::$context['db']['name'] = $_POST['db_name']; + Maintenance::$context['db']['server'] = $_POST['db_server']; + Maintenance::$context['db']['prefix'] = $_POST['db_prefix']; + + if (!empty($_POST['db_port'])) { + Maintenance::$context['db']['port'] = (int) $_POST['db_port']; + } + } + + // Are we submitting? + if (!isset($_POST['db_type'])) { + return false; + } + + // What type are they trying? + $db_type = preg_replace('~[^A-Za-z0-9]~', '', $_POST['db_type']); + $db_prefix = $_POST['db_prefix']; + + if (!isset(Maintenance::$context['databases'][$db_type])) { + Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Install'); + + return false; + } + + // Validate the prefix. + $db = Maintenance::$context['databases'][$db_type]; + + // Use a try/catch here, so we can send specific details about the validation error. + try { + if (!$db->validatePrefix($db_prefix)) { + Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Install'); + + return false; + } + } catch (\Throwable $e) { + Maintenance::$fatal_error = $e->getMessage(); + + return false; + } + + // Take care of these variables... + $vars = [ + 'db_type' => $db_type, + 'db_name' => $_POST['db_name'], + 'db_user' => $_POST['db_user'], + 'db_passwd' => $_POST['db_passwd'] ?? '', + 'db_server' => $_POST['db_server'], + 'db_prefix' => $db_prefix, + // The cookiename is special; we want it to be the same if it ever needs to be reinstalled with the same info. + 'cookiename' => $this->createCookieName($_POST['db_name'], $db_prefix), + ]; + + // Only set the port if we're not using the default + if (!empty($_POST['db_port']) && $db->getDefaultPort() !== (int) $_POST['db_port']) { + $vars['db_port'] = (int) $_POST['db_port']; + } + + // God I hope it saved! + try { + if (!Config::updateSettingsFile($vars)) { + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + + return false; + } + } catch (\Throwable $e) { + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + + return false; + } + + // Update SMF\Config with the changes we just saved. + Config::load(); + + // Better find the database file! + if (!file_exists(Config::$sourcedir . '/Db/APIs/' . Db::getClass(Config::$db_type) . '.php')) { + Maintenance::$fatal_error = Lang::getTxt('error_db_file', ['Db/APIs/' . Db::getClass(Config::$db_type) . '.php']); + + return false; + } + + // We need to make some queries, that would trip up our normal security checks. + Config::$modSettings['disableQueryCheck'] = true; + + // Attempt a connection. + Db::load([ + 'non_fatal' => true, + 'dont_select_db' => !Maintenance::$context['databases'][$db_type]->alwaysHasDb(), + ]); + + // Still no connection? Big fat error message :P. + if (!isset(Db::$db->connection)) { + // Get error info... Recast just in case we get false or 0... + $error_message = Db::$db->connect_error(); + + if (empty($error_message)) { + $error_message = ''; + } + $error_number = Db::$db->connect_errno(); + + if (empty($error_number)) { + $error_number = ''; + } + $db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message; + + Maintenance::$fatal_error = Lang::getTxt('error_db_connect', file: 'Install') . '
' . $db_error . '
'; + + return false; + } + + // Do they meet the install requirements? + // @todo Old client, new server? + if ( + version_compare( + preg_replace('~^\D*|\-.+?$~', '', Db::$db->get_version()), + Db::$db->getMinimumVersion(), + '<', + ) + ) { + Maintenance::$fatal_error = Lang::getTxt('error_db_too_low', ['name' => Db::$db->title]); + + return false; + } + + // Let's try that database on for size... assuming we haven't already lost the opportunity. + if (Db::$db->name != '' && !Maintenance::$context['databases'][$db_type]->alwaysHasDb()) { + Db::$db->query( + '', + 'CREATE DATABASE IF NOT EXISTS {identifier:name}', + [ + 'security_override' => true, + 'db_error_skip' => true, + 'name' => Db::$db->name, + ], + Db::$db->connection, + ); + + // Okay, let's try the prefix if it didn't work... + if (!Db::$db->select(Db::$db->name, Db::$db->connection) && Db::$db->name != '') { + Db::$db->query( + '', + 'CREATE DATABASE IF NOT EXISTS {identifier:name}', + [ + 'security_override' => true, + 'db_error_skip' => true, + 'name' => Db::$db->name, + ], + Db::$db->connection, + ); + + if (Db::$db->select(Db::$db->prefix . Db::$db->name, Db::$db->connection)) { + Db::$db->name = Db::$db->prefix . Db::$db->name; + Config::updateSettingsFile(['db_name' => Db::$db->name]); + } + } + + // Okay, now let's try to connect... + if (!Db::$db->select(Db::$db->name, Db::$db->connection)) { + Maintenance::$fatal_error = Lang::getTxt('error_db_database', ['db_name' => Db::$db->name]); + + return false; + } + } + + // Everything looks good, lets get on with it. + return true; + } + + /** + * Forum Settings action. + * + * @return bool True if we can continue, false otherwise. + */ + public function forumSettings(): bool + { + // Let's see if we got the database type correct. + if (isset($_POST['db_type'], $this->supportedDatabases()[$_POST['db_type']])) { + Config::$db_type = $_POST['db_type']; + + try { + if (!Config::updateSettingsFile(['db_type' => Config::$db_type])) { + throw new \Exception(); + } + } catch (\Throwable $e) { + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + + return false; + } + + Config::load(); + } + + // We'd better be able to get the connection. + Db::load(); + + // Now, to put what we've learned together... and add a path. + Maintenance::$context['detected_url'] = 'http' . (Sapi::httpsOn() ? 's' : '') . '://' . $this->defaultHost() . substr(Maintenance::getSelf(), 0, strrpos(Maintenance::getSelf(), '/')); + + // Check if the database sessions will even work. + Maintenance::$context['test_dbsession'] = (ini_get('session.auto_start') != 1); + + Maintenance::$context['continue'] = true; + + // We have a failure of database configuration. + try { + if (!Db::$db->checkConfiguration()) { + Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Install'); + + return false; + } + } catch (\Throwable $e) { + Maintenance::$fatal_error = $e->getMessage(); + + return false; + } + + // Setup the SSL checkbox... + Maintenance::$context['ssl_chkbx_protected'] = false; + Maintenance::$context['ssl_chkbx_checked'] = false; + + // If redirect in effect, force SSL ON. + $url = new Url(Maintenance::$context['detected_url']); + + if ($url->redirectsToHttps()) { + Maintenance::$context['ssl_chkbx_protected'] = true; + Maintenance::$context['ssl_chkbx_checked'] = true; + $_POST['force_ssl'] = true; + } + + // If no cert, make sure SSL stays OFF. + if (!$url->hasSSL()) { + Maintenance::$context['ssl_chkbx_protected'] = true; + Maintenance::$context['ssl_chkbx_checked'] = false; + } + + // Submitting? + if (!isset($_POST['boardurl'])) { + return false; + } + + // Deal with different operating systems' directory structure... + $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', Maintenance::getBaseDir()), '/'); + + // Save these variables. + $vars = [ + 'boardurl' => $this->cleanBoardUrl($_POST['boardurl']), + 'boarddir' => $path, + 'sourcedir' => $path . '/Sources', + 'cachedir' => $path . '/cache', + 'packagesdir' => $path . '/Packages', + 'languagesdir' => $path . '/Languages', + 'mbname' => strtr($_POST['mbname'], ['\"' => '"']), + 'language' => Maintenance::getRequestedLanguage(), + 'image_proxy_secret' => $this->createImageProxySecret(), + 'image_proxy_enabled' => !empty($_POST['force_ssl']), + 'auth_secret' => $this->createAuthSecret(), + ]; + + try { + if (!Config::updateSettingsFile($vars)) { + throw new \Exception(); + } + } catch (\Throwable $e) { + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + + return false; + } + + // Update SMF\Config with the changes we just saved. + Config::load(); + + // Good, skip on. + return true; + } + + /** + * Database Population action. + * + * @return bool True if we can continue, false otherwise. + */ + public function databasePopulation(): bool + { + Maintenance::$context['continue'] = true; + + // Already done? + if (isset($_POST['pop_done'])) { + return true; + } + + // Reload settings. + Config::load(); + Db::load(); + $newSettings = []; + $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', Maintenance::getBaseDir()), '/'); + + // Before running any of the queries, let's make sure another version isn't already installed. + $result = Db::$db->query( + '', + 'SELECT variable, value + FROM {db_prefix}settings', + [ + 'db_error_skip' => true, + ], + ); + + if ($result !== false) { + while ($row = Db::$db->fetch_assoc($result)) { + Config::$modSettings[$row['variable']] = $row['value']; + } + + Db::$db->free_result($result); + + // Do they match? If so, this is just a refresh so charge on! + if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] != SMF_VERSION) { + Maintenance::$fatal_error = Lang::getTxt('error_versions_do_not_match', file: 'Install'); + + return false; + } + } + + Config::$modSettings['disableQueryCheck'] = true; + + $attachdir = $path . '/attachments'; + + $replaces = [ + '{$db_prefix}' => Db::$db->prefix, + '{$attachdir}' => json_encode([1 => Db::$db->escape_string($attachdir)]), + '{$boarddir}' => Db::$db->escape_string(Config::$boarddir), + '{$boardurl}' => Config::$boardurl, + '{$enableCompressedOutput}' => isset($_POST['compress']) ? '1' : '0', + '{$databaseSession_enable}' => isset($_POST['dbsession']) ? '1' : '0', + '{$smf_version}' => SMF_VERSION, + '{$current_time}' => time(), + '{$sched_task_offset}' => 82800 + mt_rand(0, 86399), + '{$registration_method}' => $_POST['reg_mode'] ?? 0, + ]; + + foreach (Lang::$txt as $key => $value) { + if (substr($key, 0, 8) == 'default_') { + $replaces['{$' . $key . '}'] = Db::$db->escape_string($value); + } + } + + $replaces['{$default_reserved_names}'] = strtr($replaces['{$default_reserved_names}'], ['\\\\n' => '\\n']); + + $existing_tables = Db::$db->list_tables(Config::$db_name, Config::$db_prefix); + + $install_tables = Table::getAll($this->schema_version); + + Maintenance::$context['sql_results'] = [ + 'tables' => 0, + 'inserts' => 0, + 'table_dups' => 0, + 'insert_dups' => 0, + ]; + + // $tables->seek(Maintenance::getCurrentSubStep()); + foreach ($install_tables as $tbl) { + if (in_array(Config::$db_prefix . $tbl->name, $existing_tables)) { + continue; + } + + $original_table = $tbl->name; + + try { + $result = $tbl->create(); + + if ($result) { + Maintenance::$context['sql_results']['tables']++; + } else { + Maintenance::$context['failures'][] = trim(Db::$db->error(Db::$db->connection)); + } + } catch (\Throwable $e) { + Maintenance::$context['failures'][] = trim($e->getMessage()); + } + + try { + if (!empty($tbl->initial_data)) { + $casts = []; + $insert_columns = []; + + foreach ($tbl->columns as $column) { + $casts[$column->name] = in_array($column->type, ['tinyint', 'smallint', 'mediumint', 'bigint', 'int', 'integer']) ? 'int' : 'string'; + } + + foreach ($tbl->initial_data as &$row) { + foreach ($row as $column => &$value) { + if (!isset($insert_columns[$column])) { + $insert_columns[$column] = $casts[$column]; + } + + settype($value, $casts[$column]); + + if (is_string($value)) { + $value = strtr($value, $replaces); + } + } + } + + $result = Db::$db->insert( + 'replace', + '{db_prefix}' . $tbl->name, + $insert_columns, + $tbl->initial_data, + array_keys($insert_columns), + ); + + if ($result || $result === null) { + Maintenance::$context['sql_results']['tables']++; + } else { + Maintenance::$context['failures'][] = $tbl->name . ':' . trim(Db::$db->error(Db::$db->connection)); + } + } + } catch (\Throwable $e) { + Maintenance::$context['failures'][] = trim($e->getMessage()); + } + + // Wait, wait, I'm still working here! + Sapi::setTimeLimit(60); + } + + // Sort out the context for the SQL. + foreach (Maintenance::$context['sql_results'] as $key => $number) { + if ($number === 0) { + unset(Maintenance::$context['sql_results'][$key]); + } else { + Maintenance::$context['sql_results'][$key] = Lang::getTxt('db_populate_' . $key, [$number]); + } + } + + $this->toggleSmStats($newSettings); + + // Are we enabling SSL? + if (!empty($_POST['force_ssl'])) { + $newSettings['force_ssl'] = 1; + } + + // Setting a timezone is required. + $newSettings['default_timezone'] = $this->determineTimezone() ?? 'UTC'; + + if (!empty($newSettings)) { + Config::updateModSettings($newSettings); + } + + // Setup smileys. + $this->populateSmileys(); + + // Let's optimize those new tables, but not on InnoDB, ok? (SMF will check this) + foreach ($install_tables as $tbl) { + $tbl->name = Config::$db_prefix . $tbl->name; + + try { + if (!(Db::$db->optimize_table($tbl->name) > -1)) { + Maintenance::$context['failures'][] = Db::$db->error(Db::$db->connection); + } + } catch (\Throwable $e) { + Maintenance::$context['failures'][] = $e->getMessage(); + } + } + + // Find out if we have permissions we didn't use, but will need for the future. + // @@ TODO: This was at this location in the original code, it should come earlier. + if (!Db::$db->hasPermissions()) { + Maintenance::$fatal_error = Lang::getTxt('error_db_alter_priv', file: 'Install'); + } + + // Was this a refresh? + if (count($existing_tables) > 0) { + $this->page_title = Lang::getTxt('user_refresh_install', file: 'Install'); + Maintenance::$context['was_refresh'] = true; + } + + return false; + } + + /** + * Admin Account action. + * + * @return bool True if we can continue, false otherwise. + */ + public function adminAccount(): bool + { + Maintenance::$context['continue'] = true; + + // Skipping? + if (!empty($_POST['skip'])) { + return true; + } + + // Need this to check whether we need the database password. + Config::load(); + Db::load(); + + $settingsDefs = Config::getSettingsDefs(); + + // Reload $modSettings. + Config::reloadModSettings(); + + Maintenance::$context['username'] = htmlspecialchars($_POST['username'] ?? ''); + Maintenance::$context['email'] = htmlspecialchars($_POST['email'] ?? ''); + Maintenance::$context['server_email'] = htmlspecialchars($_POST['server_email'] ?? ''); + + Maintenance::$context['require_db_confirm'] = empty(Config::$db_type); + + // Only allow skipping if we think they already have an account setup. + $request = Db::$db->query( + '', + 'SELECT id_member + FROM {db_prefix}members + WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0 + LIMIT 1', + [ + 'db_error_skip' => true, + 'admin_group' => 1, + ], + ); + + if (Db::$db->num_rows($request) != 0) { + Maintenance::$context['skip'] = true; + + return false; + } + Db::$db->free_result($request); + + // Trying to create an account? + if (!isset($_POST['password1']) || empty($_POST['contbutt'])) { + return false; + } + + $_POST['username'] ??= ''; + $_POST['email'] ??= ''; + $_POST['password2'] ??= ''; + $_POST['password3'] ??= ''; + + // Wrong password? + if (Maintenance::$context['require_db_confirm'] && $_POST['password3'] != Config::$db_passwd) { + Maintenance::$fatal_error = Lang::getTxt('error_db_connect', file: 'Install'); + + return false; + } + + // Not matching passwords? + if ($_POST['password1'] != $_POST['password2']) { + Maintenance::$fatal_error = Lang::getTxt('error_user_settings_again_match', file: 'Install'); + + return false; + } + + // No password? + if (strlen($_POST['password1']) < 4) { + Maintenance::$fatal_error = Lang::getTxt('error_user_settings_no_password', file: 'Install'); + + return false; + } + + if (!file_exists(Config::$sourcedir . '/Utils.php')) { + Maintenance::$fatal_error = Lang::getTxt('error_sourcefile_missing', ['file' => 'Utils.php']); + + return false; + } + + // Update the webmaster's email? + if (!empty($_POST['server_email']) && (empty(Config::$webmaster_email) || Config::$webmaster_email == $settingsDefs['webmaster_email']['default'])) { + Config::updateSettingsFile(['webmaster_email' => (string) $_POST['server_email']]); + } + + // Work out whether we're going to have dodgy characters and remove them. + $invalid_characters = preg_match('~[<>&"\'=\\\]~', $_POST['username']) != 0; + $_POST['username'] = preg_replace('~[<>&"\'=\\\]~', '', $_POST['username']); + + $result = Db::$db->query( + '', + 'SELECT id_member, password_salt + FROM {db_prefix}members + WHERE member_name = {string:username} OR email_address = {string:email} + LIMIT 1', + [ + 'username' => $_POST['username'], + 'email' => $_POST['email'], + 'db_error_skip' => true, + ], + ); + + if (Db::$db->num_rows($result) != 0) { + list(Maintenance::$context['member_id'], Maintenance::$context['member_salt']) = Db::$db->fetch_row($result); + Db::$db->free_result($result); + + Maintenance::$context['account_existed'] = Lang::getTxt('error_user_settings_taken', file: 'Install'); + } elseif ($_POST['username'] == '' || strlen($_POST['username']) > 25) { + // Try the previous step again. + Maintenance::$fatal_error = $_POST['username'] == '' ? Lang::getTxt('error_username_left_empty', file: 'Install') : Lang::getTxt('error_username_too_long', file: 'Install'); + + return false; + } elseif ($invalid_characters || $_POST['username'] == '_' || $_POST['username'] == '|' || strpos($_POST['username'], '[code') !== false || strpos($_POST['username'], '[/code') !== false) { + // Try the previous step again. + Maintenance::$fatal_error = Lang::getTxt('error_invalid_characters_username', file: 'Install'); + + return false; + } elseif (empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) || strlen($_POST['email']) > 255) { + // One step back, this time fill out a proper admin email address. + Maintenance::$fatal_error = Lang::getTxt('error_valid_admin_email_needed', file: 'Install'); + + return false; + } elseif (empty($_POST['server_email']) || !filter_var($_POST['server_email'], FILTER_VALIDATE_EMAIL) || strlen($_POST['server_email']) > 255) { + // One step back, this time fill out a proper admin email address. + Maintenance::$fatal_error = Lang::getTxt('error_valid_server_email_needed', file: 'Install'); + + return false; + } elseif ($_POST['username'] != '') { + Maintenance::$context['member_salt'] = bin2hex(random_bytes(16)); + + // Format the username properly. + $_POST['username'] = preg_replace('~[\t\n\r\x0B\0\xA0]+~', ' ', $_POST['username']); + $ip = isset($_SERVER['REMOTE_ADDR']) ? substr($_SERVER['REMOTE_ADDR'], 0, 255) : ''; + + $_POST['password1'] = Security::hashPassword($_POST['password1']); + + try { + Maintenance::$context['member_id'] = Db::$db->insert( + '', + Db::$db->prefix . 'members', + [ + 'member_name' => 'string-25', + 'real_name' => 'string-25', + 'passwd' => 'string', + 'email_address' => 'string', + 'id_group' => 'int', + 'posts' => 'int', + 'date_registered' => 'int', + 'password_salt' => 'string', + 'lngfile' => 'string', + 'personal_text' => 'string', + 'avatar' => 'string', + 'member_ip' => 'inet', + 'member_ip2' => 'inet', + 'buddy_list' => 'string', + 'pm_ignore_list' => 'string', + 'website_title' => 'string', + 'website_url' => 'string', + 'signature' => 'string', + 'usertitle' => 'string', + 'secret_question' => 'string', + 'additional_groups' => 'string', + 'ignore_boards' => 'string', + ], + [ + [ + $_POST['username'], + $_POST['username'], + $_POST['password1'], + $_POST['email'], + 1, + 0, + time(), + Maintenance::$context['member_salt'], + '', + '', + '', + $ip, + $ip, + '', + '', + '', + '', + '', + '', + '', + '', + '', + ], + ], + ['id_member'], + 1, + ); + + if ((int) Maintenance::$context['member_id'] > 0) { + return true; + } + + Maintenance::$fatal_error = trim(Db::$db->error(Db::$db->connection)); + + return false; + + } catch (\Throwable $e) { + Maintenance::$fatal_error = $e->getMessage(); + var_dump(Maintenance::$fatal_error); + } + } + + return false; + } + + /** + * Delete Install action. + * + * @return bool True if we can continue, false otherwise. + */ + public function finalize(): bool + { + Maintenance::$context['continue'] = false; + + // Rebuild the settings file. + Config::updateSettingsFile(['upgradeData' => ''], false, true); + + Config::load(); + Db::load(); + + chdir(Config::$boarddir); + + // Reload $modSettings. + Config::reloadModSettings(); + + // Bring a warning over. + if (!empty(Maintenance::$context['account_existed'])) { + Maintenance::$warnings = Maintenance::$context['account_existed']; + } + + // As track stats is by default enabled let's add some activity. + Db::$db->insert( + 'ignore', + '{db_prefix}log_activity', + [ + 'date' => 'date', + 'topics' => 'int', + 'posts' => 'int', + 'registers' => 'int', + ], + [ + [ + Time::strftime('%Y-%m-%d', time()), + 1, + 1, + !empty(Maintenance::$context['member_id']) ? 1 : 0, + ], + ], + ['date'], + ); + + // We're going to want our lovely Config::$modSettings now. + $request = Db::$db->query( + '', + 'SELECT variable, value + FROM {db_prefix}settings', + [ + 'db_error_skip' => true, + ], + ); + + // Only proceed if we can load the data. + if ($request) { + while ($row = Db::$db->fetch_row($request)) { + Config::$modSettings[$row[0]] = $row[1]; + } + Db::$db->free_result($request); + } + + // Automatically log them in ;) + if (isset(Maintenance::$context['member_id'], Maintenance::$context['member_salt'])) { + Cookie::setLoginCookie(3153600 * 60, Maintenance::$context['member_id'], Cookie::encrypt($_POST['password1'], Maintenance::$context['member_salt'])); + } + + $result = Db::$db->query( + '', + 'SELECT value + FROM {db_prefix}settings + WHERE variable = {string:db_sessions}', + [ + 'db_sessions' => 'databaseSession_enable', + 'db_error_skip' => true, + ], + ); + + if (Db::$db->num_rows($result) != 0) { + list($db_sessions) = Db::$db->fetch_row($result); + } + Db::$db->free_result($result); + + if (empty($db_sessions)) { + $_SESSION['admin_time'] = time(); + } else { + $_SERVER['HTTP_USER_AGENT'] = substr($_SERVER['HTTP_USER_AGENT'], 0, 211); + + Db::$db->insert( + 'replace', + '{db_prefix}sessions', + [ + 'session_id' => 'string', + 'last_update' => 'int', + 'data' => 'string', + ], + [ + [ + session_id(), + time(), + 'USER_AGENT|s:' . strlen($_SERVER['HTTP_USER_AGENT']) . ':"' . $_SERVER['HTTP_USER_AGENT'] . '";admin_time|i:' . time() . ';', + ], + ], + ['session_id'], + ); + } + + Logging::updateStats('member'); + Logging::updateStats('message'); + Logging::updateStats('topic'); + + $request = Db::$db->query( + '', + 'SELECT id_msg + FROM {db_prefix}messages + WHERE id_msg = 1 + AND modified_time = 0 + LIMIT 1', + [ + 'db_error_skip' => true, + ], + ); + Utils::$context['utf8'] = true; + + if (Db::$db->num_rows($request) > 0) { + Logging::updateStats('subject', 1, htmlspecialchars(Lang::getTxt('default_topic_subject', file: 'Install'))); + } + Db::$db->free_result($request); + + // Now is the perfect time to fetch the SM files. + // Sanity check that they loaded earlier! + if (isset(Config::$modSettings['recycle_board'])) { + (new TaskRunner())->runScheduledTasks(['fetchSMfiles']); // Now go get those files! + + // We've just installed! + $_SERVER['BAN_CHECK_IP'] = $_SERVER['REMOTE_ADDR']; + + if (isset(Maintenance::$context['member_id'])) { + User::setMe(Maintenance::$context['member_id']); + } else { + User::load(); + } + + User::$me->ip = $_SERVER['REMOTE_ADDR']; + + Logging::logAction('install', ['version' => SMF_FULL_VERSION], 'admin'); + } + + // Disable the legacy BBC by default for new installs + Config::updateModSettings([ + 'disabledBBC' => implode(',', Utils::$context['legacy_bbc']), + ]); + + // Some final context for the template. + Maintenance::$context['dir_still_writable'] = is_writable(Config::$boarddir); + Maintenance::$context['probably_delete_install'] = isset($_SESSION['installer_temp_ftp']) || is_writable(Config::$boarddir) || is_writable(Config::$boarddir . '/' . $this->script_name); + + // Update hash's cost to an appropriate setting + Config::updateModSettings([ + 'bcrypt_hash_cost' => Security::hashBenchmark(), + ]); + + return false; + } + + /** + * Write out our current information to our settings file to track the upgrade progress. + */ + public function preExit(): void + { + $this->saveUpgradeData(); + } + + /****************** + * Internal methods + ******************/ + + /** + * Create an .htaccess file to prevent mod_security. SMF has filtering built-in. + * + * @return bool True if we could create the file or do not need to. False if this failed. + */ + private function checkAndTryToFixModSecurity(): bool + { + $htaccess_addition = ' + + # Turn off mod_security filtering. SMF is a big boy, it doesn\'t need its hands held. + SecFilterEngine Off + + # The below probably isn\'t needed, but better safe than sorry. + SecFilterScanPOST Off + '; + + if (!function_exists('apache_get_modules') || !in_array('mod_security', apache_get_modules())) { + return true; + } + + if (file_exists(Config::$boarddir . '/.htaccess') && is_writable(Config::$boarddir . '/.htaccess')) { + $current_htaccess = implode('', file(Config::$boarddir . '/.htaccess')); + + // Only change something if mod_security hasn't been addressed yet. + if (strpos($current_htaccess, '') === false) { + if ($ht_handle = fopen(Config::$boarddir . '/.htaccess', 'a')) { + fwrite($ht_handle, $htaccess_addition); + fclose($ht_handle); + + return true; + } + + return false; + } + + return true; + } + + if (file_exists(Config::$boarddir . '/.htaccess')) { + return strpos(implode('', file(Config::$boarddir . '/.htaccess')), '') !== false; + } + + if (is_writable(Config::$boarddir)) { + if ($ht_handle = fopen(Config::$boarddir . '/.htaccess', 'w')) { + fwrite($ht_handle, $htaccess_addition); + fclose($ht_handle); + + return true; + } + + return false; + } + + return false; + } + + /** + * Creates a unique cookie name based on some inputs. + * + * @param string $db_name The database named provided by Config::$db_name. + * @param string $db_prefix The database prefix provided by Config::$db_prefix. + * @return string The cookie name. + */ + private function createCookieName(string $db_name, string $db_prefix): string + { + return 'SMFCookie' . abs(crc32($db_name . preg_replace('~[^A-Za-z0-9_$]~', '', $db_prefix)) % 1000); + } + + /** + * Generates a Config::$auth_secret string. + * + * @return string a cryptographic string. + */ + private function createAuthSecret(): string + { + return bin2hex(random_bytes(32)); + } + + /** + * Generates a Config::$image_proxy_secret string. + * + * @return string a cryptographic string. + */ + private function createImageProxySecret(): string + { + return bin2hex(random_bytes(10)); + } + + /** + * Get our upgrade data. + */ + private function getUpgradeData(): void + { + $defined_vars = Config::getCurrentSettings(); + + $data = isset($defined_vars['upgradeData']) ? Utils::jsonDecode($defined_vars['upgradeData'], true) : []; + + $this->time_started = isset($data['started']) ? (int) $data['started'] : time(); + } + + /** + * Save our data. + * + * @return bool True if we could update our settings file, false otherwise. + */ + private function saveUpgradeData(): bool + { + return Config::updateSettingsFile(['upgradeData' => json_encode([ + 'started' => $this->time_started, + ])]); + } + + /** + * Determine the default host, used during install to populate Config::$boardurl. + * + * @return string The host we have determined to be on. + */ + private function defaultHost(): string + { + return empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST']; + } + + /** + * Given a board url, this will clean up some mistakes and other errors. + * + * @param string $boardurl Input boardurl + * @return string Returned board url. + */ + private function cleanBoardUrl(string $boardurl): string + { + if (substr($boardurl, -10) == '/index.php') { + $boardurl = substr($boardurl, 0, -10); + } elseif (substr($boardurl, -1) == '/') { + $boardurl = substr($boardurl, 0, -1); + } + + if (substr($boardurl, 0, 7) != 'http://' && substr($boardurl, 0, 7) != 'file://' && substr($boardurl, 0, 8) != 'https://') { + $boardurl = 'http://' . $boardurl; + } + + // Make sure boardurl is aligned with ssl setting + if (empty($_POST['force_ssl'])) { + $boardurl = strtr($boardurl, ['https://' => 'http://']); + } else { + $boardurl = strtr($boardurl, ['http://' => 'https://']); + } + + // Make sure international domain names are normalized correctly. + $boardurl = (string) new Url($boardurl, true); + + return $boardurl; + } + + /** + * Determine if we need to enable or disable (during upgrades) SMF stat collection. + * + * @param array $settings Settings array, passed by reference. + */ + private function toggleSmStats(array &$settings): void + { + if ( + !empty($_POST['stats']) + && substr(Config::$boardurl, 0, 16) != 'http://localhost' + && empty(Config::$modSettings['allow_sm_stats']) + && empty(Config::$modSettings['enable_sm_stats']) + ) { + Maintenance::$context['allow_sm_stats'] = true; + + // Attempt to register the site etc. + $fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr); + + if (!$fp) { + $fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr); + } + + if (!$fp) { + return; + } + + $out = 'GET /smf/stats/register_stats.php?site=' . base64_encode(Config::$boardurl) . ' HTTP/1.1' . "\r\n"; + $out .= 'Host: www.simplemachines.org' . "\r\n"; + $out .= 'Connection: Close' . "\r\n\r\n"; + fwrite($fp, $out); + + $return_data = ''; + + while (!feof($fp)) { + $return_data .= fgets($fp, 128); + } + + fclose($fp); + + // Get the unique site ID. + preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID); + + if (!empty($ID[1])) { + $settings['sm_stats_key'] = $ID[1]; + $settings['enable_sm_stats'] = 1; + } + } + // Don't remove stat collection unless we unchecked the box for real, not from the loop. + elseif (empty($_POST['stats']) && empty(Maintenance::$context['allow_sm_stats'])) { + $settings['enable_sm_stats'] = null; + } + } + + /** + * Attempt to determine what our time zone is. If this can't be determined we return nothing. + * + * @return null|string A valid time zone or nothing. + */ + private function determineTimezone(): ?string + { + if (isset(Config::$modSettings['default_timezone']) || !function_exists('date_default_timezone_set')) { + return null; + } + + // Get PHP's default timezone, if set + $ini_tz = ini_get('date.timezone'); + + if (!empty($ini_tz)) { + $timezone_id = $ini_tz; + } else { + $timezone_id = ''; + } + + // If date.timezone is unset, invalid, or just plain weird, make a best guess + if (!in_array($timezone_id, timezone_identifiers_list())) { + $server_offset = @mktime(0, 0, 0, 1, 1, 1970) * -1; + $timezone_id = timezone_name_from_abbr('', $server_offset, 0); + + if (empty($timezone_id)) { + $timezone_id = 'UTC'; + } + } + + if (date_default_timezone_set($timezone_id)) { + return $timezone_id; + } + + return null; + } + + /** + * Populating smileys are a bit complicated, so its performed here rather than inline. + * + */ + private function populateSmileys(): void + { + // Populate the smiley_files table. + // Can't just dump this data in the SQL file because we need to know the id for each smiley. + $smiley_filenames = [ + ':)' => 'smiley', + ';)' => 'wink', + ':D' => 'cheesy', + ';D' => 'grin', + '>:(' => 'angry', + ':(' => 'sad', + ':o' => 'shocked', + '8)' => 'cool', + '???' => 'huh', + '::)' => 'rolleyes', + ':P' => 'tongue', + ':-[' => 'embarrassed', + ':-X' => 'lipsrsealed', + ':-\\' => 'undecided', + ':-*' => 'kiss', + ':\'(' => 'cry', + '>:D' => 'evil', + '^-^' => 'azn', + 'O0' => 'afro', + ':))' => 'laugh', + 'C:-)' => 'police', + 'O:-)' => 'angel', + ]; + $smiley_set_extensions = ['fugue' => '.png', 'alienine' => '.png']; + + $smiley_inserts = []; + $request = Db::$db->query( + '', + 'SELECT id_smiley, code + FROM {db_prefix}smileys', + [], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + foreach ($smiley_set_extensions as $set => $ext) { + $smiley_inserts[] = [$row['id_smiley'], $set, $smiley_filenames[$row['code']] . $ext]; + } + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}smiley_files', + ['id_smiley' => 'int', 'smiley_set' => 'string-48', 'filename' => 'string-48'], + $smiley_inserts, + ['id_smiley', 'smiley_set'], + ); + } +} diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php new file mode 100644 index 00000000000..6125001e5b2 --- /dev/null +++ b/Sources/Maintenance/Tools/ToolsBase.php @@ -0,0 +1,438 @@ + (Object for DatabaseApi) $db + */ + public function supportedDatabases(): array + { + static $dbs = []; + + if (count($dbs) > 0) { + return $dbs; + } + + if (!file_exists(Config::$sourcedir . '/Db/APIs')) { + return $dbs; + } + + $dir = dir(Config::$sourcedir . '/Db/APIs'); + + while ($entry = $dir->read()) { + if ($entry == 'index.php' || substr($entry, -4) !== '.php') { + continue; + } + + $db_class = '\\SMF\\Db\\APIs\\' . substr($entry, 0, -4); + $db = new $db_class(); + + if (!($db instanceof \SMF\Db\DatabaseApi) || !$db->isSupported()) { + continue; + } + + $dbs[$db->title] = $db; + } + + ksort($dbs); + + return $dbs; + } + + /** + * Last chance to do anything before we exit. + * + * Some tools may call this to save their progress, etc. + */ + public function preExit(): void {} + + /** + * Given a database type, loads the maintenance database object. + * + * @param string $db_type The database type, typically from Config::$db_type. + * @return Db The database object. + */ + public function loadMaintenanceDatabase(string $db_type): Db + { + $db_class = '\\SMF\\Db\\APIs\\' . Db::getClass(Config::$db_type); + + require_once Config::$sourcedir . '/Db/APIs/' . Db::getClass(Config::$db_type) . '.php'; + + return new $db_class(); + } + + /** + * Used by various places to determine if the tool is in debug mode or not. + * + * @return bool + */ + public function isDebug(): bool + { + return $this->debug ?? false; + } + + /** + * Delete the tool. + * + * This is typically called with a ?delete. + * + * No output is returned. Upon successful deletion, the browser is + * redirected to a blank file. + */ + public function deleteTool(): void + { + if ( + !empty($this->script_name) + && file_exists(Config::$boarddir . '/' . $this->script_name) + ) { + if (!empty($_SESSION['ftp'])) { + $ftp = new FtpConnection($_SESSION['ftp']['server'], $_SESSION['ftp']['port'], $_SESSION['ftp']['username'], $_SESSION['ftp']['password']); + $ftp->chdir($_SESSION['ftp']['path']); + $ftp->unlink($this->script_name); + $ftp->close(); + + unset($_SESSION['ftp']); + } else { + @unlink(Config::$boarddir . '/' . $this->script_name); + } + + // Now just redirect to a blank.png... + header('location: http' . (Sapi::httpsOn() ? 's' : '') . '://' . ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png'); + } + } + + /** + * Make file writable. First try to use regular chmod, but if that fails, try to use FTP. + * + * @param string $file file to make writable. + * @return bool True if succesfull, false otherwise. + */ + final public function quickFileWritable(string $file): bool + { + $files = [$file]; + + return $this->makeFilesWritable($files); + } + + /** + * Make files writable. First try to use regular chmod, but if that fails, try to use FTP. + * + * @param array $files List of files to make writable. + * @return bool True if succesfull, false otherwise. + */ + final public function makeFilesWritable(array &$files): bool + { + if (empty($files)) { + return true; + } + + $failure = false; + + // On linux, it's easy - just use is_writable! + // Windows is trickier. Let's try opening for r+... + if (Sapi::isOS(Sapi::OS_WINDOWS)) { + foreach ($files as $k => $file) { + // Folders can't be opened for write... but the index.php in them can ;). + if (is_dir($file)) { + $file .= '/index.php'; + } + + // Funny enough, chmod actually does do something on windows - it removes the read only attribute. + @chmod($file, 0777); + $fp = @fopen($file, 'r+'); + + // Hmm, okay, try just for write in that case... + if (!$fp) { + $fp = @fopen($file, 'w'); + } + + if (!$fp) { + $failure = true; + } else { + unset($files[$k]); + } + @fclose($fp); + } + } else { + foreach ($files as $k => $file) { + // Some files won't exist, try to address up front + if (!file_exists($file)) { + @touch($file); + } + + // NOW do the writable check... + if (!is_writable($file)) { + @chmod($file, 0755); + + // Well, 755 hopefully worked... if not, try 777. + if (!is_writable($file) && !@chmod($file, 0777)) { + $failure = true; + } + // Otherwise remove it as it's good! + else { + unset($files[$k]); + } + } else { + unset($files[$k]); + } + } + } + + if (empty($files)) { + return true; + } + + if (!isset($_SERVER)) { + return !$failure; + } + + // What still needs to be done? + Maintenance::$context['chmod_files'] = $files; + + // If it's windows it's a mess... + if ($failure && Sapi::isOS(Sapi::OS_WINDOWS)) { + Maintenance::$context['chmod']['ftp_error'] = 'total_mess'; + + return false; + } + + // We're going to have to use... FTP! + if ($failure) { + // Load any session data we might have... + if (!isset($_POST['ftp_username']) && isset($_SESSION['temp_ftp'])) { + Maintenance::$context['chmod']['server'] = $_SESSION['temp_ftp']['server']; + Maintenance::$context['chmod']['port'] = $_SESSION['temp_ftp']['port']; + Maintenance::$context['chmod']['username'] = $_SESSION['temp_ftp']['username']; + Maintenance::$context['chmod']['password'] = $_SESSION['temp_ftp']['password']; + Maintenance::$context['chmod']['path'] = $_SESSION['temp_ftp']['path']; + } + // Or have we submitted? + elseif (isset($_POST['ftp_username'])) { + Maintenance::$context['chmod']['server'] = $_POST['ftp_server']; + Maintenance::$context['chmod']['port'] = $_POST['ftp_port']; + Maintenance::$context['chmod']['username'] = $_POST['ftp_username']; + Maintenance::$context['chmod']['password'] = $_POST['ftp_password']; + Maintenance::$context['chmod']['path'] = $_POST['ftp_path']; + } + + if (isset(Maintenance::$context['chmod']['username'])) { + $ftp = new FtpConnection(Maintenance::$context['chmod']['server'], Maintenance::$context['chmod']['port'], Maintenance::$context['chmod']['username'], Maintenance::$context['chmod']['password']); + + if ($ftp->error === false) { + // Try it without /home/abc just in case they messed up. + if (!$ftp->chdir(Maintenance::$context['chmod']['path'])) { + Maintenance::$context['chmod']['ftp_error'] = $ftp->last_message; + $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', Maintenance::$context['chmod']['path'])); + } + } + } + + if (!isset($ftp) || $ftp->error !== false) { + if (!isset($ftp)) { + $ftp = new FtpConnection(null); + } + // Save the error so we can mess with listing... + elseif ( + $ftp->error !== false + && !isset(Maintenance::$context['chmod']['ftp_error']) + ) { + Maintenance::$context['chmod']['ftp_error'] = $ftp->last_message === null ? '' : $ftp->last_message; + } + + list($username, $detect_path, $found_path) = $ftp->detect_path(dirname(__FILE__)); + + if ($found_path || !isset(Maintenance::$context['chmod']['path'])) { + Maintenance::$context['chmod']['path'] = $detect_path; + } + + if (!isset(Maintenance::$context['chmod']['username'])) { + Maintenance::$context['chmod']['username'] = $username; + } + + // Don't forget the login token. + Maintenance::$context += SecurityToken::create('login'); + + return false; + } + + // We want to do a relative path for FTP. + if (!in_array(Maintenance::$context['chmod']['path'], ['', '/'])) { + $ftp_root = strtr(Config::$boarddir, [Maintenance::$context['chmod']['path'] => '']); + + if (substr($ftp_root, -1) == '/' && (Maintenance::$context['chmod']['path'] == '' || Maintenance::$context['chmod']['path'][0] === '/')) { + $ftp_root = substr($ftp_root, 0, -1); + } + } else { + $ftp_root = Config::$boarddir; + } + + // Save the info for next time! + $_SESSION['temp_ftp'] = [ + 'server' => Maintenance::$context['chmod']['server'], + 'port' => Maintenance::$context['chmod']['port'], + 'username' => Maintenance::$context['chmod']['username'], + 'password' => Maintenance::$context['chmod']['password'], + 'path' => Maintenance::$context['chmod']['path'], + 'root' => $ftp_root, + ]; + + foreach ($files as $k => $file) { + if (!is_writable($file)) { + $ftp->chmod($file, 0755); + } + + if (!is_writable($file)) { + $ftp->chmod($file, 0777); + } + + // Assuming that didn't work calculate the path without the boarddir. + if (!is_writable($file)) { + if (strpos($file, Config::$boarddir) === 0) { + $ftp_file = strtr($file, [$_SESSION['installer_temp_ftp']['root'] => '']); + $ftp->chmod($ftp_file, 0755); + + if (!is_writable($file)) { + $ftp->chmod($ftp_file, 0777); + } + // Sometimes an extra slash can help... + $ftp_file = '/' . $ftp_file; + + if (!is_writable($file)) { + $ftp->chmod($ftp_file, 0755); + } + + if (!is_writable($file)) { + $ftp->chmod($ftp_file, 0777); + } + } + } + + if (is_writable($file)) { + unset($files[$k]); + } + } + + $ftp->close(); + } + + // What remains? + Maintenance::$context['chmod']['files'] = $files; + + return (bool) (empty($files)); + } + + /** + * Takes a string in and cleans up issues with path entries. + * + * @param string $path Dirty path + * @return string Clean path + */ + final public function fixRelativePath(string $path): string + { + // Fix the . at the start, clear any duplicate slashes, and fix any trailing slash... + return addslashes(preg_replace(['~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'], [dirname(SMF_SETTINGS_FILE) . '$1', '/', '\\', ''], $path)); + } + + /** + * Detects languages installed in SMF's languages folder. + * + * @param array $key_files Language files that must exist in order to be + * considered a valid language. + * @return array List of valid languages in the format of $locale => $name + */ + final public function detectLanguages(array $key_files = ['General']): array + { + foreach (Lang::get(false) as $locale => $lang_info) { + $languages[$locale] = $lang_info['name']; + } + + return $languages; + } + + /** + * This will check if we need to handle a timeout, if so, it sets up data for the next round. + * + * @throws \ValueError + * @throws \Exception + */ + public function checkAndHandleTimeout(): void + { + if (!Maintenance::isOutOfTime()) { + return; + } + + // If this is not json, we need to do a few things. + if (!Maintenance::isJson()) { + // We're going to pause after this! + Maintenance::$context['pause'] = true; + + Maintenance::setQueryString(); + } + + Maintenance::exit(); + + throw new \Exception('Zombies!'); + } +} diff --git a/Sources/Maintenance/Tools/ToolsInterface.php b/Sources/Maintenance/Tools/ToolsInterface.php new file mode 100644 index 00000000000..f0551b0ac05 --- /dev/null +++ b/Sources/Maintenance/Tools/ToolsInterface.php @@ -0,0 +1,65 @@ +getSteps()) - 1 !== (int) Maintenance::getCurrentStep()) { + echo ' +
'; + } + } + + /** + * Lower template for installer. + */ + public static function lower(): void + { + if (!empty(Maintenance::$context['continue']) || !empty(Maintenance::$context['skip'])) { + echo ' +
'; + + if (!empty(Maintenance::$context['continue'])) { + echo ' + '; + } + + if (!empty(Maintenance::$context['skip'])) { + echo ' + '; + } + echo ' +
'; + } + + // Show the closing form tag and other data only if not in the last step + if (count(Maintenance::$tool->getSteps()) - 1 !== (int) Maintenance::getCurrentStep()) { + echo ' +
'; + } + } + + /** + * Welcome page for installer. + */ + public static function welcome(): void + { + echo ' + + +

', Lang::getTxt('install_welcome_desc', ['SMF_VERSION' => SMF_VERSION]), '

+ '; + + // Oh no! + if (!empty(Maintenance::$fatal_error) || count(Maintenance::$errors) > 0 || count(Maintenance::$warnings) > 0) { + MaintenanceTemplate::warningsAndErrors(); + } + + // For the latest version stuff. + echo ' + '; + } + + /** + * Check Files Writable page for installer. + */ + public static function checkFilesWritable(): void + { + echo ' +

', Lang::$txt['ftp_setup_why_info'], '

+
    +
  • ', implode('
  • +
  • ', Maintenance::$context['failed_files']), '
  • +
'; + + if (isset(Maintenance::$context['systemos'], Maintenance::$context['detected_path']) && Maintenance::$context['systemos'] == 'linux') { + echo ' +
+

', Lang::$txt['chmod_linux_info'], '

+ # chmod a+w ', implode(' ' . Maintenance::$context['detected_path'] . '/', Maintenance::$context['failed_files']), ''; + } + + // This is serious! + if (!empty(Maintenance::$fatal_error) || count(Maintenance::$errors) > 0 || count(Maintenance::$warnings) > 0) { + MaintenanceTemplate::warningsAndErrors(); + + return; + } + + echo ' +
+

', Lang::$txt['ftp_setup_info'], '

'; + + if (!empty(Maintenance::$context['ftp_errors'])) { + echo ' +
+ ', Lang::$txt['error_ftp_no_connect'], '

+ ', implode('
', Maintenance::$context['ftp_errors']), '
+
'; + } + + echo ' +
+
+
+ +
+
+
+ + +
+ +
', Lang::$txt['ftp_server_info'], '
+
+
+ +
+
+ +
', Lang::$txt['ftp_username_info'], '
+
+
+ +
+
+ +
', Lang::$txt['ftp_password_info'], '
+
+
+ +
+
+ +
', Maintenance::$context['ftp']['path_msg'], '
+
+
+
+ +
+
+ ', Lang::$txt['error_message_click'], ' ', Lang::$txt['ftp_setup_again']; + } + + /** + * Database Settings page for installer. + */ + public static function databaseSettings(): void + { + echo ' +

', Lang::$txt['db_settings_info'], '

'; + + MaintenanceTemplate::warningsAndErrors(); + + echo ' +
'; + + // More than one database type? + if (count(Maintenance::$context['databases']) > 1) { + echo ' +
+ +
+
+ +
', Lang::$txt['db_settings_type_info'], '
+
'; + } else { + echo ' +
+ +
'; + } + + echo ' +
+ +
+
+ +
', Lang::$txt['db_settings_server_info'], '
+
+
+ +
+
+ +
', Lang::$txt['db_settings_port_info'], '
+
+
+ +
+
+ +
', Lang::$txt['db_settings_username_info'], '
+
+
+ +
+
+ +
', Lang::$txt['db_settings_password_info'], '
+
+
+ +
+
+ +
+ ', Lang::$txt['db_settings_database_info'], ' + ', Lang::$txt['db_settings_database_info_note'], ' +
+
+
+ +
+
+ +
', Lang::$txt['db_settings_prefix_info'], '
+
+
'; + + // Toggles a warning related to db names in PostgreSQL + echo ' + '; + } + + /** + * Forum Settings page for installer. + */ + public static function forumSettings(): void + { + echo ' +

', Lang::$txt['install_settings_info'], '

'; + + MaintenanceTemplate::warningsAndErrors(); + + echo ' +
+
+ +
+
+ +
', Lang::$txt['install_settings_name_info'], '
+
+
+ +
+
+ +
', Lang::$txt['install_settings_url_info'], '
+
+
+ +
+
+ +
', Lang::$txt['install_settings_reg_mode_info'], '
+
+
', Lang::$txt['install_settings_compress'], ':
+
+ + +
', Lang::$txt['install_settings_compress_info'], '
+
+
', Lang::$txt['install_settings_dbsession'], ':
+
+ + +
', Maintenance::$context['test_dbsession'] ? Lang::$txt['install_settings_dbsession_info1'] : Lang::$txt['install_settings_dbsession_info2'], '
+
+
', Lang::$txt['install_settings_stats'], ':
+
+ + +
', Lang::$txt['install_settings_stats_info'], '
+
+
', Lang::$txt['force_ssl'], ':
+
+ + +
', Lang::$txt['force_ssl_info'], '
+
+
'; + + } + + /** + * Database Populate page for installer. + */ + public static function databasePopulation(): void + { + echo ' +

', !empty(Maintenance::$context['was_refresh']) ? Lang::$txt['user_refresh_install_desc'] : Lang::$txt['db_populate_info'], '

'; + + if (!empty(Maintenance::$context['sql_results'])) { + echo ' +
    +
  • ', implode('
  • ', Maintenance::$context['sql_results']), '
  • +
'; + } + + if (!empty(Maintenance::$context['failures'])) { + echo ' +
', Lang::$txt['error_db_queries'], '
+
    '; + + foreach (Maintenance::$context['failures'] as $line => $fail) { + echo ' +
  • ', nl2br(htmlspecialchars($fail)), '
  • '; + } + + echo ' +
'; + } + + echo ' +

', Lang::$txt['db_populate_info2'], '

'; + + MaintenanceTemplate::warningsAndErrors(); + + echo ' + '; + } + + /** + * Admin Account page for installer. + */ + public static function adminAccount(): void + { + echo ' +

', Lang::$txt['user_settings_info'], '

'; + + MaintenanceTemplate::warningsAndErrors(); + + echo ' +
+
+ +
+
+ +
', Lang::$txt['user_settings_username_info'], '
+
+
+ +
+
+ +
', Lang::$txt['user_settings_password_info'], '
+
+
+ +
+
+ +
', Lang::$txt['user_settings_again_info'], '
+
+
+ +
+
+ +
', Lang::$txt['user_settings_admin_email_info'], '
+
+
+ +
+
+ +
', Lang::$txt['user_settings_server_email_info'], '
+
+
'; + + if (Maintenance::$context['require_db_confirm']) { + echo ' +

', Lang::$txt['user_settings_database'], '

+

', Lang::$txt['user_settings_database_info'], '

+ +
+ +
'; + } + } + + /** + * Finalization page for installer. + */ + public static function finalize(): void + { + echo ' +

', Lang::$txt['congratulations_help'], '

'; + + MaintenanceTemplate::warningsAndErrors(); + + // Install directory still writable? + if (Maintenance::$context['dir_still_writable']) { + echo ' +

', Lang::$txt['still_writable'], '

'; + } + + // Don't show the box if it's like 99% sure it won't work :P. + if (Maintenance::$context['probably_delete_install']) { + echo ' + + '; + } + + echo ' +

', Lang::getTxt('go_to_your_forum', ['scripturl' => Config::$boardurl . '/index.php']), '

+
+ ', Lang::$txt['good_luck']; + } +} diff --git a/Themes/default/MaintenanceTemplate.php b/Themes/default/MaintenanceTemplate.php new file mode 100644 index 00000000000..5883b073e56 --- /dev/null +++ b/Themes/default/MaintenanceTemplate.php @@ -0,0 +1,266 @@ + + + + + + ', Maintenance::$tool->getPageTitle(), ' + + ', Lang::$txt['lang_rtl'] == '1' ? ' + ' : '', ' + + + + + +
+ +
'; + + // Have we got a language drop down - if so do it on the first step only. + if (!empty(Maintenance::$languages) && count(Maintenance::$languages) > 1 && Maintenance::getCurrentStep() == 0) { + echo ' +
+
+
+
+
+ + + +
+
+
+
+
+
'; + } + + echo ' +
+
+
+

', Lang::$txt['maintenance_progress'], '

+
    '; + + if (Maintenance::$tool->hasSteps()) { + foreach (Maintenance::$tool->getSteps() as $num => $step) { + echo ' + ', Lang::$txt['maintenance_step'], ' ', $step->getID(), ': ', $step->getName(), ''; + } + } + + echo ' +
+
+
+
+

' . Lang::$txt['maintenance_overall_progress'], '

+ ', Maintenance::$overall_percent, '% +
+
'; + + if (Maintenance::$total_substeps !== null) { + echo ' +
+

', Lang::$txt['maintenance_substep_progress'], '

+
+ ', Maintenance::getSubStepProgress(), '% +
'; + } + + echo ' +
+

', trim(strtr(Maintenance::$item_name, ['.' => ''])), '

+
+ ', Maintenance::getItemsProgress(), '% +
+ +
+ ', Maintenance::getTimeElapsed(), ' +
+
+
+

', Maintenance::$tool->getStepTitle(), '

+
'; + + } + + /** + * Footer template. + */ + public static function footer(): void + { + echo ' +
+
+
+
+
+
+ + + + '; + + } + + /** + * A simple errors display. + */ + public static function warningsAndErrors(): void + { + // Errors are very serious.. + if (!empty(Maintenance::$fatal_error)) { + echo ' +
+

', Lang::$txt['critical_error'], '

+ ', Maintenance::$fatal_error, ' +
'; + } elseif (!empty(Maintenance::$errors)) { + echo ' +
+

', Lang::$txt['critical_error'], '

+ ', implode(' + ', Maintenance::$errors), ' +
'; + } + // A warning message? + elseif (!empty(Maintenance::$warnings)) { + echo ' +
+

', Lang::$txt['warning'], '

+ ', implode(' + ', Maintenance::$warnings), ' +
'; + } + } + + /** + * Shows a fatal error message if we are missing language files. + */ + public static function missingLanguages(): void + { + // Let's not cache this message, eh? + header('expires: Mon, 26 Jul 1997 05:00:00 GMT'); + header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + header('cache-control: no-cache'); + + echo ' + + + SMF Installer: Error! + + + +

A critical error has occurred.

+ +

This installer was unable to find this tools\'s language file or files. They should be found under:

+ +
', dirname(Maintenance::getSelf()) != '/' ? dirname(Maintenance::getSelf()) : '', '/Languages
+ +

In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you have uploaded all the files in the distribution.

+

If that doesn\'t help, please make sure this install.php file is in the same place as the Themes folder.

+

If you continue to get this error message, feel free to look to us for support.

+ +'; + + die; + } +} diff --git a/Themes/default/css/maintenance.css b/Themes/default/css/maintenance.css new file mode 100644 index 00000000000..016f9c2e9cb --- /dev/null +++ b/Themes/default/css/maintenance.css @@ -0,0 +1,180 @@ +a:link, a:hover, a:visited { + text-decoration: underline; +} +/* These divisions wrap the forum sections when a forum width is set. */ +h1.forumtitle { + color: #a85400; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.5), 1px 1px 0 #fff; +} + +#inner_wrap { + min-height: 80px; +} +#inner_wrap .news { + height: 80px; + text-align: right; + width: 100%; + max-width: 100%; + float: left; +} +#main_content_section { + display: flex; + flex-wrap: wrap; +} +/* Add the gradient here if there is no upper_section */ +#main_content_section:first-child { + margin: 2px; + padding-top: 1em; + border-radius: 6px; + background: linear-gradient(to bottom, #e2e9f3 0%, #fff 90px); +} +#main_steps { + order: 0; + width: 49%; +} +#main_steps h2 { + font-size: 1.2em; + border-bottom: 1px solid #d05800; + line-height: 1.6em; + margin: 0 1em 0.5em 0; + color: #d05800; +} +ul.steps_list { + line-height: 1.8em; + color: #c2c2c2; +} +ul.steps_list li.stepcurrent { + color: #222; + font-weight: bold; +} +ul.steps_list .stepcurrent ~ li { + color: #666; +} +#install_progress { + order: -1; + margin: 0.32em 2% 0 0; + width: 49%; +} +.progress_bar { + line-height: 2em; + max-width: 500px; +} +.progress_bar + h3, .progress_bar + .progress_bar { + margin-top: 1.5em; +} +.progress_bar h3 { + position: absolute; + font-weight: normal; + font-size: 1.1em; + left: 0.5em; + z-index: 3; + text-shadow: 1px 1px rgba(255, 255, 255, .4); +} +#substep_bar_div { + line-height: 1.6em; +} +#substep_progress { + background-color: #eebaf4; +} +.time_elapsed { + margin-top: 1em; +} +#main_screen { + width: 100%; + margin-top: 2em; +} +#main_screen h2 { + font-size: 1.2em; + border-bottom: 1px solid #d05800; + line-height: 1.6em; + margin: 0 0 0.5em 0; + color: #d05800; +} +.panel form div { + max-height: 560px; +} +.panel p, .panel h3, .panel ul { + margin: 0 0 1em 0; +} +.error { + padding: 0.5em 0; +} +.panel .button { + font-weight: bold; +} +.panel .button:enabled:hover { + color: #af6700; + text-decoration: none; +} +.panel .clear { + padding: 1em 0 0 0; + overflow: auto; +} +.upgrade_settings li + li { + margin-top: 0.5em; +} +.buttons { + margin-top: 1em; +} +#commess, #indexmsg { + font-weight: bold; +} +#indexmsg { + font-style: italic; +} +.error_content { + margin: 2.5ex; + font-family: monospace; +} +#debug_section { + overflow: auto; + max-height: 8.4em; /* 6 lines of text */ + line-height: 1.4em; +} +dl.settings dt, dl.settings dd { + width: 50%; +} +dl.settings.adminlogin dt { + width: 20ch; +} +dl.settings.adminlogin dd { + width: calc(100% - 20ch); +} +@media (max-width: 480px) { + dl.settings.adminlogin dt, dl.settings.adminlogin dd { + width: 100%; + } +} +/* [WIP] Warning: this next bit may cause trouble. */ +/* It's just to hide an empty div when the submits are not shown. */ +.panel .clear:nth-child(3) { + border: 1px solid green; + display: none; +} +/* End [WIP] */ + + +/* Now make the installer and upgrader adaptive */ +@media screen and (max-width: 950px) { + .progress_bar span { + float: right; + } +} + +@media screen and (max-width: 700px) { + #install_progress, #main_steps { + width: 100%; + margin-right: 0; + } + #install_progress { + order: 0; + margin-top: 2em; + } + dl.settings dd { + -ms-grid-column: 1; + grid-column: 1; + } + dl.settings dt { + margin: 10px 0 0; + } +} \ No newline at end of file diff --git a/other/install.php b/other/install.php index f4e70f6c30b..d57de9537bc 100644 --- a/other/install.php +++ b/other/install.php @@ -11,2553 +11,21 @@ * @version 3.0 Alpha 3 */ -use SMF\Config; -use SMF\Cookie; -use SMF\Db\DatabaseApi as Db; -use SMF\Lang; -use SMF\Logging; -use SMF\PackageManager\FtpConnection; -use SMF\Security; -use SMF\TaskRunner; -use SMF\Time; -use SMF\Url; -use SMF\User; -use SMF\Utils; -use SMF\Uuid; - -define('SMF_VERSION', '3.0 Alpha 3'); -define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION); -define('SMF_SOFTWARE_YEAR', '2025'); -define('DB_SCRIPT_VERSION', '3-0'); -define('SMF_INSTALLING', 1); - -define('JQUERY_VERSION', '3.6.3'); -define('POSTGRE_TITLE', 'PostgreSQL'); -define('MYSQL_TITLE', 'MySQL'); -define('SMF_USER_AGENT', 'Mozilla/5.0 (' . php_uname('s') . ' ' . php_uname('m') . ') AppleWebKit/605.1.15 (KHTML, like Gecko) SMF/' . strtr(SMF_VERSION, ' ', '.')); - -if (!defined('TIME_START')) { - define('TIME_START', microtime(true)); -} - -define('SMF_SETTINGS_FILE', __DIR__ . '/Settings.php'); -define('SMF_SETTINGS_BACKUP_FILE', __DIR__ . '/Settings_bak.php'); - -$GLOBALS['required_php_version'] = '8.0.0'; +declare(strict_types=1); // Don't have PHP support, do you? // >Error!Sorry, this installer requires PHP!
-if (!defined('SMF')) { - define('SMF', 1); -} - -// Let's pull in useful classes -require_once 'Sources/Autoloader.php'; - -// Get the current settings, without affecting global namespace. -Config::$backward_compatibility = false; -Config::load(); -Config::$backward_compatibility = true; - -Utils::load(); - -require_once Config::$sourcedir . '/Subs-Compat.php'; - -// Database info. -$databases = [ - 'mysql' => [ - 'name' => 'MySQL', - 'version' => '8.0.35', - 'version_check' => function () { - if (!function_exists('mysqli_fetch_row')) { - return false; - } - - return mysqli_fetch_row(mysqli_query(Db::$db->connection, 'SELECT VERSION();'))[0]; - }, - 'supported' => function_exists('mysqli_connect'), - 'default_user' => 'mysql.default_user', - 'default_password' => 'mysql.default_password', - 'default_host' => 'mysql.default_host', - 'default_port' => 'mysql.default_port', - 'utf8_support' => function () { - $request = Db::$db->query('', 'SHOW CHARACTER SET'); - $db_charsets = array_map(fn ($row) => $row['Charset'], Db::$db->fetch_all($request)); - Db::$db->free_result($request); - return in_array('utf8mb4', $db_charsets); - }, - 'utf8_version' => '5.5.3', - 'alter_support' => true, - 'validate_prefix' => function (&$value) { - $value = preg_replace('~[^A-Za-z0-9_\$]~', '', $value); - - return true; - }, - ], - 'postgresql' => [ - 'name' => 'PostgreSQL', - 'version' => '12.17', - 'version_check' => function () { - $request = pg_query(Db::$db->connection, 'SELECT version()'); - list($version) = pg_fetch_row($request); - list($pgl, $version) = explode(' ', $version); - - return $version; - }, - 'supported' => function_exists('pg_connect'), - 'always_has_db' => true, - 'utf8_support' => function () { - $request = pg_query(Db::$db->connection, 'SHOW SERVER_ENCODING'); - - list($charcode) = pg_fetch_row($request); - - return (bool) ($charcode == 'UTF8'); - }, - 'utf8_version' => '8.0', - 'validate_prefix' => function (&$value) { - $value = preg_replace('~[^A-Za-z0-9_\$]~', '', $value); - - // Is it reserved? - if ($value == 'pg_') { - return Lang::$txt['error_db_prefix_reserved']; - } - - // Is the prefix numeric? - if (preg_match('~^\d~', $value)) { - return Lang::$txt['error_db_prefix_numeric']; - } - - return true; - }, - ], -]; - -// Initialize everything and load the language files. -initialize_inputs(); -load_lang_file(); - -// This is what we are. -$installurl = $_SERVER['PHP_SELF']; - -// All the steps in detail. -// Number,Name,Function,Progress Weight. -$incontext['steps'] = [ - 0 => [1, Lang::$txt['install_step_welcome'], 'Welcome', 0], - 1 => [2, Lang::$txt['install_step_writable'], 'CheckFilesWritable', 10], - 2 => [3, Lang::$txt['install_step_databaseset'], 'DatabaseSettings', 15], - 3 => [4, Lang::$txt['install_step_forum'], 'ForumSettings', 40], - 4 => [5, Lang::$txt['install_step_databasechange'], 'DatabasePopulation', 15], - 5 => [6, Lang::$txt['install_step_admin'], 'AdminAccount', 20], - 6 => [7, Lang::$txt['install_step_delete'], 'DeleteInstall', 0], -]; - -// Default title... -$incontext['page_title'] = Lang::$txt['smf_installer']; - -// What step are we on? -$incontext['current_step'] = isset($_GET['step']) ? (int) $_GET['step'] : 0; - -// Loop through all the steps doing each one as required. -$incontext['overall_percent'] = 0; - -foreach ($incontext['steps'] as $num => $step) { - if ($num >= $incontext['current_step']) { - // The current weight of this step in terms of overall progress. - $incontext['step_weight'] = $step[3]; - // Make sure we reset the skip button. - $incontext['skip'] = false; - - // Call the step and if it returns false that means pause! - if (function_exists($step[2]) && $step[2]() === false) { - break; - } - - if (function_exists($step[2])) { - $incontext['current_step']++; - } - - // No warnings pass on. - $incontext['warning'] = ''; - } - $incontext['overall_percent'] += $step[3]; -} - -// Actually do the template stuff. -installExit(); - -function initialize_inputs() -{ - global $databases; - - // Just so people using older versions of PHP aren't left in the cold. - if (!isset($_SERVER['PHP_SELF'])) { - $_SERVER['PHP_SELF'] = $GLOBALS['HTTP_SERVER_VARS']['PHP_SELF'] ?? 'install.php'; - } - - // In pre-release versions, report all errors. - if (strspn(SMF_VERSION, '1234567890.') !== strlen(SMF_VERSION)) { - error_reporting(E_ALL); - } - // Otherwise, report all errors except for deprecation notices. - else { - error_reporting(E_ALL & ~E_DEPRECATED); - } - - // Fun. Low PHP version... - if (!isset($_GET)) { - $GLOBALS['_GET']['step'] = 0; - - return; - } - - if (!isset($_GET['obgz'])) { - ob_start(); - - if (ini_get('session.save_handler') == 'user') { - @ini_set('session.save_handler', 'files'); - } - - if (function_exists('session_start')) { - @session_start(); - } - } else { - ob_start('ob_gzhandler'); - - if (ini_get('session.save_handler') == 'user') { - @ini_set('session.save_handler', 'files'); - } - session_start(); - - if (!headers_sent()) { - echo ' - - - ', htmlspecialchars($_GET['pass_string']), ' - - - ', htmlspecialchars($_GET['pass_string']), ' - -'; - } - - exit; - } - - // This is really quite simple; if ?delete is on the URL, delete the installer... - if (isset($_GET['delete'])) { - if (isset($_SESSION['installer_temp_ftp'])) { - $ftp = new FtpConnection($_SESSION['installer_temp_ftp']['server'], $_SESSION['installer_temp_ftp']['port'], $_SESSION['installer_temp_ftp']['username'], $_SESSION['installer_temp_ftp']['password']); - $ftp->chdir($_SESSION['installer_temp_ftp']['path']); - - $ftp->unlink('install.php'); - - foreach ($databases as $key => $dummy) { - $type = ($key == 'mysqli') ? 'mysql' : $key; - $ftp->unlink('install_' . DB_SCRIPT_VERSION . '_' . Db::getClass($type) . '.sql'); - } - - $ftp->close(); - - unset($_SESSION['installer_temp_ftp']); - } else { - @unlink(__FILE__); - - foreach ($databases as $key => $dummy) { - $type = ($key == 'mysqli') ? 'mysql' : $key; - @unlink(Config::$boarddir . '/install_' . DB_SCRIPT_VERSION . '_' . Db::getClass($type) . '.sql'); - } - } - - // Now just redirect to a blank.png... - $secure = false; - - if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { - $secure = true; - } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') { - $secure = true; - } - - header('location: http' . ($secure ? 's' : '') . '://' . ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png'); - - exit; - } - - // PHP 5 might cry if we don't do this now. - if (function_exists('date_default_timezone_set')) { - // Get PHP's default timezone, if set - $ini_tz = ini_get('date.timezone'); - - if (!empty($ini_tz)) { - $timezone_id = $ini_tz; - } else { - $timezone_id = ''; - } - - // If date.timezone is unset, invalid, or just plain weird, make a best guess - if (!in_array($timezone_id, timezone_identifiers_list())) { - $server_offset = @mktime(0, 0, 0, 1, 1, 1970) * -1; - $timezone_id = timezone_name_from_abbr('', $server_offset, 0); - - if (empty($timezone_id)) { - $timezone_id = 'UTC'; - } - } - - date_default_timezone_set($timezone_id); - } - header('X-Frame-Options: SAMEORIGIN'); - header('X-XSS-Protection: 1'); - header('X-Content-Type-Options: nosniff'); - - // Force an integer step, defaulting to 0. - $_GET['step'] = (int) @$_GET['step']; -} - -// Load the list of language files, and the current language file. -function load_lang_file() -{ - global $incontext; - - $incontext['detected_languages'] = []; - - // Make sure the languages directory actually exists. - if (file_exists(Config::$languagesdir)) { - // Find all the "Maintenance" language files in the directory. - $dir = dir(Config::$languagesdir); - - while ($entry = $dir->read()) { - if (!is_dir(Config::$languagesdir . '/' . $entry) || !file_exists(Config::$languagesdir . '/' . $entry . '/' . 'Maintenance.php') || !file_exists(Config::$languagesdir . '/' . $entry . '/' . 'General.php')) { - continue; - } - - // Get the line we need. - $fp = @fopen(Config::$languagesdir . '/' . $entry . '/' . 'General.php', 'r'); - - // Yay! - if ($fp) - { - while (($line = fgets($fp)) !== false) - { - if (!str_contains($line, '$txt[\'native_name\']')) - continue; - - preg_match('~\$txt\[\'native_name\'\]\s*=\s*\'([^\']+)\';~', $line, $matchNative); - - // Set the language's name. - if (!empty($matchNative) && !empty($matchNative[1])) - { - // Don't mislabel the language if the translator missed this one. - if ($entry !== 'en_US' && $matchNative[1] === 'English (US)') - break; - - $langName = Utils::htmlspecialcharsDecode($matchNative[1]); - break; - } - } - - fclose($fp); - } - - $incontext['detected_languages'][$entry] = $langName ?? $entry; - } - $dir->close(); - } - - // Didn't find any, show an error message! - if (empty($incontext['detected_languages'])) { - // Let's not cache this message, eh? - header('expires: Mon, 26 Jul 1997 05:00:00 GMT'); - header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); - header('cache-control: no-cache'); - - echo ' - - - SMF Installer: Error! - - - -

A critical error has occurred.

- -

This installer was unable to find the installer\'s language file or files. They should be found under:

- -
', dirname($_SERVER['PHP_SELF']) != '/' ? dirname($_SERVER['PHP_SELF']) : '', '/Languages
- -

In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you have uploaded all the files in the distribution.

-

If that doesn\'t help, please make sure this install.php file is in the same place as the Themes folder.

-

If you continue to get this error message, feel free to look to us for support.

-
-'; - - die; - } - - // Override the language file? - if (isset($_GET['lang_file'])) { - $_SESSION['installer_temp_lang'] = $_GET['lang_file']; - } elseif (isset($GLOBALS['HTTP_GET_VARS']['lang_file'])) { - $_SESSION['installer_temp_lang'] = $GLOBALS['HTTP_GET_VARS']['lang_file']; - } - - // Make sure it exists, if it doesn't reset it. - if (!isset($_SESSION['installer_temp_lang']) || preg_match('~[^\\w_\\-.]~', $_SESSION['installer_temp_lang']) === 1 || !file_exists(Config::$languagesdir . '/' . $_SESSION['installer_temp_lang'] . '/Maintenance.php')) { - // Use the first one... - list($_SESSION['installer_temp_lang']) = array_keys($incontext['detected_languages']); - - // If we have english and some other language, use the other language. We Americans hate english :P. - if ($_SESSION['installer_temp_lang'] == 'en_US' && count($incontext['detected_languages']) > 1) { - list (, $_SESSION['installer_temp_lang']) = array_keys($incontext['detected_languages']); - } - } - - // Which language are we loading? Assume that the admin likes that language. - Config::$language = preg_replace('~^[A-Za-z0-9]+$~', '', $_SESSION['installer_temp_lang']); - - // Ensure SMF\Lang knows the path to the language directory. - Lang::addDirs(Config::$languagesdir); - - // And now load the language file. - Lang::load('General+Maintenance'); -} - -// This handy function loads some settings and the like. -function load_database() -{ - Config::$modSettings['disableQueryCheck'] = true; - - // Connect the database. - if (empty(Db::$db->connection)) { - Db::load(); - } -} - -// This is called upon exiting the installer, for template etc. -function installExit($fallThrough = false) -{ - global $incontext, $installurl; - - // Send character set. - header('content-type: text/html; charset=UTF-8'); - - // We usually dump our templates out. - if (!$fallThrough) { - // The top install bit. - template_install_above(); - - // Call the template. - if (isset($incontext['sub_template'])) { - $incontext['form_url'] = $installurl . '?step=' . $incontext['current_step']; - - call_user_func('template_' . $incontext['sub_template']); - } - // @todo REMOVE THIS!! - else { - if (function_exists('doStep' . $_GET['step'])) { - call_user_func('doStep' . $_GET['step']); - } - } - // Show the footer. - template_install_below(); - } - - // Bang - gone! - die(); -} - -function Welcome() -{ - global $incontext, $databases, $installurl; - - $incontext['page_title'] = Lang::$txt['install_welcome']; - $incontext['sub_template'] = 'welcome_message'; - - // Done the submission? - if (isset($_POST['contbutt'])) { - return true; - } - - // See if we think they have already installed it? - $probably_installed = 0; - - $settingsDefs = Config::getSettingsDefs(); - - foreach (['db_passwd', 'boardurl'] as $var) { - if (!empty(Config::${$var}) && Config::${$var} != $settingsDefs[$var]['default']) { - $probably_installed++; - } - } - - if ($probably_installed == 2) { - $incontext['warning'] = Lang::$txt['error_already_installed']; - } - - // Is some database support even compiled in? - $incontext['supported_databases'] = []; - - foreach ($databases as $key => $db) { - if ($db['supported']) { - $type = ($key == 'mysqli') ? 'mysql' : $key; - - if (!file_exists(Config::$boarddir . '/install_' . DB_SCRIPT_VERSION . '_' . Db::getClass($type) . '.sql')) { - $databases[$key]['supported'] = false; - $notFoundSQLFile = true; - Lang::$txt['error_db_script_missing'] = Lang::getTxt('error_db_script_missing', ['file' => 'install_' . DB_SCRIPT_VERSION . '_' . Db::getClass($type) . '.sql']); - } else { - $incontext['supported_databases'][] = $db; - } - } - } - - // Check the PHP version. - if ((!function_exists('version_compare') || version_compare($GLOBALS['required_php_version'], PHP_VERSION, '>='))) { - $error = 'error_php_too_low'; - } - // Make sure we have a supported database - elseif (empty($incontext['supported_databases'])) { - $error = empty($notFoundSQLFile) ? 'error_db_missing' : 'error_db_script_missing'; - } - // How about session support? Some crazy sysadmin remove it? - elseif (!function_exists('session_start')) { - $error = 'error_session_missing'; - } - // Make sure they uploaded all the files. - elseif (!file_exists(Config::$boarddir . '/index.php')) { - $error = 'error_missing_files'; - } - // Very simple check on the session.save_path for Windows. - // @todo Move this down later if they don't use database-driven sessions? - elseif (@ini_get('session.save_path') == '/tmp' && substr(__FILE__, 1, 2) == ':\\') { - $error = 'error_session_save_path'; - } - - // Since each of the three messages would look the same, anyway... - if (isset($error)) { - $incontext['error'] = Lang::$txt[$error]; - } - - // Mod_security blocks everything that smells funny. Let SMF handle security. - if (!fixModSecurity() && !isset($_GET['overmodsecurity'])) { - $incontext['error'] = Lang::$txt['error_mod_security'] . '

' . Lang::$txt['error_message_click'] . ' ' . Lang::$txt['error_message_bad_try_again']; - } - - // Confirm mbstring is loaded... - if (!extension_loaded('mbstring')) { - $incontext['error'] = Lang::$txt['install_no_mbstring']; - } - - // Confirm fileinfo is loaded... - if (!extension_loaded('fileinfo')) { - $incontext['error'] = Lang::$txt['install_no_fileinfo']; - } - - // Check for https stream support. - $supported_streams = stream_get_wrappers(); - - if (!in_array('https', $supported_streams)) { - $incontext['warning'] = Lang::$txt['install_no_https']; - } - - return false; -} - -function CheckFilesWritable() -{ - global $incontext; - - $incontext['page_title'] = Lang::$txt['ftp_checking_writable']; - $incontext['sub_template'] = 'chmod_files'; - - $writable_files = [ - 'attachments', - 'avatars', - 'custom_avatar', - 'cache', - 'Packages', - 'Smileys', - 'Themes', - 'Languages/en_US/agreement.txt', - 'Settings.php', - 'Settings_bak.php', - 'cache/db_last_error.php', - ]; - - foreach ($incontext['detected_languages'] as $lang => $temp) { - $extra_files[] = 'Languages/' . $lang; - } - - // With mod_security installed, we could attempt to fix it with .htaccess. - if (function_exists('apache_get_modules') && in_array('mod_security', apache_get_modules())) { - $writable_files[] = file_exists(Config::$boarddir . '/.htaccess') ? '.htaccess' : '.'; - } - - $failed_files = []; - - // On linux, it's easy - just use is_writable! - if (substr(__FILE__, 1, 2) != ':\\') { - $incontext['systemos'] = 'linux'; - - foreach ($writable_files as $file) { - // Some files won't exist, try to address up front - if (!file_exists(Config::$boarddir . '/' . $file)) { - @touch(Config::$boarddir . '/' . $file); - } - - // NOW do the writable check... - if (!is_writable(Config::$boarddir . '/' . $file)) { - @chmod(Config::$boarddir . '/' . $file, 0755); - - // Well, 755 hopefully worked... if not, try 777. - if (!is_writable(Config::$boarddir . '/' . $file) && !@chmod(Config::$boarddir . '/' . $file, 0777)) { - $failed_files[] = $file; - } - } - } - - foreach ($extra_files as $file) { - @chmod(Config::$boarddir . (empty($file) ? '' : '/' . $file), 0777); - } - } - // Windows is trickier. Let's try opening for r+... - else { - $incontext['systemos'] = 'windows'; - - foreach ($writable_files as $file) { - // Folders can't be opened for write... but the index.php in them can ;) - if (is_dir(Config::$boarddir . '/' . $file)) { - $file .= '/index.php'; - } - - // Funny enough, chmod actually does do something on windows - it removes the read only attribute. - @chmod(Config::$boarddir . '/' . $file, 0777); - $fp = @fopen(Config::$boarddir . '/' . $file, 'r+'); - - // Hmm, okay, try just for write in that case... - if (!is_resource($fp)) { - $fp = @fopen(Config::$boarddir . '/' . $file, 'w'); - } - - if (!is_resource($fp)) { - $failed_files[] = $file; - } - - @fclose($fp); - } - - foreach ($extra_files as $file) { - @chmod(Config::$boarddir . (empty($file) ? '' : '/' . $file), 0777); - } - } - - $failure = count($failed_files) >= 1; - - if (!isset($_SERVER)) { - return !$failure; - } - - // Put the list into context. - $incontext['failed_files'] = $failed_files; - - // It's not going to be possible to use FTP on windows to solve the problem... - if ($failure && substr(__FILE__, 1, 2) == ':\\') { - $incontext['error'] = Lang::$txt['error_windows_chmod'] . ' -
    -
  • ' . implode('
  • -
  • ', $failed_files) . '
  • -
'; - - return false; - } - - // We're going to have to use... FTP! - if ($failure) { - // Load any session data we might have... - if (!isset($_POST['ftp_username']) && isset($_SESSION['installer_temp_ftp'])) { - $_POST['ftp_server'] = $_SESSION['installer_temp_ftp']['server']; - $_POST['ftp_port'] = $_SESSION['installer_temp_ftp']['port']; - $_POST['ftp_username'] = $_SESSION['installer_temp_ftp']['username']; - $_POST['ftp_password'] = $_SESSION['installer_temp_ftp']['password']; - $_POST['ftp_path'] = $_SESSION['installer_temp_ftp']['path']; - } - - $incontext['ftp_errors'] = []; - - if (isset($_POST['ftp_username'])) { - $ftp = new FtpConnection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']); - - if ($ftp->error === false) { - // Try it without /home/abc just in case they messed up. - if (!$ftp->chdir($_POST['ftp_path'])) { - $incontext['ftp_errors'][] = $ftp->last_message; - $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path'])); - } - } - } - - if (!isset($ftp) || $ftp->error !== false) { - if (!isset($ftp)) { - $ftp = new FtpConnection(null); - } - // Save the error so we can mess with listing... - elseif ($ftp->error !== false && empty($incontext['ftp_errors']) && !empty($ftp->last_message)) { - $incontext['ftp_errors'][] = $ftp->last_message; - } - - list($username, $detect_path, $found_path) = $ftp->detect_path(Config::$boarddir); - - if (empty($_POST['ftp_path']) && $found_path) { - $_POST['ftp_path'] = $detect_path; - } - - if (!isset($_POST['ftp_username'])) { - $_POST['ftp_username'] = $username; - } - - // Set the username etc, into context. - $incontext['ftp'] = [ - 'server' => $_POST['ftp_server'] ?? 'localhost', - 'port' => $_POST['ftp_port'] ?? '21', - 'username' => $_POST['ftp_username'] ?? '', - 'path' => $_POST['ftp_path'] ?? '/', - 'path_msg' => !empty($found_path) ? Lang::$txt['ftp_path_found_info'] : Lang::$txt['ftp_path_info'], - ]; - - return false; - } - - - $_SESSION['installer_temp_ftp'] = [ - 'server' => $_POST['ftp_server'], - 'port' => $_POST['ftp_port'], - 'username' => $_POST['ftp_username'], - 'password' => $_POST['ftp_password'], - 'path' => $_POST['ftp_path'], - ]; - - $failed_files_updated = []; - - foreach ($failed_files as $file) { - if (!is_writable(Config::$boarddir . '/' . $file)) { - $ftp->chmod($file, 0755); - } - - if (!is_writable(Config::$boarddir . '/' . $file)) { - $ftp->chmod($file, 0777); - } - - if (!is_writable(Config::$boarddir . '/' . $file)) { - $failed_files_updated[] = $file; - $incontext['ftp_errors'][] = rtrim($ftp->last_message) . ' -> ' . $file . "\n"; - } - } - - $ftp->close(); - - // Are there any errors left? - if (count($failed_files_updated) >= 1) { - // Guess there are... - $incontext['failed_files'] = $failed_files_updated; - - // Set the username etc, into context. - $incontext['ftp'] = $_SESSION['installer_temp_ftp'] += [ - 'path_msg' => Lang::$txt['ftp_path_info'], - ]; - - return false; - } - - } - - return true; -} - -function DatabaseSettings() -{ - global $databases, $incontext; - - $incontext['sub_template'] = 'database_settings'; - $incontext['page_title'] = Lang::$txt['db_settings']; - $incontext['continue'] = 1; - - // Set up the defaults. - $incontext['db']['server'] = 'localhost'; - $incontext['db']['user'] = ''; - $incontext['db']['name'] = ''; - $incontext['db']['pass'] = ''; - $incontext['db']['type'] = ''; - $incontext['supported_databases'] = []; - - $foundOne = false; - - foreach ($databases as $key => $db) { - // Override with the defaults for this DB if appropriate. - if ($db['supported']) { - $incontext['supported_databases'][$key] = $db; - - if (!$foundOne) { - if (isset($db['default_host'])) { - $incontext['db']['server'] = ini_get($db['default_host']) or $incontext['db']['server'] = 'localhost'; - } - - if (isset($db['default_user'])) { - $incontext['db']['user'] = ini_get($db['default_user']); - $incontext['db']['name'] = ini_get($db['default_user']); - } - - if (isset($db['default_password'])) { - $incontext['db']['pass'] = ini_get($db['default_password']); - } - - // For simplicity and less confusion, leave the port blank by default - $incontext['db']['port'] = ''; - - $incontext['db']['type'] = $key; - $foundOne = true; - } - } - } - - // Override for repost. - if (isset($_POST['db_user'])) { - $incontext['db']['user'] = $_POST['db_user']; - $incontext['db']['name'] = $_POST['db_name']; - $incontext['db']['server'] = $_POST['db_server']; - $incontext['db']['prefix'] = $_POST['db_prefix']; - - if (!empty($_POST['db_port'])) { - $incontext['db']['port'] = $_POST['db_port']; - } - } else { - $incontext['db']['prefix'] = 'smf_'; - } - - // Are we submitting? - if (isset($_POST['db_type'])) { - // What type are they trying? - $db_type = preg_replace('~[^A-Za-z0-9]~', '', $_POST['db_type']); - $db_prefix = $_POST['db_prefix']; - // Validate the prefix. - $valid_prefix = $databases[$db_type]['validate_prefix']($db_prefix); - - if ($valid_prefix !== true) { - $incontext['error'] = $valid_prefix; - - return false; - } - - // Take care of these variables... - $vars = [ - 'db_type' => $db_type, - 'db_name' => $_POST['db_name'], - 'db_user' => $_POST['db_user'], - 'db_passwd' => $_POST['db_passwd'] ?? '', - 'db_server' => $_POST['db_server'], - 'db_prefix' => $db_prefix, - // The cookiename is special; we want it to be the same if it ever needs to be reinstalled with the same info. - 'cookiename' => 'SMFCookie' . abs(crc32($_POST['db_name'] . preg_replace('~[^A-Za-z0-9_$]~', '', $_POST['db_prefix'])) % 1000), - ]; - - // Only set the port if we're not using the default - if (!empty($_POST['db_port'])) { - // For MySQL, we can get the "default port" from PHP. PostgreSQL has no such option though. - if (($db_type == 'mysql' || $db_type == 'mysqli') && $_POST['db_port'] != ini_get($db_type . '.default_port')) { - $vars['db_port'] = (int) $_POST['db_port']; - } elseif ($db_type == 'postgresql' && $_POST['db_port'] != 5432) { - $vars['db_port'] = (int) $_POST['db_port']; - } - } - - // God I hope it saved! - if (!installer_updateSettingsFile($vars)) { - $incontext['error'] = Lang::$txt['settings_error']; - - return false; - } - - // Update SMF\Config with the changes we just saved. - Config::load(); - - // Better find the database file! - if (!file_exists(Config::$sourcedir . '/Db/APIs/' . Db::getClass(Config::$db_type) . '.php')) { - $incontext['error'] = Lang::getTxt('error_db_file', ['Db/APIs/' . Db::getClass(Config::$db_type) . '.php']); - - return false; - } - - Config::$modSettings['disableQueryCheck'] = true; - - // Attempt a connection. - $needsDB = !empty($databases[Config::$db_type]['always_has_db']); - - Db::load(['non_fatal' => true, 'dont_select_db' => !$needsDB]); - - // Still no connection? Big fat error message :P. - if (!Db::$db->connection) { - // Get error info... Recast just in case we get false or 0... - $error_message = Db::$db->connect_error(); - - if (empty($error_message)) { - $error_message = ''; - } - $error_number = Db::$db->connect_errno(); - - if (empty($error_number)) { - $error_number = ''; - } - $db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message; - - $incontext['error'] = Lang::$txt['error_db_connect'] . '
' . $db_error . '
'; - - return false; - } - - // Do they meet the install requirements? - // @todo Old client, new server? - if (version_compare($databases[Config::$db_type]['version'], preg_replace('~^\D*|\-.+?$~', '', $databases[Config::$db_type]['version_check']())) > 0) { - $incontext['error'] = Lang::getTxt('error_db_too_low', $databases[Config::$db_type]); - - return false; - } - - // Let's try that database on for size... assuming we haven't already lost the opportunity. - if (Db::$db->name != '' && !$needsDB) { - Db::$db->query( - '', - 'CREATE DATABASE IF NOT EXISTS {identifier:db_name} {raw:extra}', - [ - 'security_override' => true, - 'db_error_skip' => true, - 'db_name' => Db::$db->name, - 'extra' => Config::$db_type === 'mysql' ? ' CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci' : '', - ], - Db::$db->connection, - ); - - // Okay, let's try the prefix if it didn't work... - if (!Db::$db->select(Db::$db->name, Db::$db->connection) && Db::$db->name != '') { - Db::$db->query( - '', - 'CREATE DATABASE IF NOT EXISTS {identifier:db_name} {raw:extra}', - [ - 'security_override' => true, - 'db_error_skip' => true, - 'db_name' => Db::$db->prefix . Db::$db->name, - 'extra' => Config::$db_type === 'mysql' ? ' CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci' : '', - ], - Db::$db->connection, - ); - - if (Db::$db->select(Db::$db->prefix . Db::$db->name, Db::$db->connection)) { - Db::$db->name = Db::$db->prefix . Db::$db->name; - installer_updateSettingsFile(['db_name' => Db::$db->name]); - } - } - - // Okay, now let's try to connect... - if (!Db::$db->select(Db::$db->name, Db::$db->connection)) { - $incontext['error'] = Lang::getTxt('error_db_database', ['db_name' => Db::$db->name]); - - return false; - } - } - - return true; - } - - return false; -} - -// Let's start with basic forum type settings. -function ForumSettings() -{ - global $incontext, $databases; - - $incontext['sub_template'] = 'forum_settings'; - $incontext['page_title'] = Lang::$txt['install_settings']; - - // Let's see if we got the database type correct. - if (isset($_POST['db_type'], $databases[$_POST['db_type']])) { - Config::$db_type = $_POST['db_type']; - } - - // Else we'd better be able to get the connection. - else { - load_database(); - } - - Config::$db_type = $_POST['db_type'] ?? Config::$db_type; - - // What host and port are we on? - $host = empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST']; - - $secure = false; - - if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { - $secure = true; - } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') { - $secure = true; - } - - // Now, to put what we've learned together... and add a path. - $incontext['detected_url'] = 'http' . ($secure ? 's' : '') . '://' . $host . substr($_SERVER['PHP_SELF'], 0, strrpos($_SERVER['PHP_SELF'], '/')); - - // Check if the database sessions will even work. - $incontext['test_dbsession'] = (ini_get('session.auto_start') != 1); - - $incontext['continue'] = 1; - - // Check Postgres setting - if (Config::$db_type === 'postgresql') { - load_database(); - $result = Db::$db->query( - '', - 'show standard_conforming_strings', - [ - 'db_error_skip' => true, - ], - ); - - if ($result !== false) { - $row = Db::$db->fetch_assoc($result); - - if ($row['standard_conforming_strings'] !== 'on') { - $incontext['continue'] = 0; - $incontext['error'] = Lang::$txt['error_pg_scs']; - } - Db::$db->free_result($result); - } - } - - // Setup the SSL checkbox... - $incontext['ssl_chkbx_protected'] = false; - $incontext['ssl_chkbx_checked'] = false; - - // If redirect in effect, force SSL ON. - $url = new Url($incontext['detected_url']); - - if ($url->redirectsToHttps()) { - $incontext['ssl_chkbx_protected'] = true; - $incontext['ssl_chkbx_checked'] = true; - $_POST['force_ssl'] = true; - } - - // If no cert, make sure SSL stays OFF. - if (!$url->hasSSL()) { - $incontext['ssl_chkbx_protected'] = true; - $incontext['ssl_chkbx_checked'] = false; - } - - // Submitting? - if (isset($_POST['boardurl'])) { - if (str_ends_with($_POST['boardurl'], '/index.php')) { - $_POST['boardurl'] = substr($_POST['boardurl'], 0, -10); - } elseif (str_ends_with($_POST['boardurl'], '/')) { - $_POST['boardurl'] = substr($_POST['boardurl'], 0, -1); - } - - if (!str_starts_with($_POST['boardurl'], 'http://') && !str_starts_with($_POST['boardurl'], 'file://') && !str_starts_with($_POST['boardurl'], 'https://')) { - $_POST['boardurl'] = 'http://' . $_POST['boardurl']; - } - - // Make sure boardurl is aligned with ssl setting - if (empty($_POST['force_ssl'])) { - $_POST['boardurl'] = strtr($_POST['boardurl'], ['https://' => 'http://']); - } else { - $_POST['boardurl'] = strtr($_POST['boardurl'], ['http://' => 'https://']); - } - - // Make sure international domain names are normalized correctly. - $_POST['boardurl'] = (string) new Url($_POST['boardurl'], true); - - // Deal with different operating systems' directory structure... - $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', __DIR__), '/'); - - // Save these variables. - $vars = [ - 'boardurl' => $_POST['boardurl'], - 'boarddir' => $path, - 'sourcedir' => $path . '/Sources', - 'cachedir' => $path . '/cache', - 'packagesdir' => $path . '/Packages', - 'languagesdir' => $path . '/Languages', - 'mbname' => strtr($_POST['mbname'], ['\"' => '"']), - 'language' => $_SESSION['installer_temp_lang'], - 'image_proxy_secret' => bin2hex(random_bytes(10)), - 'image_proxy_enabled' => !empty($_POST['force_ssl']), - 'auth_secret' => bin2hex(random_bytes(32)), - ]; - - // Must save! - if (!installer_updateSettingsFile($vars)) { - $incontext['error'] = Lang::$txt['settings_error']; - - return false; - } - - // Update SMF\Config with the changes we just saved. - Config::load(); - - // UTF-8 requires a setting to override the language charset. - if (!$databases[Config::$db_type]['utf8_support']()) { - $incontext['error'] = Lang::getTxt('error_utf8_support'); - - return false; - } - - if (version_compare($databases[Config::$db_type]['utf8_version'], preg_replace('~\-.+?$~', '', $databases[Config::$db_type]['version_check']()), '>')) { - $incontext['error'] = Lang::getTxt('error_utf8_version', $databases[Config::$db_type]); - - return false; - } - - // Good, skip on. - return true; - } - - return false; -} - -// Step one: Do the SQL thang. -function DatabasePopulation() -{ - global $databases, $incontext; - - $incontext['sub_template'] = 'populate_database'; - $incontext['page_title'] = Lang::$txt['db_populate']; - $incontext['continue'] = 1; - - // Already done? - if (isset($_POST['pop_done'])) { - return true; - } - - // Reload settings. - Config::load(); - load_database(); - - // Before running any of the queries, let's make sure another version isn't already installed. - $result = Db::$db->query( - '', - 'SELECT variable, value - FROM {db_prefix}settings', - [ - 'db_error_skip' => true, - ], - ); - $newSettings = []; - - if ($result !== false) { - while ($row = Db::$db->fetch_assoc($result)) { - Config::$modSettings[$row['variable']] = $row['value']; - } - - Db::$db->free_result($result); - - // Do they match? If so, this is just a refresh so charge on! - if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] != SMF_VERSION) { - $incontext['error'] = Lang::$txt['error_versions_do_not_match']; - - return false; - } - } - Config::$modSettings['disableQueryCheck'] = true; - - // Windows likes to leave the trailing slash, which yields to C:\path\to\SMF\/attachments... - if (str_ends_with(__DIR__, '\\')) { - $attachdir = __DIR__ . 'attachments'; - } else { - $attachdir = __DIR__ . '/attachments'; - } - - $replaces = [ - '{$db_prefix}' => Db::$db->prefix, - '{$attachdir}' => json_encode([1 => Db::$db->escape_string($attachdir)]), - '{$boarddir}' => Db::$db->escape_string(Config::$boarddir), - '{$boardurl}' => Config::$boardurl, - '{$enableCompressedOutput}' => isset($_POST['compress']) ? '1' : '0', - '{$databaseSession_enable}' => isset($_POST['dbsession']) ? '1' : '0', - '{$smf_version}' => SMF_VERSION, - '{$current_time}' => time(), - '{$sched_task_offset}' => 82800 + mt_rand(0, 86399), - '{$registration_method}' => $_POST['reg_mode'] ?? 0, - ]; - - foreach (Lang::$txt as $key => $value) { - if (str_starts_with($key, 'default_')) { - $replaces['{$' . $key . '}'] = Db::$db->escape_string($value); - } - } - $replaces['{$default_reserved_names}'] = strtr($replaces['{$default_reserved_names}'], ['\\\\n' => '\\n']); - - // MySQL-specific stuff - storage engine and UTF8 handling - if (str_starts_with(Config::$db_type, 'mysql')) { - // Just in case the query fails for some reason... - $engines = []; - - // Figure out storage engines - what do we have, etc. - $get_engines = Db::$db->query('', 'SHOW ENGINES', []); - - while ($row = Db::$db->fetch_assoc($get_engines)) { - if ($row['Support'] == 'YES' || $row['Support'] == 'DEFAULT') { - $engines[] = $row['Engine']; - } - } - - // Done with this now - Db::$db->free_result($get_engines); - - // InnoDB is better, so use it if possible... - $has_innodb = in_array('InnoDB', $engines); - $replaces['{$engine}'] = $has_innodb ? 'InnoDB' : 'MyISAM'; - $replaces['{$memory}'] = (!$has_innodb && in_array('MEMORY', $engines)) ? 'MEMORY' : $replaces['{$engine}']; - - // UTF-8 is required. - $replaces['{$engine}'] .= ' DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'; - $replaces['{$memory}'] .= ' DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'; - - // One last thing - if we don't have InnoDB, we can't do transactions... - if (!$has_innodb) { - $replaces['START TRANSACTION;'] = ''; - $replaces['COMMIT;'] = ''; - } - } else { - $has_innodb = false; - } - - // Read in the SQL. Turn this on and that off... internationalize... etc. - $type = (Config::$db_type == 'mysqli' ? 'mysql' : Config::$db_type); - $sql_lines = explode("\n", strtr(implode(' ', file(Config::$boarddir . '/install_' . DB_SCRIPT_VERSION . '_' . Db::getClass($type) . '.sql')), $replaces)); - - // Execute the SQL. - $current_statement = ''; - $exists = []; - $incontext['failures'] = []; - $incontext['sql_results'] = [ - 'tables' => 0, - 'inserts' => 0, - 'table_dups' => 0, - 'insert_dups' => 0, - ]; - - foreach ($sql_lines as $count => $line) { - // No comments allowed! - if (!str_starts_with(trim($line), '#')) { - $current_statement .= "\n" . rtrim($line); - } - - // Is this the end of the query string? - if (empty($current_statement) || (preg_match('~;[\s]*$~s', $line) == 0 && $count != count($sql_lines))) { - continue; - } - - // Does this table already exist? If so, don't insert more data into it! - if (preg_match('~^\s*INSERT INTO ([^\s\n\r]+?)~', $current_statement, $match) != 0 && in_array($match[1], $exists)) { - preg_match_all('~\)[,;]~', $current_statement, $matches); - - if (!empty($matches[0])) { - $incontext['sql_results']['insert_dups'] += count($matches[0]); - } else { - $incontext['sql_results']['insert_dups']++; - } - - $current_statement = ''; - - continue; - } - - if (Db::$db->query('', $current_statement, ['security_override' => true, 'db_error_skip' => true], Db::$db->connection) === false) { - // Error 1050: Table already exists! - // @todo Needs to be made better! - if (((Config::$db_type != 'mysql' && Config::$db_type != 'mysqli') || mysqli_errno(Db::$db->connection) == 1050) && preg_match('~^\s*CREATE TABLE ([^\s\n\r]+?)~', $current_statement, $match) == 1) { - $exists[] = $match[1]; - $incontext['sql_results']['table_dups']++; - } - // Don't error on duplicate indexes (or duplicate operators in PostgreSQL.) - elseif (!preg_match('~^\s*CREATE( UNIQUE)? INDEX ([^\n\r]+?)~', $current_statement, $match) && !(Config::$db_type == 'postgresql' && preg_match('~^\s*CREATE OPERATOR (^\n\r]+?)~', $current_statement, $match))) { - // MySQLi requires a connection object. It's optional with MySQL and Postgres - $incontext['failures'][$count] = Db::$db->error(Db::$db->connection); - } - } else { - if (preg_match('~^\s*CREATE TABLE ([^\s\n\r]+?)~', $current_statement, $match) == 1) { - $incontext['sql_results']['tables']++; - } elseif (preg_match('~^\s*INSERT INTO ([^\s\n\r]+?)~', $current_statement, $match) == 1) { - preg_match_all('~\)[,;]~', $current_statement, $matches); - - if (!empty($matches[0])) { - $incontext['sql_results']['inserts'] += count($matches[0]); - } else { - $incontext['sql_results']['inserts']++; - } - } - } - - $current_statement = ''; - - // Wait, wait, I'm still working here! - @set_time_limit(60); - } - - // Sort out the context for the SQL. - foreach ($incontext['sql_results'] as $key => $number) { - if ($number == 0) { - unset($incontext['sql_results'][$key]); - } else { - $incontext['sql_results'][$key] = Lang::getTxt('db_populate_' . $key, [$number]); - } - } - - // Are we allowing stat collection? - if (!empty($_POST['stats']) && !str_starts_with(Config::$boardurl, 'http://localhost') && empty(Config::$modSettings['allow_sm_stats']) && empty(Config::$modSettings['enable_sm_stats'])) { - $incontext['allow_sm_stats'] = true; - - // Attempt to register the site etc. - $fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr); - - if (!$fp) { - $fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr); - } - - if ($fp) { - $out = 'GET /smf/stats/register_stats.php?site=' . base64_encode(Config::$boardurl) . ' HTTP/1.1' . "\r\n"; - $out .= 'Host: www.simplemachines.org' . "\r\n"; - $out .= 'Connection: Close' . "\r\n\r\n"; - fwrite($fp, $out); - - $return_data = ''; - - while (!feof($fp)) { - $return_data .= fgets($fp, 128); - } - - fclose($fp); - - // Get the unique site ID. - preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID); - - if (!empty($ID[1])) { - Db::$db->insert( - 'replace', - Db::$db->prefix . 'settings', - ['variable' => 'string', 'value' => 'string'], - [ - ['sm_stats_key', $ID[1]], - ['enable_sm_stats', 1], - ], - ['variable'], - ); - } - } - } - // Don't remove stat collection unless we unchecked the box for real, not from the loop. - elseif (empty($_POST['stats']) && empty($incontext['allow_sm_stats'])) { - Db::$db->query( - '', - 'DELETE FROM {db_prefix}settings - WHERE variable = {string:enable_sm_stats}', - [ - 'enable_sm_stats' => 'enable_sm_stats', - 'db_error_skip' => true, - ], - ); - } - - // Are we enabling SSL? - if (!empty($_POST['force_ssl'])) { - $newSettings[] = ['force_ssl', 1]; - } - - // Setting a timezone is required. - if (!isset(Config::$modSettings['default_timezone']) && function_exists('date_default_timezone_set')) { - // Get PHP's default timezone, if set - $ini_tz = ini_get('date.timezone'); - - if (!empty($ini_tz)) { - $timezone_id = $ini_tz; - } else { - $timezone_id = ''; - } - - // If date.timezone is unset, invalid, or just plain weird, make a best guess - if (!in_array($timezone_id, timezone_identifiers_list())) { - $server_offset = @mktime(0, 0, 0, 1, 1, 1970) * -1; - $timezone_id = timezone_name_from_abbr('', $server_offset, 0); - - if (empty($timezone_id)) { - $timezone_id = 'UTC'; - } - } - - if (date_default_timezone_set($timezone_id)) { - $newSettings[] = ['default_timezone', $timezone_id]; - } - } - - if (!empty($newSettings)) { - Db::$db->insert( - 'replace', - '{db_prefix}settings', - ['variable' => 'string-255', 'value' => 'string-65534'], - $newSettings, - ['variable'], - ); - } - - // Populate the smiley_files table. - // Can't just dump this data in the SQL file because we need to know the id for each smiley. - $smiley_filenames = [ - ':)' => 'smiley', - ';)' => 'wink', - ':D' => 'cheesy', - ';D' => 'grin', - '>:(' => 'angry', - ':(' => 'sad', - ':o' => 'shocked', - '8)' => 'cool', - '???' => 'huh', - '::)' => 'rolleyes', - ':P' => 'tongue', - ':-[' => 'embarrassed', - ':-X' => 'lipsrsealed', - ':-\\' => 'undecided', - ':-*' => 'kiss', - ':\'(' => 'cry', - '>:D' => 'evil', - '^-^' => 'azn', - 'O0' => 'afro', - ':))' => 'laugh', - 'C:-)' => 'police', - 'O:-)' => 'angel', - ]; - $smiley_set_extensions = ['fugue' => '.png', 'alienine' => '.png']; - - $smiley_inserts = []; - $request = Db::$db->query( - '', - 'SELECT id_smiley, code - FROM {db_prefix}smileys', - [], - ); - - while ($row = Db::$db->fetch_assoc($request)) { - foreach ($smiley_set_extensions as $set => $ext) { - $smiley_inserts[] = [$row['id_smiley'], $set, $smiley_filenames[$row['code']] . $ext]; - } - } - Db::$db->free_result($request); - - Db::$db->insert( - 'ignore', - '{db_prefix}smiley_files', - ['id_smiley' => 'int', 'smiley_set' => 'string-48', 'filename' => 'string-48'], - $smiley_inserts, - ['id_smiley', 'smiley_set'], - ); - - // Set the UID column for calendar events. - $calendar_updates = []; - $request = Db::$db->query( - '', - 'SELECT id_event, uid - FROM {db_prefix}calendar', - [], - ); - - while ($row = Db::$db->fetch_assoc($request)) { - if ($row['uid'] === '') { - $calendar_updates[] = ['id_event' => $row['id_event'], 'uid' => (string) new Uuid()]; - } - } - Db::$db->free_result($request); - - foreach ($calendar_updates as $calendar_update) { - Db::$db->query( - '', - 'UPDATE {db_prefix}calendar - SET uid = {string:uid} - WHERE id_event = {int:id_event}', - $calendar_update, - ); - } - - // Let's optimize those new tables, but not on InnoDB, ok? - if (!$has_innodb) { - $tables = Db::$db->list_tables(Db::$db->name, Db::$db->prefix . '%'); - - foreach ($tables as $table) { - Db::$db->optimize_table($table) != -1 or $db_messed = true; - - if (!empty($db_messed)) { - $incontext['failures'][-1] = Db::$db->error(); - break; - } - } - } - - // MySQL specific stuff - if (!str_starts_with(Config::$db_type, 'mysql')) { - return false; - } - - // Find database user privileges. - $privs = []; - $get_privs = Db::$db->query('', 'SHOW PRIVILEGES', []); - - while ($row = Db::$db->fetch_assoc($get_privs)) { - if ($row['Privilege'] == 'Alter') { - $privs[] = $row['Privilege']; - } - } - Db::$db->free_result($get_privs); - - // Check for the ALTER privilege. - if (!empty($databases[Config::$db_type]['alter_support']) && !in_array('Alter', $privs)) { - $incontext['error'] = Lang::$txt['error_db_alter_priv']; - - return false; - } - - if (!empty($exists)) { - $incontext['page_title'] = Lang::$txt['user_refresh_install']; - $incontext['was_refresh'] = true; - } - - return false; -} - -// Ask for the administrator login information. -function AdminAccount() -{ - global $incontext; - - $incontext['sub_template'] = 'admin_account'; - $incontext['page_title'] = Lang::$txt['user_settings']; - $incontext['continue'] = 1; - - // Skipping? - if (!empty($_POST['skip'])) { - return true; - } - - // Need this to check whether we need the database password. - Config::load(); - load_database(); - - $settingsDefs = Config::getSettingsDefs(); - - // Reload $modSettings. - Config::reloadModSettings(); - - if (!isset($_POST['username'])) { - $_POST['username'] = ''; - } - - if (!isset($_POST['email'])) { - $_POST['email'] = ''; - } - - if (!isset($_POST['server_email'])) { - $_POST['server_email'] = ''; - } - - $incontext['username'] = htmlspecialchars($_POST['username']); - $incontext['email'] = htmlspecialchars($_POST['email']); - $incontext['server_email'] = htmlspecialchars($_POST['server_email']); - - $incontext['require_db_confirm'] = empty(Config::$db_type); - - // Only allow skipping if we think they already have an account setup. - $request = Db::$db->query( - '', - 'SELECT id_member - FROM {db_prefix}members - WHERE id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0 - LIMIT 1', - [ - 'db_error_skip' => true, - 'admin_group' => 1, - ], - ); - - if (Db::$db->num_rows($request) != 0) { - $incontext['skip'] = 1; - } - Db::$db->free_result($request); - - // Trying to create an account? - if (isset($_POST['password1']) && !empty($_POST['contbutt'])) { - // Wrong password? - if ($incontext['require_db_confirm'] && $_POST['password3'] != Config::$db_passwd) { - $incontext['error'] = Lang::$txt['error_db_connect']; - - return false; - } - - // Not matching passwords? - if ($_POST['password1'] != $_POST['password2']) { - $incontext['error'] = Lang::$txt['error_user_settings_again_match']; - - return false; - } - - // No password? - if (strlen($_POST['password1']) < 4) { - $incontext['error'] = Lang::$txt['error_user_settings_no_password']; - - return false; - } - - if (!file_exists(Config::$sourcedir . '/Utils.php')) { - $incontext['error'] = Lang::getTxt('error_sourcefile_missing', ['file' => 'Utils.php']); - - return false; - } - - // Update the webmaster's email? - if (!empty($_POST['server_email']) && (empty(Config::$webmaster_email) || Config::$webmaster_email == $settingsDefs['webmaster_email']['default'])) { - installer_updateSettingsFile(['webmaster_email' => $_POST['server_email']]); - } - - // Work out whether we're going to have dodgy characters and remove them. - $invalid_characters = preg_match('~[<>&"\'=\\\]~', $_POST['username']) != 0; - $_POST['username'] = preg_replace('~[<>&"\'=\\\]~', '', $_POST['username']); - - $result = Db::$db->query( - '', - 'SELECT id_member, password_salt - FROM {db_prefix}members - WHERE member_name = {string:username} OR email_address = {string:email} - LIMIT 1', - [ - 'username' => $_POST['username'], - 'email' => $_POST['email'], - 'db_error_skip' => true, - ], - ); - - if (Db::$db->num_rows($result) != 0) { - list($incontext['member_id'], $incontext['member_salt']) = Db::$db->fetch_row($result); - Db::$db->free_result($result); - - $incontext['account_existed'] = Lang::$txt['error_user_settings_taken']; - } elseif ($_POST['username'] == '' || strlen($_POST['username']) > 25) { - // Try the previous step again. - $incontext['error'] = $_POST['username'] == '' ? Lang::$txt['error_username_left_empty'] : Lang::$txt['error_username_too_long']; - - return false; - } elseif ($invalid_characters || $_POST['username'] == '_' || $_POST['username'] == '|' || str_contains($_POST['username'], '[code') || str_contains($_POST['username'], '[/code')) { - // Try the previous step again. - $incontext['error'] = Lang::$txt['error_invalid_characters_username']; - - return false; - } elseif (empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) || strlen($_POST['email']) > 255) { - // One step back, this time fill out a proper admin email address. - $incontext['error'] = Lang::$txt['error_valid_admin_email_needed']; - - return false; - } elseif (empty($_POST['server_email']) || !filter_var($_POST['server_email'], FILTER_VALIDATE_EMAIL) || strlen($_POST['server_email']) > 255) { - // One step back, this time fill out a proper admin email address. - $incontext['error'] = Lang::$txt['error_valid_server_email_needed']; - - return false; - } elseif ($_POST['username'] != '') { - $incontext['member_salt'] = bin2hex(random_bytes(16)); - - // Format the username properly. - $_POST['username'] = preg_replace('~[\t\n\r\x0B\0\xA0]+~', ' ', $_POST['username']); - $ip = isset($_SERVER['REMOTE_ADDR']) ? substr($_SERVER['REMOTE_ADDR'], 0, 255) : ''; - - $_POST['password1'] = Security::hashPassword($_POST['password1']); - - $incontext['member_id'] = Db::$db->insert( - '', - Db::$db->prefix . 'members', - [ - 'member_name' => 'string-25', - 'real_name' => 'string-25', - 'passwd' => 'string', - 'email_address' => 'string', - 'id_group' => 'int', - 'posts' => 'int', - 'date_registered' => 'int', - 'password_salt' => 'string', - 'lngfile' => 'string', - 'personal_text' => 'string', - 'avatar' => 'string', - 'member_ip' => 'inet', - 'member_ip2' => 'inet', - 'buddy_list' => 'string', - 'pm_ignore_list' => 'string', - 'website_title' => 'string', - 'website_url' => 'string', - 'signature' => 'string', - 'usertitle' => 'string', - 'secret_question' => 'string', - 'additional_groups' => 'string', - 'ignore_boards' => 'string', - ], - [ - [ - $_POST['username'], - $_POST['username'], - $_POST['password1'], - $_POST['email'], - 1, - 0, - time(), - $incontext['member_salt'], - '', - '', - '', - $ip, - $ip, - '', - '', - '', - '', - '', - '', - '', - '', - '', - ], - ], - ['id_member'], - 1, - ); - } - - // If we're here we're good. - return true; - } - - return false; -} - -// Final step, clean up and a complete message! -function DeleteInstall() -{ - global $incontext; - global $databases; - - $incontext['page_title'] = Lang::$txt['congratulations']; - $incontext['sub_template'] = 'delete_install'; - $incontext['continue'] = 0; - - Config::load(); - load_database(); - - chdir(Config::$boarddir); - - // Reload $modSettings. - Config::reloadModSettings(); - - // Bring a warning over. - if (!empty($incontext['account_existed'])) { - $incontext['warning'] = $incontext['account_existed']; - } - - // As track stats is by default enabled let's add some activity. - Db::$db->insert( - 'ignore', - '{db_prefix}log_activity', - [ - 'date' => 'date', - 'topics' => 'int', - 'posts' => 'int', - 'registers' => 'int', - ], - [ - [ - Time::strftime('%Y-%m-%d', time()), - 1, - 1, - !empty($incontext['member_id']) ? 1 : 0, - ], - ], - ['date'], - ); - - // We're going to want our lovely Config::$modSettings now. - $request = Db::$db->query( - '', - 'SELECT variable, value - FROM {db_prefix}settings', - [ - 'db_error_skip' => true, - ], - ); - - // Only proceed if we can load the data. - if ($request) { - while ($row = Db::$db->fetch_row($request)) { - Config::$modSettings[$row[0]] = $row[1]; - } - Db::$db->free_result($request); - } - - // Automatically log them in ;) - if (isset($incontext['member_id'], $incontext['member_salt'])) { - Cookie::setLoginCookie(Cookie::LENGTH_DEFAULT, $incontext['member_id'], Cookie::encrypt($_POST['password1'], $incontext['member_salt'])); - } - - $result = Db::$db->query( - '', - 'SELECT value - FROM {db_prefix}settings - WHERE variable = {string:db_sessions}', - [ - 'db_sessions' => 'databaseSession_enable', - 'db_error_skip' => true, - ], - ); - - if (Db::$db->num_rows($result) != 0) { - list($db_sessions) = Db::$db->fetch_row($result); - } - Db::$db->free_result($result); - - if (empty($db_sessions)) { - $_SESSION['admin_time'] = time(); - } else { - $_SERVER['HTTP_USER_AGENT'] = substr($_SERVER['HTTP_USER_AGENT'], 0, 211); - - Db::$db->insert( - 'replace', - '{db_prefix}sessions', - [ - 'session_id' => 'string', - 'last_update' => 'int', - 'data' => 'string', - ], - [ - [ - session_id(), - time(), - 'USER_AGENT|s:' . strlen($_SERVER['HTTP_USER_AGENT']) . ':"' . $_SERVER['HTTP_USER_AGENT'] . '";admin_time|i:' . time() . ';', - ], - ], - ['session_id'], - ); - } - - Logging::updateStats('member'); - Logging::updateStats('message'); - Logging::updateStats('topic'); - - $request = Db::$db->query( - '', - 'SELECT id_msg - FROM {db_prefix}messages - WHERE id_msg = 1 - AND modified_time = 0 - LIMIT 1', - [ - 'db_error_skip' => true, - ], - ); - - if (Db::$db->num_rows($request) > 0) { - Logging::updateStats('subject', 1, htmlspecialchars(Lang::$txt['default_topic_subject'])); - } - Db::$db->free_result($request); - - // Now is the perfect time to fetch the SM files. - // Sanity check that they loaded earlier! - if (isset(Config::$modSettings['recycle_board'])) { - (new TaskRunner())->runScheduledTasks(['fetchSMfiles']); // Now go get those files! - - // We've just installed! - if (isset($incontext['member_id'])) { - User::setMe($incontext['member_id']); - } else { - User::load(); - } - - User::$me->ip = $_SERVER['REMOTE_ADDR']; - - Logging::logAction('install', ['version' => SMF_FULL_VERSION], 'admin'); - } - - // Disable the legacy BBC by default for new installs - Config::updateModSettings([ - 'disabledBBC' => implode(',', Utils::$context['legacy_bbc']), - ]); - - // Some final context for the template. - $incontext['dir_still_writable'] = is_writable(Config::$boarddir) && substr(__FILE__, 1, 2) != ':\\'; - $incontext['probably_delete_install'] = isset($_SESSION['installer_temp_ftp']) || is_writable(Config::$boarddir) || is_writable(__FILE__); - - // Update hash's cost to an appropriate setting - Config::updateModSettings([ - 'bcrypt_hash_cost' => Security::hashBenchmark(), - ]); - - return false; -} - -function installer_updateSettingsFile($vars, $rebuild = false) -{ - if (!is_writable(SMF_SETTINGS_FILE)) { - @chmod(SMF_SETTINGS_FILE, 0777); - - if (!is_writable(SMF_SETTINGS_FILE)) { - return false; - } - } - - return Config::updateSettingsFile($vars, false, $rebuild); -} - -// Create an .htaccess file to prevent mod_security. SMF has filtering built-in. -function fixModSecurity() -{ - $htaccess_addition = ' - - # Turn off mod_security filtering. SMF is a big boy, it doesn\'t need its hands held. - SecFilterEngine Off - - # The below probably isn\'t needed, but better safe than sorry. - SecFilterScanPOST Off -'; - - if (!function_exists('apache_get_modules') || !in_array('mod_security', apache_get_modules())) { - return true; - } - - if (file_exists(Config::$boarddir . '/.htaccess') && is_writable(Config::$boarddir . '/.htaccess')) { - $current_htaccess = implode('', file(Config::$boarddir . '/.htaccess')); - - // Only change something if mod_security hasn't been addressed yet. - if (!str_contains($current_htaccess, '')) { - if ($ht_handle = fopen(Config::$boarddir . '/.htaccess', 'a')) { - fwrite($ht_handle, $htaccess_addition); - fclose($ht_handle); - - return true; - } - - return false; - } - - return true; - } - - if (file_exists(Config::$boarddir . '/.htaccess')) { - return str_contains(implode('', file(Config::$boarddir . '/.htaccess')), ''); - } - - if (is_writable(Config::$boarddir)) { - if ($ht_handle = fopen(Config::$boarddir . '/.htaccess', 'w')) { - fwrite($ht_handle, $htaccess_addition); - fclose($ht_handle); - - return true; - } - - return false; - } - - return false; -} - -function template_install_above() -{ - global $incontext, $installurl; - - echo ' - - - - - ', Lang::$txt['smf_installer'], ' - - - ', Lang::$txt['lang_rtl'] == '1' ? '' : '', ' - - - - - -
- -
'; - - // Have we got a language drop down - if so do it on the first step only. - if (!empty($incontext['detected_languages']) && count($incontext['detected_languages']) > 1 && $incontext['current_step'] == 0) { - echo ' -
-
-
-
-
- - - -
-
-
-
-
-
'; - } - - echo ' -
-
-
-

', Lang::$txt['upgrade_progress'], '

-
    '; - - foreach ($incontext['steps'] as $num => $step) { - echo ' - - ', Lang::$txt['upgrade_step'], ' ', $step[0], ': ', $step[1], ' - '; - } - - echo ' -
-
-
-
-

' . Lang::$txt['upgrade_overall_progress'], '

- ', $incontext['overall_percent'], '% -
-
-
-
-

', $incontext['page_title'], '

-
'; -} - -function template_install_below() -{ - global $incontext; - - if (!empty($incontext['continue']) || !empty($incontext['skip'])) { - echo ' -
'; - - if (!empty($incontext['continue'])) { - echo ' - '; - } - - if (!empty($incontext['skip'])) { - echo ' - '; - } - echo ' -
'; - } - - // Show the closing form tag and other data only if not in the last step - if (count($incontext['steps']) - 1 !== (int) $incontext['current_step']) { - echo ' - '; - } - - echo ' -
-
-
-
-
-
- - -'; -} - -// Welcome them to the wonderful world of SMF! -function template_welcome_message() -{ - global $incontext; - - echo ' - -
-

', Lang::getTxt('install_welcome_desc', ['SMF_VERSION' => SMF_VERSION]), '

- '; - - // Show the warnings, or not. - if (template_warning_divs()) { - echo ' -

', Lang::$txt['install_all_lovely'], '

'; - } - - // Say we want the continue button! - if (empty($incontext['error'])) { - $incontext['continue'] = 1; - } - - // For the latest version stuff. - echo ' - '; -} - -// A shortcut for any warning stuff. -function template_warning_divs() -{ - global $incontext; - - // Errors are very serious.. - if (!empty($incontext['error'])) { - echo ' -
-

', Lang::$txt['upgrade_critical_error'], '

- ', $incontext['error'], ' -
'; - } - // A warning message? - elseif (!empty($incontext['warning'])) { - echo ' -
-

', Lang::$txt['upgrade_warning'], '

- ', $incontext['warning'], ' -
'; - } - - return empty($incontext['error']) && empty($incontext['warning']); -} - -function template_chmod_files() -{ - global $incontext; - - echo ' -

', Lang::$txt['ftp_setup_why_info'], '

-
    -
  • ', implode('
  • -
  • ', $incontext['failed_files']), '
  • -
'; - - if (isset($incontext['systemos'], $incontext['detected_path']) && $incontext['systemos'] == 'linux') { - echo ' -
-

', Lang::$txt['chmod_linux_info'], '

- # chmod a+w ', implode(' ' . $incontext['detected_path'] . '/', $incontext['failed_files']), ''; - } - - // This is serious! - if (!template_warning_divs()) { - return; - } - - echo ' -
-

', Lang::$txt['ftp_setup_info'], '

'; - - if (!empty($incontext['ftp_errors'])) { - echo ' -
- ', Lang::$txt['error_ftp_no_connect'], '

- ', implode('
', $incontext['ftp_errors']), '
-
'; - } - - echo ' - -
-
- -
-
-
- - -
- -
', Lang::$txt['ftp_server_info'], '
-
-
- -
-
- -
', Lang::$txt['ftp_username_info'], '
-
-
- -
-
- -
', Lang::$txt['ftp_password_info'], '
-
-
- -
-
- -
', $incontext['ftp']['path_msg'], '
-
-
-
- -
-
- ', Lang::$txt['error_message_click'], ' ', Lang::$txt['ftp_setup_again']; -} - -// Get the database settings prepared. -function template_database_settings() -{ - global $incontext; - - echo ' -
-

', Lang::$txt['db_settings_info'], '

'; - - template_warning_divs(); - - echo ' -
'; - - // More than one database type? - if (count($incontext['supported_databases']) > 1) { - echo ' -
- -
-
- -
', Lang::$txt['db_settings_type_info'], '
-
'; - } else { - echo ' -
- -
'; - } - - echo ' -
- -
-
- -
', Lang::$txt['db_settings_server_info'], '
-
-
- -
-
- -
', Lang::$txt['db_settings_port_info'], '
-
-
- -
-
- -
', Lang::$txt['db_settings_username_info'], '
-
-
- -
-
- -
', Lang::$txt['db_settings_password_info'], '
-
-
- -
-
- -
- ', Lang::$txt['db_settings_database_info'], ' - ', Lang::$txt['db_settings_database_info_note'], ' -
-
-
- -
-
- -
', Lang::$txt['db_settings_prefix_info'], '
-
-
'; - - // Toggles a warning related to db names in PostgreSQL - echo ' - '; -} - -// Stick in their forum settings. -function template_forum_settings() -{ - global $incontext; - - echo ' - -

', Lang::$txt['install_settings_info'], '

'; - - template_warning_divs(); - - echo ' -
-
- -
-
- -
', Lang::$txt['install_settings_name_info'], '
-
-
- -
-
- -
', Lang::$txt['install_settings_url_info'], '
-
-
- -
-
- -
', Lang::$txt['install_settings_reg_mode_info'], '
-
-
', Lang::$txt['install_settings_compress'], ':
-
- - -
', Lang::$txt['install_settings_compress_info'], '
-
-
', Lang::$txt['install_settings_dbsession'], ':
-
- - -
', $incontext['test_dbsession'] ? Lang::$txt['install_settings_dbsession_info1'] : Lang::$txt['install_settings_dbsession_info2'], '
-
-
', Lang::$txt['install_settings_stats'], ':
-
- - -
', Lang::$txt['install_settings_stats_info'], '
-
-
', Lang::$txt['force_ssl'], ':
-
- - -
', Lang::$txt['force_ssl_info'], '
-
-
'; -} - -// Show results of the database population. -function template_populate_database() -{ - global $incontext; - - echo ' - -

', !empty($incontext['was_refresh']) ? Lang::$txt['user_refresh_install_desc'] : Lang::$txt['db_populate_info'], '

'; - - if (!empty($incontext['sql_results'])) { - echo ' -
    -
  • ', implode('
  • ', $incontext['sql_results']), '
  • -
'; - } - - if (!empty($incontext['failures'])) { - echo ' -
', Lang::$txt['error_db_queries'], '
-
    '; - - foreach ($incontext['failures'] as $line => $fail) { - echo ' -
  • ', Lang::$txt['error_db_queries_line'], $line + 1, ': ', nl2br(htmlspecialchars($fail)), '
  • '; - } - - echo ' -
'; - } - - echo ' -

', Lang::$txt['db_populate_info2'], '

'; - - template_warning_divs(); - - echo ' - '; -} - -// Create the admin account. -function template_admin_account() -{ - global $incontext; - - echo ' - -

', Lang::$txt['user_settings_info'], '

'; - - template_warning_divs(); - - echo ' -
-
- -
-
- -
', Lang::$txt['user_settings_username_info'], '
-
-
- -
-
- -
', Lang::$txt['user_settings_password_info'], '
-
-
- -
-
- -
', Lang::$txt['user_settings_again_info'], '
-
-
- -
-
- -
', Lang::$txt['user_settings_admin_email_info'], '
-
-
- -
-
- -
', Lang::$txt['user_settings_server_email_info'], '
-
-
'; - - if ($incontext['require_db_confirm']) { - echo ' -

', Lang::$txt['user_settings_database'], '

-

', Lang::$txt['user_settings_database_info'], '

+define('SMF', 'INSTALL'); +define('SMF_INSTALLING', 1); +define('SMF_SETTINGS_FILE', __DIR__ . '/Settings.php'); -
- -
'; - } +// Ensure Settings.php exists. We can recover if it is blank, but it must exist. +if (!file_exists(SMF_SETTINGS_FILE)) { + @touch(SMF_SETTINGS_FILE); } -// Tell them it's done, and to delete. -function template_delete_install() -{ - global $incontext, $installurl; - - echo ' -

', Lang::$txt['congratulations_help'], '

'; - - template_warning_divs(); - - // Install directory still writable? - if ($incontext['dir_still_writable']) { - echo ' -

', Lang::$txt['still_writable'], '

'; - } +// Initialize. +require_once __DIR__ . '/index.php'; - // Don't show the box if it's like 99% sure it won't work :P. - if ($incontext['probably_delete_install']) { - echo ' - - '; - } - - echo ' -

', Lang::getTxt('go_to_your_forum', ['scripturl' => Config::$boardurl . '/index.php']), '

-
- ', Lang::$txt['good_luck']; -} +(new SMF\Maintenance\Maintenance())->execute(SMF\Maintenance\Maintenance::INSTALL); From 99321ba410694cfc41dc595e4adf6d5ffa11d4d5 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Sun, 18 May 2025 22:54:19 -0600 Subject: [PATCH 10/90] Rewrites upgrader Signed-off-by: Jon Stovell --- Languages/en_US/Maintenance.php | 32 +- Sources/Maintenance/Cleanup/CleanupBase.php | 57 + Sources/Maintenance/Cleanup/index.php | 8 + Sources/Maintenance/Cleanup/v2_1/OldFiles.php | 63 + Sources/Maintenance/Cleanup/v2_1/index.php | 8 + Sources/Maintenance/Cleanup/v3_0/OldFiles.php | 168 + .../Maintenance/Cleanup/v3_0/TasksDirCase.php | 84 + Sources/Maintenance/Cleanup/v3_0/index.php | 8 + Sources/Maintenance/GenericSubStep.php | 111 + .../Maintenance/Migration/MigrationBase.php | 155 + Sources/Maintenance/Migration/index.php | 8 + .../Migration/v2_1/AdminInfoFiles.php | 100 + .../Migration/v2_1/AgreementUpdate.php | 219 + .../Migration/v2_1/AlertsObsolete.php | 136 + .../Migration/v2_1/AlertsWatchedBoards.php | 110 + .../Migration/v2_1/AlertsWatchedTopics.php | 115 + .../Migration/v2_1/AttachmentDirectory.php | 68 + .../Migration/v2_1/AttachmentSizes.php | 87 + .../Maintenance/Migration/v2_1/AutoNotify.php | 126 + .../Migration/v2_1/BoardDescriptions.php | 85 + .../Migration/v2_1/BoardPermissionsView.php | 247 + .../Migration/v2_1/CalendarEvents.php | 87 + .../Migration/v2_1/CalendarUpdates.php | 273 + .../Migration/v2_1/CategoryDescrptions.php | 54 + .../Migration/v2_1/CollapsedCategories.php | 90 + .../Migration/v2_1/CreateAlerts.php | 221 + .../Migration/v2_1/CreateBackgroundTasks.php | 53 + .../Migration/v2_1/CreateLogGroupRequests.php | 76 + .../Migration/v2_1/CreateMemberLogins.php | 52 + .../Migration/v2_1/CustomFieldsPart1.php | 219 + .../Migration/v2_1/CustomFieldsPart2.php | 140 + .../Migration/v2_1/CustomFieldsPart3.php | 103 + .../Maintenance/Migration/v2_1/FixDates.php | 199 + .../Migration/v2_1/IdxAdminInfo.php | 75 + .../Migration/v2_1/IdxAttachments.php | 56 + .../Maintenance/Migration/v2_1/IdxBoards.php | 75 + .../Migration/v2_1/IdxLogActions.php | 60 + .../Migration/v2_1/IdxLogActivity.php | 74 + .../Migration/v2_1/IdxLogComments.php | 75 + .../Migration/v2_1/IdxLogPackages.php | 76 + .../Migration/v2_1/IdxLogSubscribed.php | 57 + .../Maintenance/Migration/v2_1/IdxMembers.php | 202 + .../Migration/v2_1/IdxMessages.php | 164 + .../Migration/v2_1/IdxScheduledTasks.php | 73 + .../Maintenance/Migration/v2_1/IdxTopics.php | 68 + .../Migration/v2_1/Ipv6BanItem.php | 163 + .../Maintenance/Migration/v2_1/Ipv6Base.php | 334 + .../Migration/v2_1/Ipv6LogAction.php | 89 + .../Migration/v2_1/Ipv6LogBanned.php | 89 + .../Migration/v2_1/Ipv6LogErrors.php | 99 + .../Migration/v2_1/Ipv6LogFloodControl.php | 83 + .../Migration/v2_1/Ipv6LogOnline.php | 63 + .../v2_1/Ipv6LogReportedComments.php | 70 + .../Migration/v2_1/Ipv6MemberLogins.php | 70 + .../Migration/v2_1/Ipv6MembersIP.php | 60 + .../Migration/v2_1/Ipv6MembersIP2.php | 60 + .../Migration/v2_1/Ipv6Messages.php | 78 + .../Migration/v2_1/LegacyAttachments.php | 282 + .../Maintenance/Migration/v2_1/LegacyData.php | 204 + Sources/Maintenance/Migration/v2_1/Likes.php | 83 + .../Migration/v2_1/LogErrorsBacktrace.php | 62 + .../Migration/v2_1/LogOnlineURL.php | 51 + .../v2_1/LogReportedCommentsEmail.php | 77 + .../Migration/v2_1/LogSpiderHitsURL.php | 52 + .../Maintenance/Migration/v2_1/MailQueue.php | 50 + .../v2_1/MemberGroupsTfaRequired.php | 62 + .../Migration/v2_1/MembergroupIcon.php | 128 + .../Migration/v2_1/MembersHideEmail.php | 77 + .../Migration/v2_1/MembersLangUTF8.php | 46 + .../Migration/v2_1/MembersOpenID.php | 71 + .../Migration/v2_1/MembersTfaBackup.php | 62 + .../Migration/v2_1/MembersTfaSecret.php | 62 + .../Migration/v2_1/MembersTimezone.php | 72 + .../Maintenance/Migration/v2_1/Mentions.php | 66 + .../Migration/v2_1/MessagesModifiedReason.php | 72 + .../Migration/v2_1/ModeratorGroups.php | 66 + .../Migration/v2_1/MovedTopics.php | 63 + .../Migration/v2_1/MysqlLegacyData.php | 204 + .../Migration/v2_1/MysqlModFixes.php | 155 + Sources/Maintenance/Migration/v2_1/OpenID.php | 55 + .../Migration/v2_1/PackageManager.php | 70 + .../Migration/v2_1/Permissions.php | 333 + .../Migration/v2_1/PersonalMessageLabels.php | 349 + .../v2_1/PersonalMessageNotification.php | 141 + .../Migration/v2_1/PostgreSQLFindInSet.php | 64 + .../Migration/v2_1/PostgreSQLIPv6Helper.php | 62 + .../Migration/v2_1/PostgreSQLSequences.php | 222 + .../Migration/v2_1/PostgreSQLUnlogged.php | 123 + .../Migration/v2_1/PostgreSqlTime.php | 85 + .../Migration/v2_1/PostgresqlSchemaDiff.php | 150 + .../Migration/v2_1/RemoveKarma.php | 86 + .../Migration/v2_1/ScheduledTasks.php | 123 + .../Maintenance/Migration/v2_1/SessionIDs.php | 70 + .../Migration/v2_1/SettingsUpdate.php | 258 + .../Maintenance/Migration/v2_1/Smileys.php | 172 + .../Migration/v2_1/ThemeSettings.php | 194 + .../Migration/v2_1/TopicUnwatch.php | 60 + .../Maintenance/Migration/v2_1/UserDrafts.php | 173 + .../Migration/v2_1/ValidationServers.php | 127 + .../Migration/v2_1/VerificationQuestions.php | 111 + Sources/Maintenance/Migration/v2_1/index.php | 8 + .../Migration/v3_0/ConvertToInnoDb.php | 83 + .../Migration/v3_0/ErrorLogSession.php | 45 + .../Maintenance/Migration/v3_0/EventUids.php | 71 + .../Migration/v3_0/HolidaysToEvents.php | 683 ++ .../Migration/v3_0/LanguageDirectory.php | 148 + .../Maintenance/Migration/v3_0/MailType.php | 68 + .../Migration/v3_0/MessageVersion.php | 68 + .../Migration/v3_0/PackageVersion.php | 54 + .../Migration/v3_0/RecurringEvents.php | 138 + .../Migration/v3_0/RemoveCookieTime.php | 45 + .../v3_0/SearchResultsPrimaryKey.php | 55 + .../Migration/v3_0/SpoofDetector.php | 67 + Sources/Maintenance/Migration/v3_0/index.php | 8 + Sources/Maintenance/SubStepInterface.php | 40 + Sources/Maintenance/Tools/Upgrade.php | 1720 +++++ Sources/Maintenance/Utf8ConverterStep.php | 934 +++ Sources/Tasks/Utf8EntityDecode.php | 398 ++ Themes/default/MaintenanceTemplate.php | 119 + Themes/default/UpgradeTemplate.php | 966 +++ Themes/default/css/maintenance.css | 4 +- other/upgrade-helper.php | 500 -- other/upgrade.php | 5969 +---------------- 123 files changed, 16685 insertions(+), 6476 deletions(-) create mode 100644 Sources/Maintenance/Cleanup/CleanupBase.php create mode 100644 Sources/Maintenance/Cleanup/index.php create mode 100644 Sources/Maintenance/Cleanup/v2_1/OldFiles.php create mode 100644 Sources/Maintenance/Cleanup/v2_1/index.php create mode 100644 Sources/Maintenance/Cleanup/v3_0/OldFiles.php create mode 100644 Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php create mode 100644 Sources/Maintenance/Cleanup/v3_0/index.php create mode 100644 Sources/Maintenance/GenericSubStep.php create mode 100644 Sources/Maintenance/Migration/MigrationBase.php create mode 100644 Sources/Maintenance/Migration/index.php create mode 100644 Sources/Maintenance/Migration/v2_1/AdminInfoFiles.php create mode 100644 Sources/Maintenance/Migration/v2_1/AgreementUpdate.php create mode 100644 Sources/Maintenance/Migration/v2_1/AlertsObsolete.php create mode 100644 Sources/Maintenance/Migration/v2_1/AlertsWatchedBoards.php create mode 100644 Sources/Maintenance/Migration/v2_1/AlertsWatchedTopics.php create mode 100644 Sources/Maintenance/Migration/v2_1/AttachmentDirectory.php create mode 100644 Sources/Maintenance/Migration/v2_1/AttachmentSizes.php create mode 100644 Sources/Maintenance/Migration/v2_1/AutoNotify.php create mode 100644 Sources/Maintenance/Migration/v2_1/BoardDescriptions.php create mode 100644 Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php create mode 100644 Sources/Maintenance/Migration/v2_1/CalendarEvents.php create mode 100644 Sources/Maintenance/Migration/v2_1/CalendarUpdates.php create mode 100644 Sources/Maintenance/Migration/v2_1/CategoryDescrptions.php create mode 100644 Sources/Maintenance/Migration/v2_1/CollapsedCategories.php create mode 100644 Sources/Maintenance/Migration/v2_1/CreateAlerts.php create mode 100644 Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php create mode 100644 Sources/Maintenance/Migration/v2_1/CreateLogGroupRequests.php create mode 100644 Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php create mode 100644 Sources/Maintenance/Migration/v2_1/CustomFieldsPart1.php create mode 100644 Sources/Maintenance/Migration/v2_1/CustomFieldsPart2.php create mode 100644 Sources/Maintenance/Migration/v2_1/CustomFieldsPart3.php create mode 100644 Sources/Maintenance/Migration/v2_1/FixDates.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxAdminInfo.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxAttachments.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxBoards.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxLogActions.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxLogActivity.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxLogComments.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxLogPackages.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxLogSubscribed.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxMembers.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxMessages.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxScheduledTasks.php create mode 100644 Sources/Maintenance/Migration/v2_1/IdxTopics.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6Base.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6LogAction.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6LogBanned.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6LogErrors.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6LogFloodControl.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6LogReportedComments.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6MemberLogins.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6MembersIP.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6MembersIP2.php create mode 100644 Sources/Maintenance/Migration/v2_1/Ipv6Messages.php create mode 100644 Sources/Maintenance/Migration/v2_1/LegacyAttachments.php create mode 100644 Sources/Maintenance/Migration/v2_1/LegacyData.php create mode 100644 Sources/Maintenance/Migration/v2_1/Likes.php create mode 100644 Sources/Maintenance/Migration/v2_1/LogErrorsBacktrace.php create mode 100644 Sources/Maintenance/Migration/v2_1/LogOnlineURL.php create mode 100644 Sources/Maintenance/Migration/v2_1/LogReportedCommentsEmail.php create mode 100644 Sources/Maintenance/Migration/v2_1/LogSpiderHitsURL.php create mode 100644 Sources/Maintenance/Migration/v2_1/MailQueue.php create mode 100644 Sources/Maintenance/Migration/v2_1/MemberGroupsTfaRequired.php create mode 100644 Sources/Maintenance/Migration/v2_1/MembergroupIcon.php create mode 100644 Sources/Maintenance/Migration/v2_1/MembersHideEmail.php create mode 100644 Sources/Maintenance/Migration/v2_1/MembersLangUTF8.php create mode 100644 Sources/Maintenance/Migration/v2_1/MembersOpenID.php create mode 100644 Sources/Maintenance/Migration/v2_1/MembersTfaBackup.php create mode 100644 Sources/Maintenance/Migration/v2_1/MembersTfaSecret.php create mode 100644 Sources/Maintenance/Migration/v2_1/MembersTimezone.php create mode 100644 Sources/Maintenance/Migration/v2_1/Mentions.php create mode 100644 Sources/Maintenance/Migration/v2_1/MessagesModifiedReason.php create mode 100644 Sources/Maintenance/Migration/v2_1/ModeratorGroups.php create mode 100644 Sources/Maintenance/Migration/v2_1/MovedTopics.php create mode 100644 Sources/Maintenance/Migration/v2_1/MysqlLegacyData.php create mode 100644 Sources/Maintenance/Migration/v2_1/MysqlModFixes.php create mode 100644 Sources/Maintenance/Migration/v2_1/OpenID.php create mode 100644 Sources/Maintenance/Migration/v2_1/PackageManager.php create mode 100644 Sources/Maintenance/Migration/v2_1/Permissions.php create mode 100644 Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php create mode 100644 Sources/Maintenance/Migration/v2_1/PersonalMessageNotification.php create mode 100644 Sources/Maintenance/Migration/v2_1/PostgreSQLFindInSet.php create mode 100644 Sources/Maintenance/Migration/v2_1/PostgreSQLIPv6Helper.php create mode 100644 Sources/Maintenance/Migration/v2_1/PostgreSQLSequences.php create mode 100644 Sources/Maintenance/Migration/v2_1/PostgreSQLUnlogged.php create mode 100644 Sources/Maintenance/Migration/v2_1/PostgreSqlTime.php create mode 100644 Sources/Maintenance/Migration/v2_1/PostgresqlSchemaDiff.php create mode 100644 Sources/Maintenance/Migration/v2_1/RemoveKarma.php create mode 100644 Sources/Maintenance/Migration/v2_1/ScheduledTasks.php create mode 100644 Sources/Maintenance/Migration/v2_1/SessionIDs.php create mode 100644 Sources/Maintenance/Migration/v2_1/SettingsUpdate.php create mode 100644 Sources/Maintenance/Migration/v2_1/Smileys.php create mode 100644 Sources/Maintenance/Migration/v2_1/ThemeSettings.php create mode 100644 Sources/Maintenance/Migration/v2_1/TopicUnwatch.php create mode 100644 Sources/Maintenance/Migration/v2_1/UserDrafts.php create mode 100644 Sources/Maintenance/Migration/v2_1/ValidationServers.php create mode 100644 Sources/Maintenance/Migration/v2_1/VerificationQuestions.php create mode 100644 Sources/Maintenance/Migration/v2_1/index.php create mode 100644 Sources/Maintenance/Migration/v3_0/ConvertToInnoDb.php create mode 100644 Sources/Maintenance/Migration/v3_0/ErrorLogSession.php create mode 100644 Sources/Maintenance/Migration/v3_0/EventUids.php create mode 100644 Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php create mode 100644 Sources/Maintenance/Migration/v3_0/LanguageDirectory.php create mode 100644 Sources/Maintenance/Migration/v3_0/MailType.php create mode 100644 Sources/Maintenance/Migration/v3_0/MessageVersion.php create mode 100644 Sources/Maintenance/Migration/v3_0/PackageVersion.php create mode 100644 Sources/Maintenance/Migration/v3_0/RecurringEvents.php create mode 100644 Sources/Maintenance/Migration/v3_0/RemoveCookieTime.php create mode 100644 Sources/Maintenance/Migration/v3_0/SearchResultsPrimaryKey.php create mode 100644 Sources/Maintenance/Migration/v3_0/SpoofDetector.php create mode 100644 Sources/Maintenance/Migration/v3_0/index.php create mode 100644 Sources/Maintenance/SubStepInterface.php create mode 100644 Sources/Maintenance/Tools/Upgrade.php create mode 100644 Sources/Maintenance/Utf8ConverterStep.php create mode 100644 Sources/Tasks/Utf8EntityDecode.php create mode 100644 Themes/default/UpgradeTemplate.php delete mode 100644 other/upgrade-helper.php diff --git a/Languages/en_US/Maintenance.php b/Languages/en_US/Maintenance.php index 3c3801a8628..525b980173e 100644 --- a/Languages/en_US/Maintenance.php +++ b/Languages/en_US/Maintenance.php @@ -91,7 +91,7 @@ $txt['upgrade_step_migration'] = 'Migrations'; $txt['upgrade_step_convertutf'] = 'Convert to UTF-8'; $txt['upgrade_step_cleanup'] = 'Cleanup'; -$txt['upgrade_step_delete'] = 'Finalize Upgrade'; +$txt['upgrade_step_finalize'] = 'Finalize Upgrade'; // Installer - Welcome. $txt['install_welcome'] = 'Welcome'; @@ -295,14 +295,21 @@ // Upgrade - steps and substeps $txt['upgrade_steps'] = 'Steps'; $txt['upgrade_substeps'] = 'Substeps'; -$txt['upgrade_db_changes'] = 'Executing database changes'; -$txt['upgrade_db_patient'] = 'Please be patient - this may take some time on large forums. The time elapsed increments from the server to show progress is being made.'; +$txt['upgrade_executing_substeps'] = 'Executing {type, select, + migration {database changes} + cleanup {cleanup steps} + other {substeps} +}'; +$txt['upgrade_please_be_patient'] = 'Please be patient - this may take some time on large forums. The time elapsed increments from the server to show progress is being made.'; $txt['upgrade_current_step'] = 'Current Step:'; -$txt['upgrade_current_substep'] = 'Current Migration:'; -$txt['upgrade_completed'] = 'Completed'; -$txt['upgrade_outof'] = 'out of'; -$txt['upgrade_db_complete'] = 'Database update complete! Click Continue to proceed.'; -$txt['upgrade_completed_migration'] = ' Completed Migration:'; +$txt['upgrade_current_substep'] = 'Current Substep: {substep}'; +$txt['upgrade_substep_progress'] = 'Completed {substep_done} out of {total_substeps} {type, select, + migration {database changes} + cleanup {cleanup steps} + other {substeps} +}.'; +$txt['upgrade_completed_substep'] = ' Completed Substep:'; +$txt['upgrade_step_complete'] = 'The "{step}" step is complete! Click Continue to proceed.'; @@ -414,9 +421,10 @@ $txt['upgrade_continue_step'] = 'Continue from step reached during last execution of upgrade script.'; $txt['upgrade_bypass'] = 'Note: If necessary, the above security check can be bypassed for users who may administrate a server, but may not have admin rights on the forum. In order to bypass the above check, simply open "upgrade.php" in a text editor and replace "$disable_security = false;" with "$disable_security = true;" and refresh this page.'; $txt['upgrade_areyouready'] = 'Before the upgrade gets underway, please review the options below and press "Continue" when you are ready to begin.'; -$txt['upgrade_backup_table'] = 'Perform a tables backup in your database with the prefix'; +$txt['upgrade_backup_table'] = 'Backup SMF tables in your database using the prefix {0}'; $txt['upgrade_backup_complete'] = 'Backup Complete! Click Continue to Proceed.'; -$txt['upgrade_recommended'] = 'recommended!'; +$txt['upgrade_recommended'] = 'Strongly recommended!'; +$txt['upgrade_backup_already_exists'] = 'Backup already exists. If you enable this option, the existing backup will be replaced with a new one.'; $txt['upgrade_maintenance'] = 'Put the forum into maintenance mode during upgrade.'; $txt['upgrade_maintenance_title'] = 'Maintenance Title:'; $txt['upgrade_maintenance_message'] = 'Maintenance Message:'; @@ -474,7 +482,7 @@ $txt['upgrade_incorrect_settings'] = 'If these seem incorrect please open Settings.php in a text editor before proceeding with this upgrade. If they are incorrect due to you moving your forum to a new location please download and execute the Repair Settings tool from the Simple Machines website before continuing.'; $txt['upgrade_fulltext_error'] = 'Your fulltext search index was dropped to facilitate the conversion. You will need to recreate it.'; -$txt['upgrade_writable_files'] = 'The following files need to be writable to continue the upgrade. Please ensure the Windows permissions are correctly set to allow this:'; +$txt['upgrade_writable_files'] = 'The following files need to be writable to continue the upgrade. Please ensure the file permissions are correctly set to allow this:'; $txt['upgrade_time_user'] = '"{name}" is running the upgrade script.'; $txt['upgrade_completed_time_hms'] = 'Upgrade completed in {h, plural, @@ -547,3 +555,5 @@ $txt['upgrade_complete'] = 'Upgrade Complete'; $txt['converting_utf8'] = 'Converting to UTF-8'; $txt['converting_json'] = 'Converting to JSON'; + +$txt['converting_table_to_utf8mb4'] = 'Converting table {0} to utf8mb4'; diff --git a/Sources/Maintenance/Cleanup/CleanupBase.php b/Sources/Maintenance/Cleanup/CleanupBase.php new file mode 100644 index 00000000000..7ff111b7f9e --- /dev/null +++ b/Sources/Maintenance/Cleanup/CleanupBase.php @@ -0,0 +1,57 @@ + [ + // Removed in 2.1. + 'core', + // Removed in 1.1. + 'default/Combat.template.php', + 'default/Modlog.template.php', + 'default/fader.js', + 'default/script.js', + 'default/spellcheck.js', + 'default/xml_board.js', + 'default/xml_topic.js', + ], + // Files in the Sources directory. + 'sourcedir' => [ + // Removed in 2.1. + 'DumpDatabase.php', + 'LockTopic.php', + // Removed in 2.0. + 'ModSettings.php', + ], + // Files in the Smileys directory. + 'smileysdir' => [], + // Files in the avatars directory. + 'avatardir' => [], + // Files in the forum's root directory. + 'boarddir' => [], + ]; +} diff --git a/Sources/Maintenance/Cleanup/v2_1/index.php b/Sources/Maintenance/Cleanup/v2_1/index.php new file mode 100644 index 00000000000..ee0549de33f --- /dev/null +++ b/Sources/Maintenance/Cleanup/v2_1/index.php @@ -0,0 +1,8 @@ + [], + // Files in the Sources directory. + 'sourcedir' => [], + // Files in the Smileys directory. + 'smileysdir' => [], + // Files in the avatars directory. + 'avatardir' => [], + // Files in the forum's root directory. + 'boarddir' => [], + ]; + + /**************** + * Public methods + ****************/ + + /** + * 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 + { + foreach ($this->removed as $dir => $files) { + foreach ($files as $file) { + if (is_file($this->getDirPath($dir) . '/' . $file)) { + return true; + } + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $success = true; + + foreach ($this->removed as $dir => $files) { + foreach ($files as $file) { + if (!is_file($this->getDirPath($dir) . '/' . $file)) { + continue; + } + + if (!$this->deletePath($this->getDirPath($dir) . '/' . $file)) { + $success = false; + } + } + } + + return $success; + } + + /****************** + * Internal methods + ******************/ + + /** + * Gets the correct directory path for a key in $this->removed. + * + * @param string $dir A key from $this->removed. + * @throws \Exception if $dir is unrecognized. + * @return string A directory path. + */ + protected function getDirPath(string $dir): string + { + switch ($dir) { + case 'sourcedir': + return Config::$sourcedir; + + case 'themedir': + return Config::$boarddir . '/Themes'; + + case 'smileysdir': + return Config::$boarddir . '/Smileys'; + + case 'avatardir': + return Config::$boarddir . '/avatars'; + + case 'boarddir': + return Config::$boarddir; + + default: + throw new \Exception(); + } + } + + /** + * Deletes a file or directory. + * + * Checks permissions first, just in case. + * + * @param string Path to a file or directory + */ + protected function deletePath(string $path): bool + { + if (!file_exists($path)) { + return true; + } + + if (!Utils::makeWritable($path)) { + return false; + } + + if (!is_dir($path)) { + @unlink($pathname); + } else { + $dir = new \DirectoryIterator($path); + + $to_delete = []; + + foreach ($dir as $fileinfo) { + if (!$fileinfo->isDot()) { + $this->deletePath($fileinfo->getPathname()); + } + } + + @rmdir($path); + } + + return !file_exists($file); + } +} diff --git a/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php b/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php new file mode 100644 index 00000000000..900fb885d64 --- /dev/null +++ b/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php @@ -0,0 +1,84 @@ +test. + */ + public array $test_args = []; + + /** + * @var array|string + * + * A callable to call in the execute() method. + */ + public array|string $exec; + + /** + * @var array + * + * Arguments to pass to $this->exec. + */ + public array $exec_args = []; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + * + * @param string $name The name of this substep. + * @param array|string $exec A callable to call in the execute() method. + * @param array|string|null $test A callable to call in the isCandidate() + * method. If null, isCandidate() will always return true. Default: null. + * @param array $exec_args Arguments to pass to $this->exec. Default: []. + * @param array $test_args Arguments to pass to $this->test. Default: []. + */ + public function __construct( + string $name, + array|string $exec, + array|string|null $test = null, + array $exec_args = [], + array $test_args = [], + ) { + $this->name = $name; + $this->test = $test; + $this->test_args = $test_args; + $this->exec = $exec; + $this->exec_args = $exec_args; + } + + /** + * Checks if the substep should be performed or not. + * + * @return bool True if this substep needs to be run, false otherwise. + */ + public function isCandidate(): bool + { + return !is_callable($this->test) ? true : call_user_func($this->test, ...$this->test_args); + } + + /** + * Runs the substep. + * + * @return bool True if successful (or skipped), false otherwise. + */ + public function execute(): bool + { + return !is_callable($this->exec) ? false : call_user_func($this->exec, ...$this->exec_args); + } +} diff --git a/Sources/Maintenance/Migration/MigrationBase.php b/Sources/Maintenance/Migration/MigrationBase.php new file mode 100644 index 00000000000..cc41716e3af --- /dev/null +++ b/Sources/Maintenance/Migration/MigrationBase.php @@ -0,0 +1,155 @@ +checkAndHandleTimeout(); + } + + /** + * Wrapper for the database query. + * + * Ensures the query runs without handling errors, as we do not have that luxury. + */ + protected function query(string $identifier, string $db_string, array $db_values = [], ?object $connection = null): object|bool + { + if (!empty(Config::$modSettings['disableQueryCheck'])) { + Config::$modSettings['disableQueryCheck'] = true; + } + + if (!empty($db_values['unbuffered'])) { + Db::$unbuffered = true; + } + + $db_values += [ + 'db_error_skip' => true, + ]; + + $result = Db::$db->query($identifier, $db_string, $db_values, $connection); + Db::$unbuffered = false; + + // Did it work? + if ($result !== false) { + return $result; + } + + // Oh no! What happened? + $db_error_message = Db::$db->error(Db::$db_connection); + + // Check whether we can fix this. + $halt = Db::$db->processError($db_error_message, Db::$db->quote($db_string, $db_values, $connection)); + + if ($halt === false) { + return $result; + } + + if (Sapi::isCLI()) { + echo 'Unsuccessful! Database error message:', "\n", $db_error_message, "\n"; + + die; + } + + // If this is JSON, we can throw it, modern code will catch this. + if (Maintenance::isJson()) { + $file = null; + $line = null; + + foreach (debug_backtrace() as $step) { + $file = $step['file']; + $line = $step['line']; + break; + } + + throw new \ErrorException($db_error_message, 0, E_USER_ERROR, $file, $line); + } + + Maintenance::$context['try_again'] = true; + Maintenance::$fatal_error = ' + ' . Lang::$txt['upgrade_unsuccessful'] . '
+
+ ' . Lang::getTxt( + 'query_failed', + [ + 'QUERY_STRING' => '
' . nl2br(htmlspecialchars(trim($db_string))) . ';
', + 'QUERY_ERROR' => '
' . nl2br(htmlspecialchars($db_error_message)) . '
', + ], + ) . + ' +
'; + + Maintenance::$tool->preExit(); + Maintenance::exit(); + + return false; + } +} diff --git a/Sources/Maintenance/Migration/index.php b/Sources/Maintenance/Migration/index.php new file mode 100644 index 00000000000..ee0549de33f --- /dev/null +++ b/Sources/Maintenance/Migration/index.php @@ -0,0 +1,8 @@ +query( + '', + 'DELETE FROM {db_prefix}admin_info_files + WHERE filename IN ({array_string:old_files}) + AND path = {string:old_path}', + [ + 'old_files' => [ + 'latest-packages.js', + 'latest-smileys.js', + 'latest-support.js', + 'latest-themes.js', + ], + 'old_path' => '/smf/', + ], + ); + + $this->handleTimeout(); + + // Don't insert the info if it's already there... + $file_check = $this->query( + '', + 'SELECT id_file + FROM {db_prefix}admin_info_files + WHERE filename = {string:latest-versions}', + [ + 'latest-versions' => 'latest-versions.txt', + ], + ); + + if (Db::$db->num_rows($file_check) == 0) { + Db::$db->insert( + '', + '{db_prefix}admin_info_files', + [ + 'filename' => 'string', + 'path' => 'string', + 'parameters' => 'string', + 'data' => 'string', + 'filetype' => 'string', + ], + [ + [ + 'latest-versions.txt', + '/smf/', + 'version=%3$s', + '', + 'text/plain', + ], + ], + ['id_file'], + ); + } + + Db::$db->free_result($file_check); + + $this->handleTimeout(); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php b/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php new file mode 100644 index 00000000000..621dda3c534 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AgreementUpdate.php @@ -0,0 +1,219 @@ + $v) { + if ((substr($k, 0, 7) === 'policy_') && (substr($k, -5) === '-utf8')) { + $utf8_policy_settings[$k] = $v; + } + } + + foreach ($utf8_policy_settings as $var => $val) { + // Note this works on the policy_updated_ strings as well... + $language = substr($var, 7, strlen($var) - 12); + + if (!array_key_exists('policy_' . $language, Config::$modSettings)) { + $newSettings['policy_' . $language] = $val; + $newSettings[$var] = null; + } + } + + if (!empty($newSettings)) { + Config::updateModSettings($newSettings); + } + + // Strip -utf8 from agreement file names + $files = glob(Config::$boarddir . '/agreement.*-utf8.txt'); + + foreach ($files as $filename) { + $newfile = substr($filename, 0, strlen($filename) - 9) . '.txt'; + + // Do not overwrite existing files + if (!file_exists($newfile)) { + @rename($filename, $newfile); + } + } + + // Setup progress bar + $request = $this->query( + '', + ' + SELECT COUNT(*) + FROM {db_prefix}log_actions + WHERE action IN ({array_string:target_actions})', + [ + 'target_actions' => ['policy_accepted', 'agreement_accepted'], + ], + ); + list($maxActions) = Db::$db->fetch_row($request); + Db::$db->free_result($request); + Maintenance::$total_items = (int) $maxActions; + + // Main process loop + $is_done = false; + $start = Maintenance::getCurrentStart(); + + while (!$is_done) { + // Keep looping at the current step. + $this->handleTimeout($start); + + $extras = []; + $request = Db::$db->query( + '', + ' + SELECT id_action, extra + FROM {db_prefix}log_actions + WHERE id_member = {int:blank_id} + AND action IN ({array_string:target_actions}) + AND id_action > {int:last} + ORDER BY id_action + LIMIT {int:limit}', + [ + 'blank_id' => 0, + 'target_actions' => ['policy_accepted', 'agreement_accepted'], + 'last' => $start, + 'limit' => $this->limit, + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $extras[$row['id_action']] = $row['extra']; + } + Db::$db->free_result($request); + + if (empty($extras)) { + $is_done = true; + } else { + $start = max(array_keys($extras)); + } + + foreach ($extras as $id => $extra_ser) { + $extra = $this->upgrade_unserialize($extra_ser); + + if ($extra === false) { + continue; + } + + if (!empty($extra['applicator'])) { + $request = Db::$db->query( + '', + ' + UPDATE {db_prefix}log_actions + SET id_member = {int:id_member} + WHERE id_action = {int:id_action}', + [ + 'id_member' => $extra['applicator'], + 'id_action' => $id, + ], + ); + } + } + } + + + return true; + } + + /****************** + * Internal methods + ******************/ + + /** + * Wrapper for unserialize that attempts to repair corrupted serialized data strings + * + * @param string $string Serialized data that may or may not have been corrupted + * @return string|bool The unserialized data, or false if the repair failed + */ + private function upgrade_unserialize($string) + { + if (!is_string($string)) { + $data = false; + } + // Might be JSON already. + elseif (str_starts_with($string, '{')) { + $data = @json_decode($string, true); + + if (is_null($data)) { + $data = false; + } + } elseif (in_array(substr($string, 0, 2), ['b:', 'i:', 'd:', 's:', 'a:', 'N;'])) { + $data = @Utils::safeUnserialize($string); + + // The serialized data is broken. + if ($data === false) { + // This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8) + $new_string = preg_replace_callback( + '~\bs:(\d+):"(.*?)";(?=$|[bidsaO]:|[{}}]|N;)~s', + function ($matches) { + return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";'; + }, + $string, + ); + + // @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc. + + // Did it work? + $data = @Utils::safeUnserialize($string); + } + } + // Just a plain string, then. + else { + $data = false; + } + + return $data; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/AlertsObsolete.php b/Sources/Maintenance/Migration/v2_1/AlertsObsolete.php new file mode 100644 index 00000000000..40b699ceb27 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AlertsObsolete.php @@ -0,0 +1,136 @@ +query( + '', + 'UPDATE {db_prefix}user_alerts + SET content_type = {literal:member}, content_id = id_member_started + WHERE content_type = {literal:buddy}', + [], + ); + + $this->handleTimeout(); + + $this->query( + '', + 'UPDATE {db_prefix}user_alerts + SET content_type = {literal:member} + WHERE content_type = {literal:profile}', + [], + ); + + $this->handleTimeout(); + + $this->query( + '', + 'UPDATE {db_prefix}user_alerts + SET content_id = id_member_started + WHERE content_type = {literal:member} + AND content_action LIKE {string:content_action}', + ['content_action' => 'register_%'], + ); + + $this->handleTimeout(); + + $this->query( + '', + 'UPDATE {db_prefix}user_alerts + SET content_id = {literal:topic}, + content_action = {literal:unapproved_topic} + WHERE content_type = {literal:unapproved} + AND content_action = {string:content_action}', + ['content_action' => 'topic'], + ); + + $this->handleTimeout(); + + $this->query( + '', + 'UPDATE {db_prefix}user_alerts + SET content_id = {literal:topic}, + content_action = {literal:unapproved_reply} + WHERE content_type = {literal:unapproved} + AND content_action = {string:content_action}', + ['content_action' => 'reply'], + ); + + $this->handleTimeout(); + + $this->query( + '', + 'UPDATE {db_prefix}user_alerts + SET content_id = {literal:topic}, + content_action = {literal:unapproved_post} + WHERE content_type = {literal:unapproved} + AND content_action = {string:content_action}', + ['content_action' => 'post'], + ); + + $this->handleTimeout(); + + $this->query( + '', + 'UPDATE {db_prefix}user_alerts AS a + JOIN {db_prefix}attachments AS f + ON (f.id_attach = a.content_id) + SET + a.content_type = {literal:msg}, + a.content_action = {literal:unapproved_attachment}, + a.content_id = f.id_msg + WHERE content_type = {literal:unapproved} + AND content_action = {literal:attachment}', + [], + ); + + $this->handleTimeout(); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/AlertsWatchedBoards.php b/Sources/Maintenance/Migration/v2_1/AlertsWatchedBoards.php new file mode 100644 index 00000000000..7c11497d201 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AlertsWatchedBoards.php @@ -0,0 +1,110 @@ +query( + '', + 'SELECT COUNT(*) + FROM {db_prefix}log_notify + WHERE id_member <> 0 AND id_board <> 0', + [], + ); + + list($maxBoards) = Db::$db->fetch_row($request); + Maintenance::$total_items = (int) $maxBoards; + + Db::$db->free_result($request); + + do { + $start = Maintenance::getCurrentStart(); + $this->handleTimeout($start); + $inserts = []; + + // This setting is stored over in the themes table in 2.0... + $request = $this->query( + '', + 'SELECT id_member, ({literal:board_notify_} || id_topic) as alert_pref, 1 as alert_value + FROM {db_prefix}log_notify + WHERE id_member <> 0 AND id_board <> 0 + LIMIT {int:start}, {int:limit}', + [ + 'start' => $start, + 'limit' => $this->limit, + ], + ); + + if (Db::$db->num_rows($request) == 0) { + break; + } + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [$row['id_member'], 'msg_auto_notify', !empty($row['value']) ? 1 : 0]; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}user_alerts_prefs', + [ + 'id_member' => 'int', + 'alert_pref' => 'string', + 'alert_value' => 'string', + ], + $inserts, + ['id_member', 'alert_pref'], + ); + + Maintenance::setCurrentStart($start + $this->limit); + } while (Maintenance::getCurrentStart() < Maintenance::$total_items); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/AlertsWatchedTopics.php b/Sources/Maintenance/Migration/v2_1/AlertsWatchedTopics.php new file mode 100644 index 00000000000..30407c3c2dd --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AlertsWatchedTopics.php @@ -0,0 +1,115 @@ +query( + '', + 'SELECT COUNT(*) + FROM {db_prefix}log_notify + WHERE id_member <> 0 AND id_topic <> 0', + [], + ); + + list($maxTopics) = Db::$db->fetch_row($request); + Maintenance::$total_items = (int) $maxTopics; + + Db::$db->free_result($request); + + do { + $start = Maintenance::getCurrentStart(); + $this->handleTimeout($start); + $inserts = []; + + // This setting is stored over in the themes table in 2.0... + $request = $this->query( + '', + 'SELECT id_member, ({literal:topic_notify_} || id_topic) as alert_pref, 1 as alert_value + FROM {db_prefix}log_notify + WHERE id_member <> 0 AND id_topic <> 0 + LIMIT {int:start}, {int:limit}', + [ + 'start' => $start, + 'limit' => $this->limit, + ], + ); + + if (Db::$db->num_rows($request) == 0) { + break; + } + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [$row['id_member'], 'msg_auto_notify', !empty($row['value']) ? 1 : 0]; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}user_alerts_prefs', + [ + 'id_member' => 'int', + 'alert_pref' => 'string', + 'alert_value' => 'string', + ], + $inserts, + ['id_member', 'alert_pref'], + ); + + Maintenance::setCurrentStart($start + $this->limit); + } while (Maintenance::getCurrentStart() < Maintenance::$total_items); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/AttachmentDirectory.php b/Sources/Maintenance/Migration/v2_1/AttachmentDirectory.php new file mode 100644 index 00000000000..74980ee7775 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AttachmentDirectory.php @@ -0,0 +1,68 @@ + Config::$modSettings['attachmentUploadDir']]); + + Config::updateModSettings([ + 'attachmentUploadDir' => Config::$modSettings['attachmentUploadDir'], + 'currentAttachmentUploadDir' => 1, + ]); + } elseif (is_array(Config::$modSettings['attachmentUploadDir'])) { + Config::updateModSettings([ + 'attachmentUploadDir' => serialize(Config::$modSettings['attachmentUploadDir']), + ]); + // Assume currentAttachmentUploadDir is already set + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/AttachmentSizes.php b/Sources/Maintenance/Migration/v2_1/AttachmentSizes.php new file mode 100644 index 00000000000..c5a30aca25c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AttachmentSizes.php @@ -0,0 +1,87 @@ + 0 OR height > 0) + AND POSITION({literal:image} IN mime_type) IS NULL + */ + + $attachs = []; + + // If id_member = 0, then it's not an avatar + // If attachment_type = 0, then it's also not a thumbnail + // Theory says there shouldn't be *that* many of these + $request = $this->query( + '', + 'SELECT id_attach, mime_type, width, height + FROM {db_prefix}attachments + WHERE id_member = 0 + AND attachment_type = 0', + [], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + if (($row['width'] > 0 || $row['height'] > 0) && strpos($row['mime_type'], 'image') !== 0) { + $attachs[] = $row['id_attach']; + } + } + Db::$db->free_result($request); + + if (!empty($attachs)) { + $this->query( + '', + 'UPDATE {db_prefix}attachments + SET width = 0, + height = 0 + WHERE id_attach IN ({array_int:attachs})', + [ + 'attachs' => $attachs, + ], + ); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/AutoNotify.php b/Sources/Maintenance/Migration/v2_1/AutoNotify.php new file mode 100644 index 00000000000..bbb03dac706 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/AutoNotify.php @@ -0,0 +1,126 @@ +query( + '', + 'SELECT COUNT(*) + FROM {db_prefix}themes + WHERE variable = {string:auto_notify}', + [ + 'auto_notify' => 'auto_notify', + ], + ); + + list($maxMembers) = Db::$db->fetch_row($request); + Maintenance::$total_items = (int) $maxMembers; + + Db::$db->free_result($request); + + do { + $start = Maintenance::getCurrentStart(); + $this->handleTimeout($start); + $inserts = []; + + // This setting is stored over in the themes table in 2.0... + $request = $this->query( + '', + 'SELECT id_member, value + FROM {db_prefix}themes + WHERE variable = {string:auto_notify} + ORDER BY id_member + LIMIT {int:start}, {int:limit}', + [ + 'auto_notify' => 'auto_notify', + 'start' => $start, + 'limit' => $this->limit, + ], + ); + + if (Db::$db->num_rows($request) == 0) { + break; + } + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [$row['id_member'], 'msg_auto_notify', !empty($row['value']) ? 1 : 0]; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}user_alerts_prefs', + [ + 'id_member' => 'int', + 'alert_pref' => 'string', + 'alert_value' => 'string', + ], + $inserts, + ['id_member', 'alert_pref'], + ); + + Maintenance::setCurrentStart($start + $this->limit); + } while (Maintenance::getCurrentStart() < Maintenance::$total_items); + + $this->query( + '', + 'DELETE FROM {db_prefix}themes + WHERE variable = {literal:auto_notify}', + [], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/BoardDescriptions.php b/Sources/Maintenance/Migration/v2_1/BoardDescriptions.php new file mode 100644 index 00000000000..7f857debd88 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/BoardDescriptions.php @@ -0,0 +1,85 @@ +query( + '', + 'SELECT name, description, id_board + FROM {db_prefix}boards + WHERE id_board > {int:start}', + [ + 'start' => Maintenance::getCurrentStart(), + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $this->query( + '', + 'UPDATE {db_prefix}boards + SET name = {string:name}, description = {string:description} + WHERE id = {int:id}', + [ + 'id' => $row['id_board'], + 'name' => Utils::htmlspecialchars(strip_tags(Parser::transform($row['name'], Parser::OUTPUT_BBC))), + 'description' => Utils::htmlspecialchars(strip_tags(Parser::transform($row['description'], Parser::OUTPUT_BBC))), + ], + ); + + Maintenance::setCurrentStart(); + $this->handleTimeout(); + } + + Db::$db->free_result($request); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php b/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php new file mode 100644 index 00000000000..f24fcc51613 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/BoardPermissionsView.php @@ -0,0 +1,247 @@ +list_tables(); + + if (!in_array(Config::$db_prefix . $table->name, $existing_tables)) { + $table->create(); + } + + $this->handleTimeout(++$start); + } + + // if one of source col is missing skip this step. + $table_columns = Db::$db->list_columns('{db_prefix}membergroups'); + $table_columns2 = Db::$db->list_columns('{db_prefix}boards'); + + if (!in_array('id_group', $table_columns) || !in_array('member_groups', $table_columns2) || !in_array('deny_member_groups', $table_columns2)) { + return true; + } + + if ($start <= 1) { + $this->query('', 'TRUNCATE {db_prefix}board_permissions_view'); + + $this->handleTimeout(++$start); + } + + // Update board_permissions_view table with membergroups + if ($start <= 2) { + $inserts = []; + + $request = $this->query('', ' + SELECT id_board, mg.id_group, 0 + FROM {db_prefix}boards b + JOIN {db_prefix}membergroups mg ON (FIND_IN_SET(mg.id_group, b.member_groups) != 0)'); + + while ($row = Db::$db->fetch_row($request)) { + $inserts[] = $row; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}board_permissions_view', + [ + 'id_board' => 'int', + 'id_group' => 'int', + 'deny' => 'int', + ], + $inserts, + ['id_board', 'id_group'], + ); + + $this->handleTimeout(++$start); + } + + // Update board_permissions_view table with -1 + if ($start <= 3) { + $inserts = []; + + $request = $this->query('', ' + SELECT id_board, -1, 0 + FROM {db_prefix}boards b + WHERE (FIND_IN_SET(-1, b.member_groups) != 0)'); + + while ($row = Db::$db->fetch_row($request)) { + $inserts[] = $row; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}board_permissions_view', + [ + 'id_board' => 'int', + 'id_group' => 'int', + 'deny' => 'int', + ], + $inserts, + ['id_board', 'id_group'], + ); + + $this->handleTimeout(++$start); + } + + // Update board_permissions_view table with 0 + if ($start <= 4) { + $inserts = []; + + $request = $this->query('', ' + SELECT id_board, 0, 0 + FROM {db_prefix}boards b + WHERE (FIND_IN_SET(0, b.member_groups) != 0)'); + + while ($row = Db::$db->fetch_row($request)) { + $inserts[] = $row; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}board_permissions_view', + [ + 'id_board' => 'int', + 'id_group' => 'int', + 'deny' => 'int', + ], + $inserts, + ['id_board', 'id_group'], + ); + + $this->handleTimeout(++$start); + } + + // Update deny board_permissions_view table with membergroups + if ($start <= 5) { + $inserts = []; + + $request = $this->query('', ' + SELECT id_board, mg.id_group, 1 + FROM {db_prefix}boards b + JOIN {db_prefix}membergroups mg ON (FIND_IN_SET(mg.id_group, b.deny_member_groups) != 0)'); + + while ($row = Db::$db->fetch_row($request)) { + $inserts[] = $row; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}board_permissions_view', + [ + 'id_board' => 'int', + 'id_group' => 'int', + 'deny' => 'int', + ], + $inserts, + ['id_board', 'id_group'], + ); + + $this->handleTimeout(++$start); + } + + // Update deny board_permissions_view table with -1 + if ($start <= 5) { + $inserts = []; + + $request = $this->query('', ' + SELECT id_board, -1, 1 + FROM {db_prefix}boards b + WHERE (FIND_IN_SET(-1, b.deny_member_groups) != 0)'); + + while ($row = Db::$db->fetch_row($request)) { + $inserts[] = $row; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}board_permissions_view', + [ + 'id_board' => 'int', + 'id_group' => 'int', + 'deny' => 'int', + ], + $inserts, + ['id_board', 'id_group'], + ); + + $this->handleTimeout(++$start); + } + + // Update deny board_permissions_view table with 0 + if ($start <= 6) { + $inserts = []; + + $request = $this->query('', ' + SELECT id_board, 0, 1 + FROM {db_prefix}boards b + WHERE (FIND_IN_SET(0, b.deny_member_groups) != 0)'); + + while ($row = Db::$db->fetch_row($request)) { + $inserts[] = $row; + } + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}board_permissions_view', + [ + 'id_board' => 'int', + 'id_group' => 'int', + 'deny' => 'int', + ], + $inserts, + ['id_board', 'id_group'], + ); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CalendarEvents.php b/Sources/Maintenance/Migration/v2_1/CalendarEvents.php new file mode 100644 index 00000000000..0108a1d2fe6 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CalendarEvents.php @@ -0,0 +1,87 @@ +getCurrentStructure(); + + foreach ($this->newColumns as $column) { + if (!isset($existing_structure['columns'][$column])) { + return true; + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Calendar(); + $existing_structure = $table->getCurrentStructure(); + + foreach ($this->newColumns as $column) { + if (isset($existing_structure['columns'][$column])) { + continue; + } + + foreach ($table->columns as $col) { + if ($col->name === $column) { + $table->addColumn($col); + + $this->handleTimeout(); + } + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CalendarUpdates.php b/Sources/Maintenance/Migration/v2_1/CalendarUpdates.php new file mode 100644 index 00000000000..43b1568b128 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CalendarUpdates.php @@ -0,0 +1,273 @@ +query( + '', + 'DELETE FROM {db_prefix}calendar_holidays + WHERE title in ({array_string:titles})', + [ + 'titles' => array_unique(array_column($this->holidays, 0)), + ], + ); + + Db::$db->insert( + 'ignore', + '{db_prefix}calendar_holidays', + [ + 'title' => 'string-60', + 'event_date' => 'date', + ], + $this->holidays, + ['id_holiday'], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CategoryDescrptions.php b/Sources/Maintenance/Migration/v2_1/CategoryDescrptions.php new file mode 100644 index 00000000000..ce97512d89b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CategoryDescrptions.php @@ -0,0 +1,54 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + // Column exists, don't need to do this. + if ($column->name !== 'description' || isset($existing_structure['columns'][$column->name])) { + continue; + } + + $table->addColumn($column); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CollapsedCategories.php b/Sources/Maintenance/Migration/v2_1/CollapsedCategories.php new file mode 100644 index 00000000000..da69df1bcd5 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CollapsedCategories.php @@ -0,0 +1,90 @@ +list_tables(); + + return in_array(Config::$db_prefix . 'collapsed_categories', $tables); + } + + /** + * + */ + public function execute(): bool + { + $request = $this->query( + '', + 'SELECT id_member, id_cat + FROM {db_prefix}collapsed_categories', + [], + ); + + $inserts = []; + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [$row['id_member'], 1, 'collapse_category_' . $row['id_cat'], $row['id_cat']]; + } + + Db::$db->free_result($request); + + $result = false; + + if (!empty($inserts)) { + $result = Db::$db->insert( + 'replace', + '{db_prefix}themes', + [ + 'id_member' => 'int', + 'id_theme' => 'int', + 'variable' => 'string', + 'value' => 'string', + ], + $inserts, + ['id_theme', 'id_member', 'variable'], + ); + } + + if ($result !== false) { + Db::$db->drop_table('{db_prefix}collapsed_categories'); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CreateAlerts.php b/Sources/Maintenance/Migration/v2_1/CreateAlerts.php new file mode 100644 index 00000000000..d43170a2205 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CreateAlerts.php @@ -0,0 +1,221 @@ +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(); + } + + $existing_structure = $members_table->getCurrentStructure(); + + foreach ($members_table->columns as $column) { + // Column exists, don't need to do this. + if ($column->name !== 'alerts' || isset($existing_structure['columns'][$column->name])) { + continue; + } + + $members_table->addColumn($column); + } + + // We don't need to increment the start, the column will exist and it should get past this. + $this->handleTimeout(0); + + // Add our default permissions. + Db::$db->insert( + 'ignore', + '{db_prefix}' . $user_alert_prefs_table->name, + [ + 'id_member' => 'int', + 'alert_pref' => 'string', + 'alert_value' => 'int', + ], + $this->default_alert_perms, + ['id_theme', 'alert_pref'], + ); + + $request = $this->query( + '', + 'SELECT COUNT(*) + FROM {db_prefix}members', + [], + ); + + list($maxMembers) = Db::$db->fetch_row($request); + Maintenance::$total_items = (int) $maxMembers; + + Db::$db->free_result($request); + + // First see if we still have a notify_regularity column + $member_columns = Db::$db->list_columns('{db_prefix}members'); + + if (in_array('notify_regularity', $member_columns)) { + do { + $start = Maintenance::getCurrentStart(); + + $this->handleTimeout($start); + $inserts = []; + + // Skip errors here so we don't croak if the columns don't exist... + $request = $this->query( + '', + 'SELECT id_member, notify_regularity, notify_send_body, notify_types, notify_announcements + FROM {db_prefix}members + ORDER BY id_member + LIMIT {int:start}, {int:limit}', + [ + 'db_error_skip' => true, + 'start' => $start, + 'limit' => $this->limit, + ], + ); + + if (Db::$db->num_rows($request) == 0) { + break; + } + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [$row['id_member'], 'msg_receive_body', !empty($row['notify_send_body']) ? 1 : 0]; + $inserts[] = [$row['id_member'], 'msg_notify_pref', intval($row['notify_regularity']) + 1]; + $inserts[] = [$row['id_member'], 'msg_notify_type', $row['notify_types']]; + $inserts[] = [$row['id_member'], 'announcements', !empty($row['notify_announcements']) ? 1 : 0]; + } + + Db::$db->free_result($request); + + Db::$db->insert( + 'ignore', + '{db_prefix}user_alerts_prefs', + [ + 'id_member' => 'int', + 'alert_pref' => 'string', + 'alert_value' => 'string', + ], + $inserts, + ['id_member', 'alert_pref'], + ); + + Maintenance::setCurrentStart($start + $this->limit); + } 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(); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php b/Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php new file mode 100644 index 00000000000..5aa919be2a8 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CreateBackgroundTasks.php @@ -0,0 +1,53 @@ +list_tables(); + + if (!in_array(Config::$db_prefix . $background_tasks_table->name, $tables)) { + $background_tasks_table->create(); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CreateLogGroupRequests.php b/Sources/Maintenance/Migration/v2_1/CreateLogGroupRequests.php new file mode 100644 index 00000000000..9b6da89bd04 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CreateLogGroupRequests.php @@ -0,0 +1,76 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + // Column exists, don't need to do this. + if (!in_array($column->name, $this->newColumns) || isset($existing_structure['columns'][$column->name])) { + continue; + } + + $table->addColumn($column); + } + + Db::$db->remove_index('{db_prefix}log_group_requests', 'id_member'); + + foreach ($table->indexes as $idx) { + // Column exists, don't need to do this. + if ($idx->name !== 'idx_id_member' || isset($existing_structure['indexes'][$idx->name])) { + continue; + } + + $table->addIndex($idx); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php b/Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php new file mode 100644 index 00000000000..6f949d88dee --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CreateMemberLogins.php @@ -0,0 +1,52 @@ +list_tables(); + + if (!in_array(Config::$db_prefix . 'member_logins', $tables)) { + $member_logins = new MemberLogins(); + $member_logins->create(); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CustomFieldsPart1.php b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart1.php new file mode 100644 index 00000000000..f95b2e4dc9e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart1.php @@ -0,0 +1,219 @@ +ICQ - {INPUT}', + 1, + ], + [ + 'cust_skype', + '{skype}', + '{skype_desc}', + 'text', + 32, + '', + 2, + 'nohtml', + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 0, + '', + '{INPUT} ', + 1, + ], + [ + 'cust_loca', + '{location}', + '{location_desc}', + 'text', + 50, + '', + 4, + 'nohtml', + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 0, + '', + '', + 0, + ], + [ + 'cust_gender', + '{gender}', + '{gender_desc}', + 'radio', + 255, + '{gender_0},{gender_1},{gender_2}', + 5, + 'nohtml', + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 0, + '{gender_0}', + '', + 1, + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + if ($start <= 0) { + $table = new \SMF\Db\Schema\v2_1\CustomFields(); + $existing_structure = $table->getCurrentStructure(); + + foreach ($table->columns as $column) { + // Add the columns. + if ( + ( + $column->name === 'field_order' + || $column->name === 'show_mlist' + ) + && !isset($existing_structure['columns'][$column->name]) + ) { + $table->addColumn($column); + continue; + } + } + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + Db::$db->insert( + 'ignore', + '{db_prefix}' . $table->name, + [ + 'col_name' => 'string', + 'field_name' => 'string', + 'field_desc' => 'string', + 'field_type' => 'string', + 'field_length' => 'int', + 'field_options' => 'string', + 'field_order' => 'int', + 'mask' => 'string', + 'show_reg' => 'int', + 'show_display' => 'int', + 'show_mlist' => 'int', + 'show_profile' => 'int', + 'private' => 'int', + 'active' => 'int', + 'bbc' => 'int', + 'can_search' => 'int', + 'default_value' => 'string', + 'enclose' => 'string', + 'placement' => 'int', + ], + $this->default_fields, + ['id_theme', 'alert_pref'], + ); + + $this->handleTimeout(++$start); + } + + if ($start <= 2) { + // Add an order value to each existing cust profile field. + $ocf = $this->query('', ' + SELECT id_field + FROM {db_prefix}custom_fields + WHERE field_order = 0'); + + // We start counting from 5 because we already have the first 5 fields. + $fields_count = 5; + + while ($row = Db::$db->fetch_assoc($ocf)) { + ++$fields_count; + + $this->query( + '', + 'UPDATE {db_prefix}custom_fields + SET field_order = {int:field_count} + WHERE id_field = {int:id_field}', + [ + 'field_count' => $fields_count, + 'id_field' => $row['id_field'], + ], + ); + } + Db::$db->free_result($ocf); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CustomFieldsPart2.php b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart2.php new file mode 100644 index 00000000000..ab60e7368b5 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart2.php @@ -0,0 +1,140 @@ +list_columns('{db_prefix}members'); + + return array_intersect($this->possible_columns, $results) !== []; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $request = $this->query( + '', + 'SELECT COUNT(*) + FROM {db_prefix}members', + [], + ); + + list($maxMembers) = Db::$db->fetch_row($request); + + Db::$db->free_result($request); + + Maintenance::$total_items = (int) $maxMembers; + + $results = Db::$db->list_columns('{db_prefix}members'); + $select_columns = array_intersect($this->possible_columns, $results); + + $is_done = false; + + while (!$is_done) { + $this->handleTimeout($start); + $inserts = []; + + $request = $this->query( + '', + 'SELECT id_member, ' . implode(',', $select_columns) . ' + FROM {db_prefix}members + ORDER BY id_member + LIMIT {int:start}, {int:limit}', + [ + 'start' => $start, + 'limit' => $this->limit, + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + if (!empty($row['icq'])) { + $inserts[] = [$row['id_member'], 1, 'cust_icq', $row['icq']]; + } + + if (!empty($row['msn'])) { + $inserts[] = [$row['id_member'], 1, 'cust_skype', $row['msn']]; + } + + if (!empty($row['location'])) { + $inserts[] = [$row['id_member'], 1, 'cust_loca', $row['location']]; + } + + if (!empty($row['gender'])) { + $inserts[] = [$row['id_member'], 1, 'cust_gender', '{gender_' . intval($row['gender']) . '}']; + } + } + Db::$db->free_result($request); + + if (!empty($inserts)) { + Db::$db->insert( + 'replace', + '{db_prefix}themes', + [ + 'id_member' => 'int', + 'id_theme' => 'int', + 'variable' => 'string', + 'value' => 'string', + ], + $inserts, + ['id_theme', 'id_member', 'variable'], + ); + } + + $start += $this->limit; + + if ($start >= $maxMembers) { + $is_done = true; + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/CustomFieldsPart3.php b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart3.php new file mode 100644 index 00000000000..206a59210bd --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/CustomFieldsPart3.php @@ -0,0 +1,103 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if (in_array($column['name'], $this->possible_columns)) { + $col = new Column( + name: $column['name'], + type: 'varchar', + ); + + $table->dropColumn($col); + } + } + + $this->handleTimeout(++$start); + } + + if ($start <= 1 && empty(Config::$modSettings['displayFields'])) { + $request = $this->query( + '', + 'SELECT col_name, field_name, field_type, field_order, bbc, enclose, placement, show_mlist + FROM {db_prefix}custom_fields', + [], + ); + + $fields = []; + + while ($row = Db::$db->fetch_assoc($request)) { + $fields[] = [ + 'col_name' => strtr($row['col_name'], ['|' => '', ';' => '']), + 'title' => strtr($row['field_name'], ['|' => '', ';' => '']), + 'type' => $row['field_type'], + 'order' => $row['field_order'], + 'bbc' => $row['bbc'] ? '1' : '0', + 'placement' => !empty($row['placement']) ? $row['placement'] : '0', + 'enclose' => !empty($row['enclose']) ? $row['enclose'] : '', + 'mlist' => $row['show_mlist'], + ]; + } + Db::$db->free_result($request); + + Config::updateModSettings([ + 'displayFields' => json_encode($fields), + ]); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/FixDates.php b/Sources/Maintenance/Migration/v2_1/FixDates.php new file mode 100644 index 00000000000..be65df76a41 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/FixDates.php @@ -0,0 +1,199 @@ +), which would be similar the more standard DATEFROMPARTS. + + // PostgreSQL does the query a bit different. + $is_pgsql = Config::$db_type == 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', + [], + ); + } + 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', + [], + ); + } + 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', + [], + ); + } + 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', + [], + ); + } + 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', + [], + ); + } + 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', + [], + ); + } + Maintenance::setCurrentStart(); + $this->handleTimeout(); + + if (Maintenance::getCurrentStart() < 7) { + Db::$db->change_column( + '{db_prefix}log_activity', + 'DATE', + [ + 'not_null' => true, + 'default' => null, + ], + ); + } + 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'], + ]; + + for ($key = Maintenance::getCurrentStart(); $key < count($fixes); Maintenance::setCurrentStart()) { + $fix = $fixes[$key - 7]; + + Db::$db->change_column($fix['tbl'], $fix['col'], ['default' => '1004-01-01']); + $this->handleTimeout(); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxAdminInfo.php b/Sources/Maintenance/Migration/v2_1/IdxAdminInfo.php new file mode 100644 index 00000000000..8dc37eadd7c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxAdminInfo.php @@ -0,0 +1,75 @@ +getCurrentStructure(); + + // Change index for table scheduled_tasks + if ($start <= 0) { + if (isset($existing_structure['indexes']['idx_filename'])) { + $table->dropIndex($table->indexes['idx_filename']); + } + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + if (!isset($existing_structure['indexes']['idx_filename'])) { + $idx = $table->indexes['idx_filename']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxAttachments.php b/Sources/Maintenance/Migration/v2_1/IdxAttachments.php new file mode 100644 index 00000000000..8df8676cc3f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxAttachments.php @@ -0,0 +1,56 @@ +getCurrentStructure(); + + if ($start <= 0) { + if (!isset($existing_structure['indexes']['idx_id_thumb'])) { + $table->addIndex($table->indexes['idx_id_thumb']); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxBoards.php b/Sources/Maintenance/Migration/v2_1/IdxBoards.php new file mode 100644 index 00000000000..a982ca5130a --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxBoards.php @@ -0,0 +1,75 @@ +getCurrentStructure(); + + // Change index for table scheduled_tasks + if ($start <= 0) { + if (isset($existing_structure['indexes']['idx_member_groups'])) { + $table->dropIndex($table->indexes['idx_member_groups']); + } + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + if (!isset($existing_structure['indexes']['idx_member_groups'])) { + $idx = $table->indexes['idx_member_groups']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxLogActions.php b/Sources/Maintenance/Migration/v2_1/IdxLogActions.php new file mode 100644 index 00000000000..2692dab0454 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogActions.php @@ -0,0 +1,60 @@ +getCurrentStructure(); + + // Updating log_actions + if ($start <= 0) { + if (!isset($existing_structure['indexes']['id_topic_id_log'])) { + // Make it match 2.1, even if wrong. + $idx = $table->indexes['idx_id_topic_id_log']; + $idx->name = 'id_topic_id_log'; + $table->addIndex($idx); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxLogActivity.php b/Sources/Maintenance/Migration/v2_1/IdxLogActivity.php new file mode 100644 index 00000000000..477ff3a4c5f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogActivity.php @@ -0,0 +1,74 @@ +dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating topics drop old id_board ix + if ($start <= 0) { + $oldIdx = new DbIndex( + ['most_on'], + 'index', + 'most_on', + ); + + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxLogComments.php b/Sources/Maintenance/Migration/v2_1/IdxLogComments.php new file mode 100644 index 00000000000..23d0bfcedf9 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogComments.php @@ -0,0 +1,75 @@ +getCurrentStructure(); + + // Change index for table scheduled_tasks + if ($start <= 0) { + if (isset($existing_structure['indexes']['idx_comment_type'])) { + $table->dropIndex($table->indexes['idx_comment_type']); + } + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + if (!isset($existing_structure['indexes']['idx_comment_type'])) { + $idx = $table->indexes['idx_comment_type']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxLogPackages.php b/Sources/Maintenance/Migration/v2_1/IdxLogPackages.php new file mode 100644 index 00000000000..8932a6ca05b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogPackages.php @@ -0,0 +1,76 @@ +getCurrentStructure(); + + // Change index for table log_packages + if ($start <= 0) { + if (isset($existing_structure['indexes']['log_packages_filename'])) { + $table->dropIndex($table->indexes['log_packages_filename']); + } + + $this->handleTimeout(++$start); + } + + // Change index for table log_packages + if ($start <= 1) { + if (!isset($existing_structure['indexes']['log_packages_filename'])) { + $idx = $table->indexes['log_packages_filename']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxLogSubscribed.php b/Sources/Maintenance/Migration/v2_1/IdxLogSubscribed.php new file mode 100644 index 00000000000..b79067f2346 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxLogSubscribed.php @@ -0,0 +1,57 @@ +getCurrentStructure(); + + // Updating log_actions + if ($start <= 0) { + if (!isset($existing_structure['indexes']['status'])) { + $table->addIndex($table->indexes['status']); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxMembers.php b/Sources/Maintenance/Migration/v2_1/IdxMembers.php new file mode 100644 index 00000000000..1d2229925c2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxMembers.php @@ -0,0 +1,202 @@ +getCurrentStructure(); + + if ($start <= 0) { + $oldIdx = new DbIndex( + ['members_member_name_low'], + 'index', + 'members_member_name_low', + ); + + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $oldIdx = new DbIndex( + ['members_real_name_low'], + 'index', + 'members_real_name_low', + ); + + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + if ($start <= 2) { + $oldIdx = new DbIndex( + ['members_active_real_name'], + 'index', + 'members_active_real_name', + ); + + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + if ($start <= 3) { + if (Db::$db->title === POSTGRE_TITLE) { + $this->query( + '', + 'CREATE INDEX {db_prefix}members_member_name_low ON {db_prefix}members (LOWER(member_name) varchar_pattern_ops)', + [ + 'security_override' => true, + ], + ); + } + + $this->handleTimeout(++$start); + } + + if ($start <= 4) { + if (Db::$db->title === POSTGRE_TITLE) { + $this->query( + '', + 'CREATE INDEX {db_prefix}members_real_name_low ON {db_prefix}members (LOWER(real_name) varchar_pattern_ops)', + [ + 'security_override' => true, + ], + ); + } + + $this->handleTimeout(++$start); + } + + // Updating members active_real_name (drop) + if ($start <= 5) { + if (isset($existing_structure['indexes']['idx_active_real_name'])) { + $table->dropIndex($table->indexes['idx_active_real_name']); + } + + $this->handleTimeout(++$start); + } + + // Updating members active_real_name (add) + if ($start <= 6) { + $table->addIndex($table->indexes['idx_active_real_name']); + + $this->handleTimeout(++$start); + } + + // Updating members email_address + if ($start <= 7) { + if (Config::$db_type === POSTGRE_TITLE) { + $idx = $table->indexes['idx_email_address']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + // Updating members drop memberName + if ($start <= 8) { + $oldIdx = new DbIndex(['member_name'], 'index', 'memberName'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Change index for table members + if ($start <= 9) { + if (Config::$db_type === POSTGRE_TITLE) { + $idx = $table->indexes['idx_lngfile']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + // Change index for table members + if ($start <= 10) { + if (Config::$db_type === POSTGRE_TITLE) { + $idx = $table->indexes['idx_member_name']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + // Change index for table members + if ($start <= 11) { + if (Config::$db_type === POSTGRE_TITLE) { + $idx = $table->indexes['idx_real_name']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + } + + $this->handleTimeout(++$start); + } + + // Create help function for index + if ($start <= 12 && Config::$db_type === POSTGRE_TITLE) { + $this->query('', ' + CREATE OR REPLACE FUNCTION indexable_month_day(date) RETURNS TEXT as \' + SELECT to_char($1, \'\'MM-DD\'\');\' + LANGUAGE \'sql\' IMMUTABLE STRICT + '); + + $this->handleTimeout(++$start); + } + + // Change index for table members + if ($start <= 13 && Config::$db_type === POSTGRE_TITLE) { + $idx = new DbIndex( + ['indexable_month_day(birthdate)'], + 'index', + 'members_birthdate2', + ); + $table->addIndex($idx); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxMessages.php b/Sources/Maintenance/Migration/v2_1/IdxMessages.php new file mode 100644 index 00000000000..a31adf249c9 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxMessages.php @@ -0,0 +1,164 @@ +getCurrentStructure(); + + if ($start <= 0) { + $oldIdx = new DbIndex( + ['id_topic'], + 'index', + 'idx_id_topic', + ); + + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $oldIdx = new DbIndex( + ['id_topic'], + 'index', + 'idx_topic', + ); + + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + if ($start <= 2) { + $table->dropIndex($table->indexes['idx_likes']); + + $this->handleTimeout(++$start); + } + + if ($start <= 3) { + $table->addIndex($table->indexes['idx_likes']); + + $this->handleTimeout(++$start); + } + + // Updating messages drop old ipIndex + if ($start <= 4) { + $oldIdx = new DbIndex(['member_ip'], 'index', 'ipIndex'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop old ip_index + if ($start <= 5) { + $oldIdx = new DbIndex(['member_ip'], 'index', 'ip_index'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop old related_ip + if ($start <= 6) { + $oldIdx = new DbIndex(['member_ip'], 'index', 'related_ip'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop old topic ix + if ($start <= 7) { + $oldIdx = new DbIndex(['id_topic'], 'index', 'topic'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop another old topic ix + if ($start <= 8) { + $oldIdx = new DbIndex(['id_topic'], 'index', 'id_topic'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop another old topic ix + if ($start <= 9) { + $oldIdx = new DbIndex(['approved'], 'index', 'approved'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop another old topic ix + if ($start <= 10) { + $oldIdx = new DbIndex(['approved'], 'index', 'idx_approved'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop id_board ix + if ($start <= 11) { + $oldIdx = new DbIndex(['id_board'], 'index', 'id_board'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages drop id_board ix alt name + if ($start <= 12) { + $oldIdx = new DbIndex(['id_board'], 'index', 'idx_id_board'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating messages add new id_board ix + if ($start <= 12) { + $table->addIndex($table->indexes['idx_id_board']); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxScheduledTasks.php b/Sources/Maintenance/Migration/v2_1/IdxScheduledTasks.php new file mode 100644 index 00000000000..436d64cd13c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxScheduledTasks.php @@ -0,0 +1,73 @@ +getCurrentStructure(); + + // Change index for table scheduled_tasks + if ($start <= 0) { + if (isset($existing_structure['indexes']['idx_task'])) { + $table->dropIndex($table->indexes['idx_task']); + } + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + $idx = $table->indexes['idx_task']; + $table->addIndex($idx, Config::$db_type === POSTGRE_TITLE ? 'replace' : 'ignore', ['varchar_pattern_ops' => $idx->columns[0]]); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/IdxTopics.php b/Sources/Maintenance/Migration/v2_1/IdxTopics.php new file mode 100644 index 00000000000..39edd44ca55 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/IdxTopics.php @@ -0,0 +1,68 @@ +dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + // Updating topics drop old id_board ix + if ($start <= 0) { + $oldIdx = new DbIndex(['id_board'], 'index', 'id_board'); + $table->dropIndex($oldIdx); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php b/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php new file mode 100644 index 00000000000..7623e34f647 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6BanItem.php @@ -0,0 +1,163 @@ +getCurrentStructure(); + + return !isset($existing_structure['columns']['ip_low']) || !isset($existing_structure['columns']['ip_high']); + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new \SMF\Db\Schema\v2_1\BanItems(); + $existing_structure = $table->getCurrentStructure(); + + // Add columns to ban_items + if ($start <= 0) { + foreach ($table->columns as $column) { + if ( + ( + $column->name === 'ip_low' + || $column->name === 'ip_high' + ) + && !isset($existing_structure['columns'][$column->name]) + ) { + $table->addColumn($column); + continue; + } + } + + $this->handleTimeout(++$start); + } + + // Convert data for ban_items + if ($start <= 1) { + // This query is performed differently for PostgreSQL + if (Config::$db_type == POSTGRE_TITLE) { + $this->query('', ' + UPDATE {db_prefix}ban_items + SET ip_low = (ip_low1||{literal:.}||ip_low2||{literal:.}||ip_low3||{literal:.}||ip_low4)::inet, + ip_high = (ip_high1||{literal:.}||ip_high2||{literal:.}||ip_high3||{literal:.}||ip_high4)::inet + WHERE ip_low1 > 0; + '); + } else { + $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; + '); + +die; + + $this->query('', ' + 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->handleTimeout(++$start); + } + + // Create new index on ban_items. + if ($start <= 2) { + foreach ($table->indexes as $idx) { + if ( + $idx->name === 'idx_id_ban_ip' + && !isset($existing_structure['indexes'][$column->name]) + ) { + $table->addIndex($idx); + continue; + } + } + + $this->handleTimeout(++$start); + } + + // Dropping columns from ban_items + if ($start <= 3) { + foreach ($table->columns as $column) { + if ( + ( + $column->name === 'ip_low1' || $column->name === 'ip_low2' || $column->name === 'ip_low3' || $column->name === 'ip_low4' + || $column->name === 'ip_high1' || $column->name === 'ip_high2' || $column->name === 'ip_high3' || $column->name === 'ip_high4' + ) + && !isset($existing_structure['columns'][$column->name]) + ) { + $table->dropColumn($column); + continue; + } + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6Base.php b/Sources/Maintenance/Migration/v2_1/Ipv6Base.php new file mode 100644 index 00000000000..5fa274b14a9 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6Base.php @@ -0,0 +1,334 @@ +query( + '', + ' + SELECT COUNT(DISTINCT {raw:col}) + FROM {db_prefix}{raw:table}', + [ + 'col' => $col . '_old', + 'table' => $table, + ], + ); + + // failed? We may have not renamed yet. + if ($request === false) { + $request = $this->query( + '', + ' + SELECT COUNT(DISTINCT {raw:col}) + FROM {db_prefix}{raw:table}', + [ + 'col' => $col, + 'table' => $table, + ], + ); + } + + if ($request === false) { + return 0; + } + + list($items) = Db::$db->fetch_row($request); + + return (int) $items; + } + + public function convertData(string $targetTable, string $oldCol, string $newCol, int $limit = 50000, int $setSize = 100): bool + { + // mysql default max length is 1mb https://dev.mysql.com/doc/refman/5.1/en/packet-too-large.html + $arIp = []; + + $request = $this->query( + '', + ' + SELECT DISTINCT {raw:old_col} + FROM {db_prefix}{raw:table_name} + WHERE {raw:new_col} = {string:empty} + LIMIT {int:limit}', + [ + 'old_col' => $oldCol, + 'new_col' => $newCol, + 'table_name' => $targetTable, + 'empty' => '', + 'limit' => $limit, + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $arIp[] = $row[$oldCol]; + } + + Db::$db->free_result($request); + + if (empty($arIp)) { + return true; + } + + $updates = []; + $new_ips = []; + $cases = []; + $count = count($arIp); + + for ($i = 0; $i < $count; $i++) { + $new_ip = trim($arIp[$i]); + + $new_ip = filter_var($new_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6); + + if ($new_ip === false) { + $new_ip = ''; + } + + $updates['ip' . $i] = $arIp[$i]; + $new_ips['newip' . $i] = $new_ip; + $cases[$arIp[$i]] = 'WHEN ' . $oldCol . ' = {string:ip' . $i . '} THEN {inet:newip' . $i . '}'; + + // 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( + '', + 'UPDATE {db_prefix}' . $targetTable . ' + SET ' . $newCol . ' = CASE ' . + implode(' + ', $cases) . ' + ELSE NULL + END + WHERE ' . $oldCol . ' IN ({array_string:whereSet})', + array_merge($updates, $new_ips), + ); + + $updates = []; + $new_ips = []; + $cases = []; + } + } + + return false; + } + + public function migrateData(Table $table, string $col): bool + { + $start = Maintenance::getCurrentStart(); + + // Get our total items. + Maintenance::$total_items = $this->getTotalItems('members', 'member_ip2'); + + $existing_structure = $table->getCurrentStructure(); + + // PostgreSQL we use a migration function. + if (Config::$db_type === POSTGRE_TITLE) { + $this->query('', ' + ALTER TABLE {db_prefix}{raw:table} + ALTER {raw:col} DROP not null, + ALTER {raw:col} DROP default, + ALTER {raw:col} TYPE inet USING migrate_inet({raw:col}); + ', [ + 'table' => $table->name, + 'col' => $col, + ]); + + return true; + } + + // Add columns to ban_items + if ($start >= 0) { + // Does the old IP exist? + foreach ($table->columns as $column) { + if ($column->name === $col && !isset($existing_structure['columns'][$col . '_old']) + ) { + $this->query('', ' + ALTER TABLE {db_prefix}{raw:table} + CHANGE {raw:col} {raw:col}_old varchar(200) + ', [ + 'table' => $table->name, + 'col' => $col, + ]); + } + } + + $this->handleTimeout(++$start); + } + + if ($start >= 1 || !isset($existing_structure['columns'][$col])) { + if (isset($existing_structure['columns'][$col . '_old']) && !isset($existing_structure['columns'][$col])) { + $table->addColumn($table->columns[$col]); + } + + $this->handleTimeout(++$start); + } + + // Make sure our temp index exists. + if ($start >= 2) { + if (!isset($existing_structure['indexes']['temp_old_' . $col])) { + $this->query('', ' + CREATE INDEX {db_prefix}temp_old_{raw:col} ON {db_prefix}{raw:table} ({raw:col}_old) + ', [ + 'table' => $table->name, + 'col' => $col, + ]); + } + + $this->handleTimeout(++$start); + } + + // Initialize new ip column. + if ($start >= 3) { + $this->query('', ' + UPDATE {db_prefix}{raw:table} + SET {raw:col} = {empty} + ', [ + 'table' => $table->name, + 'col' => $col, + ]); + + $this->handleTimeout(++$start); + } + + if ($start >= 4) { + $is_done = false; + + while (!$is_done) { + $this->handleTimeout(); + $is_done = $this->convertData($table->name, $col . '_old', $col); + } + + $this->handleTimeout(++$start); + } + + // Remove the temporary ip indexes. + if ($start >= 5) { + if (isset($existing_structure['indexes']['temp_old_' . $col])) { + $this->query('', ' + DROP INDEX {db_prefix}temp_old_{raw:col} ON {db_prefix}{raw:table} + ', [ + 'table' => $table->name, + 'col' => $col, + ]); + } + + $this->handleTimeout(++$start); + } + + // Remove the old member columns. + if ($start >= 6) { + if (isset($existing_structure['columns'][$col . '_old'])) { + $this->query('', ' + ALTER TABLE {db_prefix}{raw:table} + DROP COLUMN {raw:col}_old + ', [ + 'table' => $table->name, + 'col' => $col, + ]); + } + + $this->handleTimeout(++$start); + } + + return true; + } + + public function truncateAndConvert(Table $table, string|array $columns, bool $force = false): bool + { + $start = Maintenance::getCurrentStart(); + + // PostgreSQL we use a migration function. + if (Config::$db_type !== POSTGRE_TITLE && !$force) { + return $this->postgreSQLmigrate($table, $columns); + } + + if ($start <= 0) { + $this->query('', 'TRUNCATE TABLE {db_prefix}{raw:table}', ['table' => $table->name]); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + // Modify ip size + foreach ($table->columns as $col) { + if (in_array($col->name, (array) $columns)) { + $table->alterColumn($col); + } + } + + $this->handleTimeout(++$start); + } + + return true; + } + + public function convertWithNoDataPreservation(Table $table, string|array $columns, bool $force = false): bool + { + $start = Maintenance::getCurrentStart(); + + // PostgreSQL we use a migration function. + if (Config::$db_type !== POSTGRE_TITLE && !$force) { + return $this->postgreSQLmigrate($table, $columns); + } + + $existing_structure = $table->getCurrentStructure(); + + foreach ($columns as $column) { + foreach ($table->columns as $col) { + if ($col->name == $column && $existing_structure['columns'][$col->name]['type'] !== (Config::$db_type === POSTGRE_TITLE ? 'inet' : 'varbinary')) { + $table->dropColumn($col); + $table->addColumn($col); + + $this->handleTimeout(++$start); + } + } + } + + return true; + } + + /****************** + * Internal methods + ******************/ + + private function postgreSQLmigrate(Table $table, string|array $columns) + { + foreach ($columns as $column) { + $this->query('', ' + ALTER TABLE {db_prefix}{raw:table} + ALTER {raw:col} DROP not null, + ALTER {raw:col} DROP default, + ALTER {raw:col} TYPE inet USING migrate_inet({raw:col}); + ', [ + 'table' => $table->name, + 'col' => $column, + ]); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6LogAction.php b/Sources/Maintenance/Migration/v2_1/Ipv6LogAction.php new file mode 100644 index 00000000000..5a376620f13 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogAction.php @@ -0,0 +1,89 @@ +name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogActions(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogActions(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + $this->query('', ' + ALTER TABLE {db_prefix}log_actions + ALTER ip DROP not null, + ALTER ip DROP default, + ALTER ip TYPE inet USING migrate_inet(ip); + '); + } else { + foreach ($table->columns as $column) { + if ($column->name === 'ip' && $existing_structure['columns'][$column->name]['type'] !== 'varbinary') { + $table->dropColumn($column); + $table->addColumn($column); + continue; + } + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6LogBanned.php b/Sources/Maintenance/Migration/v2_1/Ipv6LogBanned.php new file mode 100644 index 00000000000..03e6a3ea7bc --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogBanned.php @@ -0,0 +1,89 @@ +name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogBanned(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogBanned(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + $this->query('', ' + ALTER TABLE {db_prefix}log_banned + ALTER ip DROP not null, + ALTER ip DROP default, + ALTER ip TYPE inet USING migrate_inet(ip); + '); + } else { + foreach ($table->columns as $column) { + if ($column->name === 'ip' && $existing_structure['columns'][$column->name]['type'] !== 'varbinary') { + $table->dropColumn($column); + $table->addColumn($column); + continue; + } + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6LogErrors.php b/Sources/Maintenance/Migration/v2_1/Ipv6LogErrors.php new file mode 100644 index 00000000000..ff0fc07434f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogErrors.php @@ -0,0 +1,99 @@ +name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogErrors(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogErrors(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + $this->query('', ' + ALTER TABLE {db_prefix}log_errors + ALTER ip DROP not null, + ALTER ip DROP default, + ALTER ip TYPE inet USING migrate_inet(ip); + '); + } else { + foreach ($table->columns as $column) { + if ($column->name === 'ip' && $existing_structure['columns'][$column->name]['type'] !== 'varbinary') { + $table->dropColumn($column); + $table->addColumn($column); + continue; + } + } + } + + foreach ($table->indexes as $idx) { + if ( + $idx->name === 'idx_ip' + && !isset($existing_structure['indexes'][$column->name]) + ) { + $table->addIndex($idx); + continue; + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6LogFloodControl.php b/Sources/Maintenance/Migration/v2_1/Ipv6LogFloodControl.php new file mode 100644 index 00000000000..d4b21b40d0a --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogFloodControl.php @@ -0,0 +1,83 @@ +getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogFloodcontrol(); + + $start = Maintenance::getCurrentStart(); + + // Prep floodcontrol + if ($start <= 0) { + $this->query('', 'TRUNCATE TABLE {db_prefix}log_floodcontrol'); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + // Add the new floodcontrol ip column + $table->dropIndex($table->indexes['primary']); + + // Modify log_type size + $table->alterColumn($table->columns['ip']); + $table->alterColumn($table->columns['log_type']); + + // Create primary key for floodcontrol + $table->addIndex($table->indexes['primary']); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php b/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php new file mode 100644 index 00000000000..01d593e0f83 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogOnline.php @@ -0,0 +1,63 @@ +getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogOnline(); + $existing_structure = $table->getCurrentStructure(); + + $start = Maintenance::getCurrentStart(); + + return $this->truncateAndConvert($table, 'ip', true); + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6LogReportedComments.php b/Sources/Maintenance/Migration/v2_1/Ipv6LogReportedComments.php new file mode 100644 index 00000000000..c15e93c3921 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6LogReportedComments.php @@ -0,0 +1,70 @@ +name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogFloodcontrol(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogFloodcontrol(); + + return $this->convertWithNoDataPreservation($table, 'ip'); + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6MemberLogins.php b/Sources/Maintenance/Migration/v2_1/Ipv6MemberLogins.php new file mode 100644 index 00000000000..64c8943fd2e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6MemberLogins.php @@ -0,0 +1,70 @@ +name .= ' without converting'; + } + } + + /** + * + */ + public function isCandidate(): bool + { + $table = new \SMF\Db\Schema\v2_1\MemberLogins(); + $existing_structure = $table->getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['ip']['type'] !== 'inet'; + } + + return $existing_structure['columns']['ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\MemberLogins(); + + return $this->convertWithNoDataPreservation($table, 'ip') && $this->convertWithNoDataPreservation($table, 'ip2'); + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP.php b/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP.php new file mode 100644 index 00000000000..b576ea01505 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP.php @@ -0,0 +1,60 @@ +getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['member_ip']['type'] !== 'inet'; + } + + return isset($existing_structure['columns']['member_ip_old']) + || $existing_structure['columns']['member_ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Members(); + + return $this->migrateData($table, 'member_ip'); + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP2.php b/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP2.php new file mode 100644 index 00000000000..b5d1e5e2e57 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6MembersIP2.php @@ -0,0 +1,60 @@ +getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['member_ip2']['type'] !== 'inet'; + } + + return isset($existing_structure['columns']['member_ip2_old']) + || $existing_structure['columns']['member_ip2']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Members(); + + return $this->migrateData($table, 'member_ip2'); + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Ipv6Messages.php b/Sources/Maintenance/Migration/v2_1/Ipv6Messages.php new file mode 100644 index 00000000000..b6ead259a54 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Ipv6Messages.php @@ -0,0 +1,78 @@ +getCurrentStructure(); + + if (Config::$db_type === POSTGRE_TITLE) { + return $existing_structure['columns']['poster_ip']['type'] !== 'inet'; + } + + return isset($existing_structure['columns']['poster_ip_old']) + || $existing_structure['columns']['poster_ip']['type'] !== 'varbinary'; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Messages(); + + // This will return true once its done, but we need to do a few more things. + $this->migrateData($table, 'poster_ip'); + + $start = Maintenance::getCurrentStart(); + + if ($start <= 7) { + $table->addIndex($table->indexes['idx_ip_index']); + + $this->handleTimeout(++$start); + } + + if ($start <= 8) { + $table->addIndex($table->indexes['idx_related_ip']); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/LegacyAttachments.php b/Sources/Maintenance/Migration/v2_1/LegacyAttachments.php new file mode 100644 index 00000000000..65392005d54 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LegacyAttachments.php @@ -0,0 +1,282 @@ +change_column( + '{db_prefix}attachments', + 'mime_type', + [ + 'type' => 'VARCHAR', + 'size' => 128, + 'not_null' => true, + 'default' => '', + ], + ); + + } + + $custom_av_dir = $this->checkCustomAvatarDirectory(); + Maintenance::$total_items = $this->getTotalAttachments(); + + // We may be using multiple attachment directories. + if (!empty(Config::$modSettings['currentAttachmentUploadDir']) && !is_array(Config::$modSettings['attachmentUploadDir']) && empty(Config::$modSettings['json_done'])) { + Config::$modSettings['attachmentUploadDir'] = @unserialize(Config::$modSettings['attachmentUploadDir']); + } + + + $is_done = false; + + while (!$is_done) { + $this->handleTimeout($start); + + $request = $this->query( + '', + 'SELECT id_attach, id_member, id_folder, filename, file_hash, mime_type + FROM {db_prefix}attachments + WHERE attachment_type != 1 + ORDER BY id_attach + LIMIT {int:start}, 100', + [ + 'start' => $start, + ], + ); + + // Finished? + if (Db::$db->num_rows($request) == 0) { + $is_done = true; + } + + while ($row = Db::$db->fetch_assoc($request)) { + // The current folder. + $currentFolder = !empty(Config::$modSettings['currentAttachmentUploadDir']) ? Config::$modSettings['attachmentUploadDir'][$row['id_folder']] : Config::$modSettings['attachmentUploadDir']; + + $fileHash = ''; + + // Old School? + if (empty($row['file_hash'])) { + // Remove international characters (windows-1252) + // These lines should never be needed again. Still, behave. + if (empty(Config::$db_character_set) || Config::$db_character_set != 'utf8') { + $row['filename'] = strtr( + $row['filename'], + "\x8a\x8e\x9a\x9e\x9f\xc0\xc1\xc2\xc3\xc4\xc5\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xe0\xe1\xe2\xe3\xe4\xe5\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xff", + 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy', + ); + $row['filename'] = strtr($row['filename'], ["\xde" => 'TH', "\xfe" => + 'th', "\xd0" => 'DH', "\xf0" => 'dh', "\xdf" => 'ss', "\x8c" => 'OE', + "\x9c" => 'oe', "\xc6" => 'AE', "\xe6" => 'ae', "\xb5" => 'u']); + } + // Sorry, no spaces, dots, or anything else but letters allowed. + $row['filename'] = preg_replace(['/\s/', '/[^\w_\.\-]/'], ['_', ''], $row['filename']); + + // Create a nice hash. + $fileHash = hash_hmac('sha1', $row['filename'] . time(), Config::$image_proxy_secret); + + // Iterate through the possible attachment names until we find the one that exists + $oldFile = $currentFolder . '/' . $row['id_attach'] . '_' . strtr($row['filename'], '.', '_') . md5($row['filename']); + + if (!file_exists($oldFile)) { + $oldFile = $currentFolder . '/' . $row['filename']; + + if (!file_exists($oldFile)) { + $oldFile = false; + } + } + + // Build the new file. + $newFile = $currentFolder . '/' . $row['id_attach'] . '_' . $fileHash . '.dat'; + } + // Just rename the file. + else { + $oldFile = $currentFolder . '/' . $row['id_attach'] . '_' . $row['file_hash']; + $newFile = $currentFolder . '/' . $row['id_attach'] . '_' . $row['file_hash'] . '.dat'; + + // Make sure it exists... + if (!file_exists($oldFile)) { + $oldFile = false; + } + } + + if (!$oldFile) { + // Existing attachment could not be found. Just skip it... + continue; + } + + // Check if the av is an attachment + if ($row['id_member'] != 0) { + if (rename($oldFile, $custom_av_dir . '/' . $row['filename'])) { + $this->query( + '', + 'UPDATE {db_prefix}attachments + SET file_hash = {empty}, attachment_type = 1 + WHERE id_attach = {int:attach_id}', + [ + 'attach_id' => $row['id_attach'], + ], + ); + $start--; + } + } + // Just a regular attachment. + else { + rename($oldFile, $newFile); + } + + // Only update this if it was successful and the file was using the old system. + if (empty($row['file_hash']) && !empty($fileHash) && file_exists($newFile) && !file_exists($oldFile)) { + $this->query( + '', + 'UPDATE {db_prefix}attachments + SET file_hash = {string:file_hash} + WHERE id_attach = {int:atach_id}', + [ + 'file_hash' => $fileHash, + 'attach_id' => $row['id_attach'], + ], + ); + } + + // While we're here, do we need to update the mime_type? + if (empty($row['mime_type']) && file_exists($newFile)) { + $size = @getimagesize($newFile); + + if (!empty($size['mime'])) { + $this->query( + '', + 'UPDATE {db_prefix}attachments + SET mime_type = {string:mime_type} + WHERE id_attach = {int:id_attach}', + [ + 'id_attach' => $row['id_attach'], + 'mime_type' => substr($size['mime'], 0, 20), + ], + ); + } + } + } + Db::$db->free_result($request); + + $start += 100; + Maintenance::setCurrentStart($start); + } + + Config::updateModSettings(['attachments_21_done' => 1]); + + return true; + } + + /****************** + * Internal methods + ******************/ + + /** + * + */ + protected function checkCustomAvatarDirectory(): string + { + // Need to know a few things first. + $custom_av_dir = !empty(Config::$modSettings['custom_avatar_dir']) ? Config::$modSettings['custom_avatar_dir'] : Config::$boarddir . '/custom_avatar'; + + // This little fellow has to cooperate... + if (!is_writable($custom_av_dir)) { + // Try 755 and 775 first since 777 doesn't always work and could be a risk... + $chmod_values = [0755, 0775, 0777]; + + foreach ($chmod_values as $val) { + // If it's writable, break out of the loop + if (is_writable($custom_av_dir)) { + break; + } + + @chmod($custom_av_dir, $val); + } + } + + // If we already are using a custom dir, delete the predefined one. + if (realpath($custom_av_dir) != realpath(Config::$boarddir . '/custom_avatar')) { + // Borrow custom_avatars index.php file. + if (!file_exists($custom_av_dir . '/index.php')) { + @rename(Config::$boarddir . '/custom_avatar/index.php', $custom_av_dir . '/index.php'); + } else { + @unlink(Config::$boarddir . '/custom_avatar/index.php'); + } + + // Borrow blank.png as well + if (!file_exists($custom_av_dir . '/blank.png')) { + @rename(Config::$boarddir . '/custom_avatar/blank.png', $custom_av_dir . '/blank.png'); + } else { + @unlink(Config::$boarddir . '/custom_avatar/blank.png'); + } + + // Attempt to delete the directory. + @rmdir(Config::$boarddir . '/custom_avatar'); + } + + return $custom_av_dir; + } + + /** + * + */ + protected function getTotalAttachments(): int + { + $request = $this->query('', ' + SELECT COUNT(*) + FROM {db_prefix}attachments + WHERE attachment_type != 1'); + list($total_attachments) = Db::$db->fetch_row($request); + Db::$db->free_result($request); + + return (int) $total_attachments; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/LegacyData.php b/Sources/Maintenance/Migration/v2_1/LegacyData.php new file mode 100644 index 00000000000..a412a149317 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LegacyData.php @@ -0,0 +1,204 @@ +query('', ' + ALTER TABLE {db_prefix}board_permissions + MODIFY COLUMN id_profile SMALLINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating log_digest id_topic + if ($start <= 1) { + $this->query('', ' + ALTER TABLE {db_prefix}log_digest + MODIFY COLUMN id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating log_digest id_msg + if ($start <= 2) { + $this->query('', ' + ALTER TABLE {db_prefix}log_digest + MODIFY COLUMN id_msg INT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating log_reported + if ($start <= 3) { + $this->query('', ' + ALTER TABLE {db_prefix}log_reported + MODIFY COLUMN body MEDIUMTEXT NOT NULL + '); + + $this->handleTimeout(++$start); + } + + // Updating log_spider_hits + if ($start <= 4) { + $this->query('', ' + ALTER TABLE {db_prefix}log_spider_hits + MODIFY COLUMN processed TINYINT NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating members new_pm + if ($start <= 5) { + $this->query('', ' + ALTER TABLE {db_prefix}members + MODIFY COLUMN new_pm TINYINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating members pm_ignore_list + if ($start <= 6) { + $this->query('', ' + ALTER TABLE {db_prefix}members + MODIFY COLUMN pm_ignore_list TEXT NULL + '); + + $this->handleTimeout(++$start); + } + + // Updating password_salt + if ($start <= 7) { + $this->query('', ' + ALTER TABLE {db_prefix}members + MODIFY COLUMN password_salt VARCHAR(255) NOT NULL DEFAULT {empty} + '); + + $this->handleTimeout(++$start); + } + + // Updating member_logins id_member + if ($start <= 8) { + $this->query('', ' + ALTER TABLE {db_prefix}member_logins + MODIFY COLUMN id_member MEDIUMINT NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating member_logins time + if ($start <= 9) { + $this->query('', ' + ALTER TABLE {db_prefix}member_logins + MODIFY COLUMN time INT NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating pm_recipients is_new + if ($start <= 10) { + $this->query('', ' + ALTER TABLE {db_prefix}pm_recipients + MODIFY COLUMN is_new TINYINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating pm_rules id_member + if ($start <= 11) { + $this->query('', ' + ALTER TABLE {db_prefix}pm_rules + MODIFY COLUMN id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating polls guest_vote + if ($start <= 12) { + $this->query('', ' + ALTER TABLE {db_prefix}polls + MODIFY COLUMN guest_vote TINYINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating polls id_member + if ($start <= 13) { + $this->query('', ' + ALTER TABLE {db_prefix}polls + MODIFY COLUMN id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating sessions last_update + if ($start <= 14) { + $this->query('', ' + ALTER TABLE {db_prefix}sessions + MODIFY COLUMN last_update INT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Likes.php b/Sources/Maintenance/Migration/v2_1/Likes.php new file mode 100644 index 00000000000..0d6d75eb66e --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Likes.php @@ -0,0 +1,83 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'user_likes', $tables); + } + + /** + * + */ + 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(); + + $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(); + + foreach ($MessagesTable->columns as $column) { + // Add the columns. + if ($column->name === 'likes' && !isset($existing_structure['columns'][$column->name])) { + $MessagesTable->addColumn($column); + } + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/LogErrorsBacktrace.php b/Sources/Maintenance/Migration/v2_1/LogErrorsBacktrace.php new file mode 100644 index 00000000000..1ca9a51a336 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogErrorsBacktrace.php @@ -0,0 +1,62 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'backtrace') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\LogErrors(); + $table->addColumn($table->columns['backtrace']); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/LogOnlineURL.php b/Sources/Maintenance/Migration/v2_1/LogOnlineURL.php new file mode 100644 index 00000000000..3bd0f8db575 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogOnlineURL.php @@ -0,0 +1,51 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + if ($column->name === 'url' && ($existing_structure['columns']['url'] !== 'varchar' || (int) $existing_structure['columns']['url']['size'] !== 2048)) { + $table->alterColumn($column); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/LogReportedCommentsEmail.php b/Sources/Maintenance/Migration/v2_1/LogReportedCommentsEmail.php new file mode 100644 index 00000000000..bb876d85150 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogReportedCommentsEmail.php @@ -0,0 +1,77 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'email_address') { + return true; + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new \SMF\Db\Schema\v2_1\LogReportedComments(); + $existing_structure = $table->getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] == 'email_address') { + $col = new Column( + name: $column['name'], + type: 'varchar', + ); + + $table->dropColumn($col); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/LogSpiderHitsURL.php b/Sources/Maintenance/Migration/v2_1/LogSpiderHitsURL.php new file mode 100644 index 00000000000..b995b20f867 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/LogSpiderHitsURL.php @@ -0,0 +1,52 @@ +getCurrentStructure(); + + if ((int) $existing_structure['columns']['url']['size'] === 512) { + $table->alterColumn( + $table->columns['url'], + 'url', + ); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MailQueue.php b/Sources/Maintenance/Migration/v2_1/MailQueue.php new file mode 100644 index 00000000000..e1eab2c6133 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MailQueue.php @@ -0,0 +1,50 @@ +columns as $column) { + if ($column->name === 'body') { + $MailQueueTable->alterColumn($column); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MemberGroupsTfaRequired.php b/Sources/Maintenance/Migration/v2_1/MemberGroupsTfaRequired.php new file mode 100644 index 00000000000..22722be405b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MemberGroupsTfaRequired.php @@ -0,0 +1,62 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'tfa_required') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Membergroups(); + $table->addColumn($table->columns['tfa_required']); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MembergroupIcon.php b/Sources/Maintenance/Migration/v2_1/MembergroupIcon.php new file mode 100644 index 00000000000..81d5c7eb1b3 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembergroupIcon.php @@ -0,0 +1,128 @@ +getCurrentStructure(); + + if (isset($existing_structure['columns']['stars'])) { + foreach ($table->columns as $column) { + if ($column->name === 'icons') { + $table->alterColumn($column, 'stars'); + break; + } + } + } + + // !! @@TODO Move this to the cleanup section. + $request = $this->query( + '', + 'SELECT icons + FROM {db_prefix}membergroups + WHERE icons != {string:blank}', + [ + 'blank' => '', + ], + ); + + if ($request === false) { + $db_error_message = Db::$db->error(Db::$db_connection); + + throw new \Exception($$db_error_message ?? 'icons columns is invalid'); + } + + $toMove = []; + $toChange = []; + + while ($row = Db::$db->fetch_assoc($request)) { + if (strpos($row['icons'], 'star.gif') !== false) { + $toChange[] = [ + 'old' => $row['icons'], + 'new' => str_replace('star.gif', 'icon.png', $row['icons']), + ]; + } elseif (strpos($row['icons'], 'starmod.gif') !== false) { + $toChange[] = [ + 'old' => $row['icons'], + 'new' => str_replace('starmod.gif', 'iconmod.png', $row['icons']), + ]; + } elseif (strpos($row['icons'], 'stargmod.gif') !== false) { + $toChange[] = [ + 'old' => $row['icons'], + 'new' => str_replace('stargmod.gif', 'icongmod.png', $row['icons']), + ]; + } elseif (strpos($row['icons'], 'staradmin.gif') !== false) { + $toChange[] = [ + 'old' => $row['icons'], + 'new' => str_replace('staradmin.gif', 'iconadmin.png', $row['icons']), + ]; + } else { + $toMove[] = $row['icons']; + } + } + Db::$db->free_result($request); + + foreach ($toChange as $change) { + $this->query( + '', + 'UPDATE {db_prefix}membergroups + SET icons = {string:new} + WHERE icons = {string:old}', + [ + 'new' => $change['new'], + 'old' => $change['old'], + ], + ); + } + + // Attempt to move any custom uploaded icons. + foreach ($toMove as $move) { + // Get the actual image. + $image = explode('#', $move); + $image = $image[1]; + + // PHP wont suppress errors when running things from shell, so make sure it exists first... + if (file_exists(Config::$modSettings['theme_dir'] . '/images/' . $image)) { + @rename(Config::$modSettings['theme_dir'] . '/images/' . $image, Config::$modSettings['theme_dir'] . '/images/membericons/' . $image); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MembersHideEmail.php b/Sources/Maintenance/Migration/v2_1/MembersHideEmail.php new file mode 100644 index 00000000000..436d3f89dbb --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersHideEmail.php @@ -0,0 +1,77 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'hide_email') { + return true; + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new \SMF\Db\Schema\v2_1\Members(); + $existing_structure = $table->getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] == 'hide_email') { + $col = new Column( + name: $column['name'], + type: 'varchar', + ); + + $table->dropColumn($col); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MembersLangUTF8.php b/Sources/Maintenance/Migration/v2_1/MembersLangUTF8.php new file mode 100644 index 00000000000..63780c924e6 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersLangUTF8.php @@ -0,0 +1,46 @@ +query('', ' + UPDATE {db_prefix}members + SET lngfile = REPLACE(lngfile, {literal:-utf8}, {empty}'); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MembersOpenID.php b/Sources/Maintenance/Migration/v2_1/MembersOpenID.php new file mode 100644 index 00000000000..c313ab2218b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersOpenID.php @@ -0,0 +1,71 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'openid_uri') { + return true; + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Members(); + $existing_structure = $table->getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'openid_uri') { + $old_col = new Column('openid_uri', 'varchar'); + $table->dropColumn($old_col); + } + } + + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MembersTfaBackup.php b/Sources/Maintenance/Migration/v2_1/MembersTfaBackup.php new file mode 100644 index 00000000000..11000c26700 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersTfaBackup.php @@ -0,0 +1,62 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'tfa_backup') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Members(); + $table->addColumn($table->columns['tfa_backup']); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MembersTfaSecret.php b/Sources/Maintenance/Migration/v2_1/MembersTfaSecret.php new file mode 100644 index 00000000000..9aa9e4b2aa8 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersTfaSecret.php @@ -0,0 +1,62 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'tfa_secret') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $table = new \SMF\Db\Schema\v2_1\Members(); + $table->addColumn($table->columns['tfa_secret']); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MembersTimezone.php b/Sources/Maintenance/Migration/v2_1/MembersTimezone.php new file mode 100644 index 00000000000..e6a8d6f2c40 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MembersTimezone.php @@ -0,0 +1,72 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'timezone') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new \SMF\Db\Schema\v2_1\Members(); + $existing_structure = $table->getCurrentStructure(); + + foreach ($table->columns as $column) { + if ($column->name === 'timezone' && !isset($existing_structure['columns'][$column->name]) + ) { + $table->addColumn($column); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Mentions.php b/Sources/Maintenance/Migration/v2_1/Mentions.php new file mode 100644 index 00000000000..7de23acfe4b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Mentions.php @@ -0,0 +1,66 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'mentions', $tables); + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $MentionsTable = new \SMF\Db\Schema\v2_1\Mentions(); + + $tables = Db::$db->list_tables(); + + // Creating draft table. + if (!in_array(Config::$db_prefix . 'mentions', $tables)) { + $MentionsTable->create(); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MessagesModifiedReason.php b/Sources/Maintenance/Migration/v2_1/MessagesModifiedReason.php new file mode 100644 index 00000000000..ae7e01a575f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MessagesModifiedReason.php @@ -0,0 +1,72 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'modified_reason') { + return false; + } + } + + return true; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $table = new \SMF\Db\Schema\v2_1\Messages(); + $existing_structure = $table->getCurrentStructure(); + + foreach ($table->columns as $column) { + if ($column->name === 'modified_reason' && !isset($existing_structure['columns'][$column->name]) + ) { + $table->addColumn($column); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php b/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php new file mode 100644 index 00000000000..a740b98adda --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ModeratorGroups.php @@ -0,0 +1,66 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'moderator_groups', $tables); + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $ModeratorGroupsTable = new \SMF\Db\Schema\v2_1\ModeratorGroups(); + + $tables = Db::$db->list_tables(); + + // Creating draft table. + if (!in_array(Config::$db_prefix . 'moderator_groups', $tables)) { + $ModeratorGroupsTable->create(); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MovedTopics.php b/Sources/Maintenance/Migration/v2_1/MovedTopics.php new file mode 100644 index 00000000000..bc9d4675dc0 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MovedTopics.php @@ -0,0 +1,63 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + // Column exists, don't need to do this. + if (!in_array($column->name, $this->newColumns) || isset($existing_structure['columns'][$column->name])) { + continue; + } + + $table->addColumn($column); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MysqlLegacyData.php b/Sources/Maintenance/Migration/v2_1/MysqlLegacyData.php new file mode 100644 index 00000000000..eade43470f9 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MysqlLegacyData.php @@ -0,0 +1,204 @@ +query('', ' + ALTER TABLE {db_prefix}board_permissions + MODIFY COLUMN id_profile SMALLINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating log_digest id_topic + if ($start <= 1) { + $this->query('', ' + ALTER TABLE {db_prefix}log_digest + MODIFY COLUMN id_topic MEDIUMINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating log_digest id_msg + if ($start <= 2) { + $this->query('', ' + ALTER TABLE {db_prefix}log_digest + MODIFY COLUMN id_msg INT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating log_reported + if ($start <= 3) { + $this->query('', ' + ALTER TABLE {db_prefix}log_reported + MODIFY COLUMN body MEDIUMTEXT NOT NULL + '); + + $this->handleTimeout(++$start); + } + + // Updating log_spider_hits + if ($start <= 4) { + $this->query('', ' + ALTER TABLE {db_prefix}log_spider_hits + MODIFY COLUMN processed TINYINT NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating members new_pm + if ($start <= 5) { + $this->query('', ' + ALTER TABLE {db_prefix}members + MODIFY COLUMN new_pm TINYINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating members pm_ignore_list + if ($start <= 6) { + $this->query('', ' + ALTER TABLE {db_prefix}members + MODIFY COLUMN pm_ignore_list TEXT NULL + '); + + $this->handleTimeout(++$start); + } + + // Updating password_salt + if ($start <= 7) { + $this->query('', ' + ALTER TABLE {db_prefix}members + MODIFY COLUMN password_salt VARCHAR(255) NOT NULL DEFAULT {empty} + '); + + $this->handleTimeout(++$start); + } + + // Updating member_logins id_member + if ($start <= 8) { + $this->query('', ' + ALTER TABLE {db_prefix}member_logins + MODIFY COLUMN id_member MEDIUMINT NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating member_logins time + if ($start <= 9) { + $this->query('', ' + ALTER TABLE {db_prefix}member_logins + MODIFY COLUMN time INT NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating pm_recipients is_new + if ($start <= 10) { + $this->query('', ' + ALTER TABLE {db_prefix}pm_recipients + MODIFY COLUMN is_new TINYINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating pm_rules id_member + if ($start <= 11) { + $this->query('', ' + ALTER TABLE {db_prefix}pm_rules + MODIFY COLUMN id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating polls guest_vote + if ($start <= 12) { + $this->query('', ' + ALTER TABLE {db_prefix}polls + MODIFY COLUMN guest_vote TINYINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating polls id_member + if ($start <= 13) { + $this->query('', ' + ALTER TABLE {db_prefix}polls + MODIFY COLUMN id_member MEDIUMINT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + // Updating sessions last_update + if ($start <= 14) { + $this->query('', ' + ALTER TABLE {db_prefix}sessions + MODIFY COLUMN last_update INT UNSIGNED NOT NULL DEFAULT {literal:0} + '); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/MysqlModFixes.php b/Sources/Maintenance/Migration/v2_1/MysqlModFixes.php new file mode 100644 index 00000000000..e5363679e5b --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/MysqlModFixes.php @@ -0,0 +1,155 @@ +query( + '', + 'SELECT COLUMN_NAME, COLUMN_TYPE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = {string:db_name} + AND TABLE_NAME = {string:table_name} + AND COLUMN_DEFAULT IS NULL + AND COLUMN_KEY <> {literal:PRI} + AND IS_NULLABLE = {literal:NO} + AND COLUMN_NAME NOT IN ({array_string:ignore_cols})', + [ + 'db_name' => Config::$db_name, + 'table_name' => Config::$db_prefix . 'members', + 'ignore_cols' => ['buddy_list', 'signature', 'ignore_boards'], + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $this->query( + '', + 'ALTER TABLE {db_prefix}members + MODIFY {raw:col_name} {raw:col_type} NULL', + [ + 'col_name' => $row['COLUMN_NAME'], + 'col_type' => $row['COLUMN_TYPE'], + ], + ); + } + + $this->handleTimeout(++$start); + } + + // make boards mod col nullable + if ($start <= 1) { + $request = $this->query( + '', + 'SELECT COLUMN_NAME, COLUMN_TYPE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = {string:db_name} + AND TABLE_NAME = {string:table_name} + AND COLUMN_DEFAULT IS NULL + AND COLUMN_KEY <> {literal:PRI} + AND IS_NULLABLE = {literal:NO} + AND COLUMN_NAME NOT IN ({array_string:ignore_cols})', + [ + 'db_name' => Config::$db_name, + 'table_name' => Config::$db_prefix . 'boards', + 'ignore_cols' => ['description'], + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $this->query( + '', + 'ALTER TABLE {db_prefix}boards + MODIFY {raw:col_name} {raw:col_type} NULL', + [ + 'col_name' => $row['COLUMN_NAME'], + 'col_type' => $row['COLUMN_TYPE'], + ], + ); + } + + $this->handleTimeout(++$start); + } + + // make topics mod col nullable + if ($start <= 1) { + $request = $this->query( + '', + 'SELECT COLUMN_NAME, COLUMN_TYPE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = {string:db_name} + AND TABLE_NAME = {string:table_name} + AND COLUMN_DEFAULT IS NULL + AND COLUMN_KEY <> {literal:PRI} + AND IS_NULLABLE = {literal:NO}', + [ + 'db_name' => Config::$db_name, + 'table_name' => Config::$db_prefix . 'topics', + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $this->query( + '', + 'ALTER TABLE {db_prefix}topics + MODIFY {raw:col_name} {raw:col_type} NULL', + [ + 'col_name' => $row['COLUMN_NAME'], + 'col_type' => $row['COLUMN_TYPE'], + ], + ); + } + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/OpenID.php b/Sources/Maintenance/Migration/v2_1/OpenID.php new file mode 100644 index 00000000000..e36095760db --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/OpenID.php @@ -0,0 +1,55 @@ +list_tables(); + + return in_array('openid_assoc', $tables); + } + + /** + * + */ + public function execute(): bool + { + Db::$db->drop_table('{db_prefix}openid_assoc'); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PackageManager.php b/Sources/Maintenance/Migration/v2_1/PackageManager.php new file mode 100644 index 00000000000..e1225d17c12 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PackageManager.php @@ -0,0 +1,70 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + // Column exists, don't need to do this. + if (!in_array($column->name, $this->newColumns) || isset($existing_structure['columns'][$column->name])) { + continue; + } + + $table->addColumn($column); + } + + $this->query( + '', + 'UPDATE {db_prefix}log_packages + SET install_state = 0', + [], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Permissions.php b/Sources/Maintenance/Migration/v2_1/Permissions.php new file mode 100644 index 00000000000..fd9c127298a --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Permissions.php @@ -0,0 +1,333 @@ + 'profile_view', + 'profile_other_own' => 'profile_website_own', + 'profile_other_any' => 'profile_website_any', + ]; + + /** + * + */ + protected array $illegalGuestPermissions = [ + 'calendar_edit_any', + 'moderate_board', + 'moderate_forum', + ]; + + /** + * + */ + protected array $illegalGuestBoardPermissions = [ + 'announce_topic', + 'delete_any', + 'lock_any', + 'make_sticky', + 'merge_any', + 'modify_any', + 'modify_replies', + 'move_any', + 'poll_add_any', + 'poll_edit_any', + 'poll_lock_any', + 'poll_remove_any', + 'remove_any', + 'report_any', + 'split_any', + ]; + + /**************** + * Public methods + ****************/ + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $this->query( + '', + 'DELETE FROM {db_prefix}permissions + WHERE permission IN ({array_string:removedPermissions})', + [ + 'removedPermissions' => $this->removedPermissions, + ], + ); + + $this->handleTimeout(++$start); + + $this->query( + '', + 'DELETE FROM {db_prefix}board_permissions + WHERE permission IN ({array_string:removedBoardPermissions})', + [ + 'removedBoardPermissions' => $this->removedBoardPermissions, + ], + ); + + $this->handleTimeout(++$start); + + foreach ($this->renamedPermissions as $old => $new) { + $this->query( + '', + 'UPDATE {db_prefix}permissions + SET permission = {string:new} + WHERE permission = {string:old}', + [ + 'new' => $new, + 'old' => $old, + ], + ); + } + + $this->handleTimeout(++$start); + + $inserts = []; + + // Adding "profile_password_own" + $request = $this->query( + '', + 'SELECT id_group, add_deny + FROM {db_prefix}permissions + WHERE permission = {literal:profile_identity_own}', + [], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [ + (int) $row['id_group'], + 'profile_password_own', + (int) $row['add_deny'], + ]; + } + + Db::$db->free_result($request); + + if (!empty($inserts)) { + Db::$db->insert( + 'ignore', + '{db_prefix}permissions', + [ + 'id_group' => 'int', + 'permission' => 'string', + 'add_deny' => 'int', + ], + $inserts, + ['id_group', 'permission'], + ); + } + + $this->handleTimeout(++$start); + + // Adding "view_warning_own" and "view_warning_any" permissions. + if (isset(Config::$modSettings['warning_show'])) { + $can_view_warning_own = []; + $can_view_warning_any = []; + + if (Config::$modSettings['warning_show'] >= 1) { + $can_view_warning_own[] = 0; + + $request = $this->query( + '', + 'SELECT id_group + FROM {db_prefix}membergroups + WHERE min_posts = {int:not_post_based}', + [ + 'not_post_based' => -1, + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + if (in_array($row['id_group'], [1, 3])) { + continue; + } + + $can_view_warning_own[] = $row['id_group']; + } + Db::$db->free_result($request); + } + + if (Config::$modSettings['warning_show'] > 1) { + $can_view_warning_any = $can_view_warning_own; + } else { + $request = $this->query( + '', + 'SELECT id_group, add_deny + FROM {db_prefix}permissions + WHERE permission = {string:perm}', + [ + 'perm' => 'issue_warning', + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + if (in_array($row['id_group'], [-1, 1, 3]) || $row['add_deny'] != 1) { + continue; + } + + $can_view_warning_any[] = $row['id_group']; + } + Db::$db->free_result($request); + } + + $inserts = []; + + foreach ($can_view_warning_own as $id_group) { + $inserts[] = [$id_group, 'view_warning_own', 1]; + } + + foreach ($can_view_warning_any as $id_group) { + $inserts[] = [$id_group, 'view_warning_any', 1]; + } + + if (!empty($inserts)) { + Db::$db->insert( + 'ignore', + '{db_prefix}permissions', + [ + 'id_group' => 'int', + 'permission' => 'string', + 'add_deny' => 'int', + ], + $inserts, + ['id_group', 'permission'], + ); + } + + Db::$db->query( + '', + 'DELETE FROM {db_prefix}settings + WHERE variable = {string:warning_show}', + [ + 'warning_show' => 'warning_show', + ], + ); + } + + $this->handleTimeout(++$start); + + $inserts = []; + + $request = $this->query( + '', + 'SELECT id_group, add_deny + FROM {db_prefix}permissions + WHERE permission = {literal:profile_extra_own}', + [], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [$row['id_group'], 'profile_blurb_own', $row['add_deny']]; + $inserts[] = [$row['id_group'], 'profile_displayed_name_own', $row['add_deny']]; + $inserts[] = [$row['id_group'], 'profile_forum_own', $row['add_deny']]; + $inserts[] = [$row['id_group'], 'profile_website_own', $row['add_deny']]; + $inserts[] = [$row['id_group'], 'profile_signature_own', $row['add_deny']]; + } + + Db::$db->free_result($request); + + if (!empty($inserts)) { + Db::$db->insert( + 'ignore', + '{db_prefix}permissions', + [ + 'id_group' => 'int', + 'permission' => 'string', + 'add_deny' => 'int', + ], + $inserts, + ['id_group', 'permission'], + ); + } + + $this->handleTimeout(++$start); + + $this->query( + '', + 'DELETE FROM {db_prefix}board_permissions + WHERE id_group = {int:guests} + AND permission IN ({array_string:illegal_board_perms})', + [ + 'guests' => -1, + 'illegal_board_perms' => $this->illegalGuestBoardPermissions, + ], + ); + + $this->handleTimeout(++$start); + + Db::$db->query( + '', + 'DELETE FROM {db_prefix}permissions + WHERE id_group = {int:guests} + AND permission IN ({array_string:illegal_perms})', + [ + 'guests' => -1, + 'illegal_perms' => $this->illegalGuestPermissions, + ], + ); + + $this->handleTimeout(++$start); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php new file mode 100644 index 00000000000..30d8927c2a8 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PersonalMessageLabels.php @@ -0,0 +1,349 @@ +getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] === 'message_labels') { + return true; + } + } + + return false; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $PmLabelsTable = new \SMF\Db\Schema\v2_1\PmLabels(); + $PmLabeledMessagesTable = new \SMF\Db\Schema\v2_1\PmLabeledMessages(); + + $tables = Db::$db->list_tables(); + + if ($start <= 0) { + if (!in_array(Config::$db_prefix . 'pm_labels', $tables)) { + $PmLabelsTable->create(); + $this->handleTimeout(0); + } + + if (!in_array(Config::$db_prefix . 'pm_labeled_messages', $tables)) { + $PmLabeledMessagesTable->create(); + $this->handleTimeout(0); + } + + $PmRecipientsTable = new \SMF\Db\Schema\v2_1\PmRecipients(); + $existing_structure = $PmRecipientsTable->getCurrentStructure(); + + foreach ($PmRecipientsTable->columns as $column) { + // Column exists, don't need to do this. + if (isset($existing_structure['columns'][$column->name])) { + continue; + } + + $PmRecipientsTable->addColumn($column); + } + + $this->handleTimeout(++$start); + } + + $start = Maintenance::getCurrentStart(); + + $request = $this->query('', 'SELECT COUNT(*) FROM {db_prefix}members'); + list($maxMembers) = Db::$db->fetch_row($request); + Db::$db->free_result($request); + Maintenance::$total_items = (int) $maxMembers; + + if ($maxMembers > 0) { + $is_done = false; + + while (!$is_done) { + $this->handleTimeout($start); + + $inserts = []; + + // Pull the label info + $get_labels = Db::$db->query( + '', + 'SELECT id_member, message_labels + FROM {db_prefix}members + WHERE message_labels != {string:blank} + ORDER BY id_member + LIMIT {int:limit}', + [ + 'blank' => '', + 'limit' => $this->limit, + ], + ); + + $label_info = []; + $member_list = []; + + while ($row = Db::$db->fetch_assoc($get_labels)) { + $member_list[] = $row['id_member']; + + // Stick this in an array + $labels = explode(',', $row['message_labels']); + + // Build some inserts + foreach ($labels as $index => $label) { + // Keep track of the index of this label - we'll need that in a bit... + $label_info[$row['id_member']][$label] = $index; + } + } + + Db::$db->free_result($get_labels); + + foreach ($label_info as $id_member => $labels) { + foreach ($labels as $label => $index) { + $inserts[] = [$id_member, $label]; + } + } + + if (!empty($inserts)) { + Db::$db->insert( + '', + '{db_prefix}pm_labels', + [ + 'id_member' => 'int', + 'name' => 'string-30', + ], + $inserts, + [], + ); + + // Clear this out for our next query below + $inserts = []; + } + + // This is the easy part - update the inbox stuff + Db::$db->query( + '', + 'UPDATE {db_prefix}pm_recipients + SET in_inbox = {int:in_inbox} + WHERE FIND_IN_SET({int:minusone}, labels) + AND id_member IN ({array_int:member_list})', + [ + 'in_inbox' => 1, + 'minusone' => -1, + 'member_list' => $member_list, + ], + ); + + // Now we go pull the new IDs for each label + $get_new_label_ids = Db::$db->query( + '', + 'SELECT * + FROM {db_prefix}pm_labels + WHERE id_member IN ({array_int:member_list})', + [ + 'member_list' => $member_list, + ], + ); + + $label_info_2 = []; + + while ($label_row = Db::$db->fetch_assoc($get_new_label_ids)) { + // Map the old index values to the new ID values... + $old_index = $label_info[$label_row['id_member']][$label_row['name']]; + $label_info_2[$label_row['id_member']][$old_index] = $label_row['id_label']; + } + + Db::$db->free_result($get_new_label_ids); + + // Pull label info from pm_recipients + // Ignore any that are only in the inbox + $get_pm_labels = Db::$db->query( + '', + 'SELECT id_pm, id_member, labels + FROM {db_prefix}pm_recipients + WHERE deleted = {int:not_deleted} + AND labels != {string:minus_one} + AND id_member IN ({array_int:member_list})', + [ + 'not_deleted' => 0, + 'minus_one' => -1, + 'member_list' => $member_list, + ], + ); + + while ($row = Db::$db->fetch_assoc($get_pm_labels)) { + $labels = explode(',', $row['labels']); + + foreach ($labels as $a_label) { + if ($a_label == '-1') { + continue; + } + + $new_label_info = $label_info_2[$row['id_member']][$a_label]; + $inserts[] = [$row['id_pm'], $new_label_info]; + } + } + + Db::$db->free_result($get_pm_labels); + + // Insert the new data + if (!empty($inserts)) { + Db::$db->insert( + '', + '{db_prefix}pm_labeled_messages', + [ + 'id_pm' => 'int', + 'id_label' => 'int', + ], + $inserts, + [], + ); + } + + // Final step of this ridiculously massive process + $get_pm_rules = Db::$db->query( + '', + 'SELECT id_member, id_rule, actions + FROM {db_prefix}pm_rules + WHERE id_member IN ({array_int:member_list})', + [ + 'member_list' => $member_list, + ], + ); + + // Go through the rules, unserialize the actions, then figure out if there's anything we can use + while ($row = Db::$db->fetch_assoc($get_pm_rules)) { + $updated = false; + + // Turn this into an array... + $actions = unserialize($row['actions']); + + // Loop through the actions and see if we're applying a label anywhere + foreach ($actions as $index => $action) { + if ($action['t'] == 'lab') { + // Update the value of this label... + $actions[$index]['v'] = $label_info_2[$row['id_member']][$action['v']]; + $updated = true; + } + } + + if ($updated) { + // Put this back into a string + $actions = serialize($actions); + + Db::$db->query( + '', + 'UPDATE {db_prefix}pm_rules + SET actions = {string:actions} + WHERE id_rule = {int:id_rule}', + [ + 'actions' => $actions, + 'id_rule' => $row['id_rule'], + ], + ); + } + } + + // Remove processed pm labels, to avoid duplicated data if upgrader is restarted. + Db::$db->query( + '', + 'UPDATE {db_prefix}members + SET message_labels = {string:blank} + WHERE id_member IN ({array_int:member_list})', + [ + 'blank' => '', + 'member_list' => $member_list, + ], + ); + + Db::$db->free_result($get_pm_rules); + $start += $this->limit; + + if ($start >= $maxMembers) { + $is_done = true; + } + } + } + + $PmRecipientsTable = new \SMF\Db\Schema\v2_1\PmRecipients(); + $existing_structure = $PmRecipientsTable->getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] == 'labels') { + $col = new Column( + name: $column['name'], + type: 'varchar', + ); + + $PmRecipientsTable->dropColumn($col); + } + } + + $MembersTable = new \SMF\Db\Schema\v2_1\Members(); + $existing_structure = $MembersTable->getCurrentStructure(); + + foreach ($existing_structure['columns'] as $column) { + if ($column['name'] == 'message_labels') { + $col = new Column( + name: $column['name'], + type: 'varchar', + ); + + $MembersTable->dropColumn($col); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PersonalMessageNotification.php b/Sources/Maintenance/Migration/v2_1/PersonalMessageNotification.php new file mode 100644 index 00000000000..84edadf3bc1 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PersonalMessageNotification.php @@ -0,0 +1,141 @@ +list_columns('{db_prefix}members'); + + return in_array('pm_email_notify', $results); + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $request = $this->query( + '', + 'SELECT COUNT(*) + FROM {db_prefix}members', + [], + ); + + list($maxMembers) = Db::$db->fetch_row($request); + + Db::$db->free_result($request); + + Maintenance::$total_items = (int) $maxMembers; + + $is_done = false; + + while (!$is_done) { + $this->handleTimeout($start); + $inserts = []; + + // Skip errors here so we don't croak if the columns don't exist... + $request = $this->query( + '', + ' + SELECT id_member, pm_email_notify + FROM {db_prefix}members + ORDER BY id_member + LIMIT {int:start}, {int:limit}', + [ + 'start' => $start, + 'limit' => $this->limit, + ], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [$row['id_member'], 'pm_new', !empty($row['pm_email_notify']) ? 2 : 0]; + $inserts[] = [$row['id_member'], 'pm_notify', $row['pm_email_notify'] == 2 ? 2 : 1]; + } + Db::$db->free_result($request); + + if (!empty($inserts)) { + Db::$db->insert( + 'ignore', + '{db_prefix}user_alerts_prefs', + [ + 'id_member' => 'int', + 'alert_pref' => 'string', + 'alert_value' => 'string', + ], + $inserts, + ['id_member', 'alert_pref'], + ); + } + + $start += $this->limit; + + if ($start >= $maxMembers) { + $is_done = true; + } + } + + if ($is_done) { + $this->handleTimeout($start); + + $table = new \SMF\Db\Schema\v2_1\Members(); + $existing_structure = $table->getCurrentStructure(); + + if (isset($existing_structure['columns']['pm_email_notify'])) { + $old_col = new Column('pm_email_notify', 'varchar'); + $table->dropColumn($old_col); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PostgreSQLFindInSet.php b/Sources/Maintenance/Migration/v2_1/PostgreSQLFindInSet.php new file mode 100644 index 00000000000..9644defd3d6 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSQLFindInSet.php @@ -0,0 +1,64 @@ +query( + '', + "CREATE OR REPLACE FUNCTION FIND_IN_SET(needle text, haystack text) RETURNS integer AS ' + SELECT i AS result + FROM generate_series(1, array_upper(string_to_array($2,'',''), 1)) AS g(i) + WHERE (string_to_array($2,'',''))[i] = $1 + UNION ALL + SELECT 0 + LIMIT 1' + LANGUAGE 'sql';", + [], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PostgreSQLIPv6Helper.php b/Sources/Maintenance/Migration/v2_1/PostgreSQLIPv6Helper.php new file mode 100644 index 00000000000..b45bac3b6f2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSQLIPv6Helper.php @@ -0,0 +1,62 @@ +title === POSTGRE_TITLE; + } + + /** + * + */ + public function execute(): bool + { + $this->query('', ' + CREATE OR REPLACE FUNCTION migrate_inet(val IN anyelement) RETURNS inet + AS + $$ + BEGIN + RETURN (trim(val))::inet; + EXCEPTION + WHEN OTHERS THEN RETURN NULL; + END; + $$ LANGUAGE plpgsql'); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PostgreSQLSequences.php b/Sources/Maintenance/Migration/v2_1/PostgreSQLSequences.php new file mode 100644 index 00000000000..3e0842900c7 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSQLSequences.php @@ -0,0 +1,222 @@ + [ + 'table' => 'admin_info_files', + 'field' => 'id_file', + ], + 'attachments_seq' => [ + 'table' => 'attachments', + 'field' => 'id_attach', + ], + 'ban_groups_seq' => [ + 'table' => 'ban_groups', + 'field' => 'id_ban_group', + ], + 'ban_items_seq' => [ + 'table' => 'ban_items', + 'field' => 'id_ban', + ], + 'boards_seq' => [ + 'table' => 'boards', + 'field' => 'id_board', + ], + 'calendar_seq' => [ + 'table' => 'calendar', + 'field' => 'id_event', + ], + 'calendar_holidays_seq' => [ + 'table' => 'calendar_holidays', + 'field' => 'id_holiday', + ], + 'categories_seq' => [ + 'table' => 'categories', + 'field' => 'id_cat', + ], + 'custom_fields_seq' => [ + 'table' => 'custom_fields', + 'field' => 'id_field', + ], + 'log_actions_seq' => [ + 'table' => 'log_actions', + 'field' => 'id_action', + ], + 'log_banned_seq' => [ + 'table' => 'log_banned', + 'field' => 'id_ban_log', + ], + 'log_comments_seq' => [ + 'table' => 'log_comments', + 'field' => 'id_comment', + ], + 'log_errors_seq' => [ + 'table' => 'log_errors', + 'field' => 'id_error', + ], + 'log_group_requests_seq' => [ + 'table' => 'log_group_requests', + 'field' => 'id_request', + ], + 'log_member_notices_seq' => [ + 'table' => 'log_member_notices', + 'field' => 'id_notice', + ], + 'log_packages_seq' => [ + 'table' => 'log_packages', + 'field' => 'id_install', + ], + 'log_reported_seq' => [ + 'table' => 'log_reported', + 'field' => 'id_report', + ], + 'log_reported_comments_seq' => [ + 'table' => 'log_reported_comments', + 'field' => 'id_comment', + ], + 'log_scheduled_tasks_seq' => [ + 'table' => 'log_scheduled_tasks', + 'field' => 'id_log', + ], + 'log_spider_hits_seq' => [ + 'table' => 'log_spider_hits', + 'field' => 'id_hit', + ], + 'log_subscribed_seq' => [ + 'table' => 'log_subscribed', + 'field' => 'id_sublog', + ], + 'mail_queue_seq' => [ + 'table' => 'mail_queue', + 'field' => 'id_mail', + ], + 'membergroups_seq' => [ + 'table' => 'membergroups', + 'field' => 'id_group', + ], + 'members_seq' => [ + 'table' => 'members', + 'field' => 'id_member', + ], + 'message_icons_seq' => [ + 'table' => 'message_icons', + 'field' => 'id_icon', + ], + 'messages_seq' => [ + 'table' => 'messages', + 'field' => 'id_msg', + ], + 'package_servers_seq' => [ + 'table' => 'package_servers', + 'field' => 'id_server', + ], + 'permission_profiles_seq' => [ + 'table' => 'permission_profiles', + 'field' => 'id_profile', + ], + 'personal_messages_seq' => [ + 'table' => 'personal_messages', + 'field' => 'id_pm', + ], + 'pm_rules_seq' => [ + 'table' => 'pm_rules', + 'field' => 'id_rule', + ], + 'polls_seq' => [ + 'table' => 'polls', + 'field' => 'id_poll', + ], + 'scheduled_tasks_seq' => [ + 'table' => 'scheduled_tasks', + 'field' => 'id_task', + ], + 'smileys_seq' => [ + 'table' => 'smileys', + 'field' => 'id_smiley', + ], + 'spiders_seq' => [ + 'table' => 'spiders', + 'field' => 'id_spider', + ], + 'subscriptions_seq' => [ + 'table' => 'subscriptions', + 'field' => 'id_subscribe', + ], + 'topics_seq' => [ + 'table' => 'topics', + 'field' => 'id_topic', + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * + */ + public function isCandidate(): bool + { + return Config::$db_type == POSTGRE_TITLE; + } + + /** + * + */ + public function execute(): bool + { + for ($key = Maintenance::getCurrentStart(); $key < count($this->sequences); Maintenance::setCurrentStart()) { + $this->handleTimeout(); + + $value = $this->sequences[$key]; + + $this->query( + '', + "SELECT setval('{raw:key}', (SELECT COALESCE(MAX({raw:field}),1) FROM {raw:table}))", + [ + 'key' => Config::$db_prefix . $key, + 'field' => $value['field'], + 'table' => $value['table'], + ], + ); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PostgreSQLUnlogged.php b/Sources/Maintenance/Migration/v2_1/PostgreSQLUnlogged.php new file mode 100644 index 00000000000..99bbe725ed8 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSQLUnlogged.php @@ -0,0 +1,123 @@ +title === POSTGRE_TITLE; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $result = $this->query('', 'SHOW server_version_num'); + + if ($result !== false) { + while ($row = Db::$db->fetch_assoc($result)) { + $pg_version = $row['server_version_num']; + } + Db::$db->free_result($result); + } + + if (!isset($pg_version)) { + return true; + } + + foreach ($this->tables as $table) { + if ($pg_version >= 90500) { + $this->query( + '', + ' + ALTER TABLE {db_prefix}{raw:table} SET UNLOGGED;', + [ + 'table' => $table, + ], + ); + } else { + $this->query( + '', + ' + ALTER TABLE {db_prefix}{raw:table} rename to old_{db_prefix}{raw:table}; + + do + $$ + declare r record; + begin + for r in select * from pg_constraint where conrelid={string:old_table_conrelid}::regclass loop + execute format({raw:alter_table}, r.conname, {literal:old_} || r.conname); + end loop; + for r in select * from pg_indexes where tablename={string:old_table_name} and indexname !~ {string:regex_old} loop + execute format({string:alter_inex}, r.indexname, {literal:old_} || r.indexname); + end loop; + end; + $$; + + create unlogged table {db_prefix}{raw:table} (like old_{db_prefix}{raw:table} including all); + + insert into {db_prefix}{raw:table} select * from old_{db_prefix}{raw:table}; + + drop table old_{db_prefix}{raw:table};', + [ + 'table' => $table, + 'old_table_conrelid' => 'old_' . Db::$db->prefix . $table, + 'old_table_name' => 'old_' . Db::$db->prefix . $table, + 'alter_table' => 'alter table old_' . Db::$db->prefix . $table . ' rename constraint %I to %I', + 'regex_old' => '^old_', + 'alter_inex' => 'alter index %I rename to %I', + ], + ); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PostgreSqlTime.php b/Sources/Maintenance/Migration/v2_1/PostgreSqlTime.php new file mode 100644 index 00000000000..b49a85c7f5a --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgreSqlTime.php @@ -0,0 +1,85 @@ +query('', ' + DROP FUNCTION IF EXISTS FROM_UNIXTIME(int)'); + + $this->query('', ' + CREATE OR REPLACE FUNCTION FROM_UNIXTIME(bigint) RETURNS timestamp AS + \'SELECT timestamp \'\'epoch\'\' + $1 * interval \'\'1 second\'\' AS result\' + LANGUAGE \'sql\''); + + $this->handleTimeout(++$start); + } + + // bigint versions of date functions + if ($start <= 1) { + // MONTH(bigint) + $this->query('', ' + CREATE OR REPLACE FUNCTION MONTH (bigint) RETURNS integer AS + \'SELECT CAST (EXTRACT(MONTH FROM TO_TIMESTAMP($1)) AS integer) AS result\' + LANGUAGE \'sql\''); + + // DAYOFMONTH(bigint) + $this->query('', ' + CREATE OR REPLACE FUNCTION DAYOFMONTH (bigint) RETURNS integer AS + \'SELECT CAST (EXTRACT(DAY FROM TO_TIMESTAMP($1)) AS integer) AS result\' + LANGUAGE \'sql\''); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/PostgresqlSchemaDiff.php b/Sources/Maintenance/Migration/v2_1/PostgresqlSchemaDiff.php new file mode 100644 index 00000000000..e9f65e20b8d --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/PostgresqlSchemaDiff.php @@ -0,0 +1,150 @@ +schemaFixes[Maintenance::getCurrentSubStep()]; + + $this->query('', ' + ALTER TABLE {db_prefix}' . $fix[0] . ' + ' . $fix[1]); + + Maintenance::setCurrentSubStep(); + $this->handleTimeout(); + } + + $this->query('', ' + DROP INDEX IF EXISTS {db_prefix}log_actions_id_topic_id_log'); + $this->query('', ' + CREATE INDEX {db_prefix}log_actions_id_topic_id_log ON {db_prefix}log_actions (id_topic, id_log)'); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/RemoveKarma.php b/Sources/Maintenance/Migration/v2_1/RemoveKarma.php new file mode 100644 index 00000000000..ebe1947e6e6 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/RemoveKarma.php @@ -0,0 +1,86 @@ +query( + '', + 'DELETE FROM {db_prefix}settings + WHERE variable IN ({array_string:karma_vars})', + [ + 'karma_vars' => ['karmaMode', 'karmaTimeRestrictAdmins', 'karmaWaitTime', 'karmaMinPosts', 'karmaLabel', 'karmaSmiteLabel', 'karmaApplaudLabel'], + ], + ); + + $member_columns = Db::$db->list_columns('{db_prefix}members'); + + // Cleaning up old karma member settings. + if (in_array('karma_good', $member_columns)) { + Db::$db->remove_column('{db_prefix}members', 'karma_good'); + } + + // Does karma bad was enable? + if (in_array('karma_bad', $member_columns)) { + Db::$db->remove_column('{db_prefix}members', 'karma_bad'); + } + + // Cleaning up old karma permissions. + $this->query( + '', + 'DELETE FROM {db_prefix}permissions + WHERE permission = {string:karma_vars}', + [ + 'karma_vars' => 'karma_edit', + ], + ); + + // Cleaning up old log_karma table + Db::$db->drop_table('{db_prefix}log_karma'); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/ScheduledTasks.php b/Sources/Maintenance/Migration/v2_1/ScheduledTasks.php new file mode 100644 index 00000000000..568f9fe4717 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ScheduledTasks.php @@ -0,0 +1,123 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + // Column exists, don't need to do this. + if ($column->name !== 'callable' || isset($existing_structure['columns'][$column->name])) { + continue; + } + + $table->addColumn($column); + } + + foreach ($this->newTasks as $task) { + $request = $this->query( + '', + 'SELECT id_task + FROM {db_prefix}scheduled_tasks + WHERE task = {string:task}', + [ + 'task' => $task[5], + ], + ); + + //next_time, time_offset, time_regularity, time_unit, disabled, task, callable + if (Db::$db->num_rows($request) === 0) { + $result = Db::$db->insert( + 'replace', + '{db_prefix}scheduled_tasks', + [ + 'next_time' => 'int', + 'time_offset' => 'int', + 'time_regularity' => 'int', + 'time_unit' => 'string', + 'disabled' => 'int', + 'task' => 'string', + 'callable' => 'string', + ], + [$task], + ['next_time', 'time_offset', 'time_regularity', 'time_unit', 'disabled', 'task', 'callable'], + ); + } + + Db::$db->free_result($request); + } + + // Remove the old 'Auto Optimize' task. + $this->query( + '', + ' + DELETE FROM {db_prefix}scheduled_tasks + WHERE id_task = {int:AutoOptimizeTaskID}', + [ + 'AutoOptimizeTaskID' => 2, + ], + ); + + $this->query( + '', + ' + DELETE FROM {db_prefix}log_scheduled_tasks + WHERE id_task = {int:AutoOptimizeTaskID}', + [ + 'AutoOptimizeTaskID' => 2, + ], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/SessionIDs.php b/Sources/Maintenance/Migration/v2_1/SessionIDs.php new file mode 100644 index 00000000000..6c10953fe0c --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/SessionIDs.php @@ -0,0 +1,70 @@ +columns as $column) { + if ($column->name !== 'session') { + continue; + } + + $LogOnlineTable->alterColumn($column); + } + + foreach ($LogErrorsTable->columns as $column) { + if ($column->name !== 'session') { + continue; + } + + $LogErrorsTable->alterColumn($column); + } + + foreach ($SessionsTable->columns as $column) { + if ($column->name !== 'session_id') { + continue; + } + + $SessionsTable->alterColumn($column); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/SettingsUpdate.php b/Sources/Maintenance/Migration/v2_1/SettingsUpdate.php new file mode 100644 index 00000000000..373fd323049 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/SettingsUpdate.php @@ -0,0 +1,258 @@ + 1, + 'enable_ajax_alerts' => 1, + 'alerts_auto_purge' => 30, + 'minimize_files' => 1, + 'additional_options_collapsable' => 1, + 'defaultMaxListItems' => 15, + 'loginHistoryDays' => 30, + 'securityDisable_moderate' => 1, + 'httponlyCookies' => 1, + 'samesiteCookies' => 'lax', + 'export_expiry' => 7, + 'export_min_diskspace_pct' => 5, + 'export_rate' => 250, + 'mark_read_beyond' => 90, + 'mark_read_delete_beyond' => 365, + 'mark_read_max_users' => 500, + 'enableThemes' => 1, + 'theme_guests' => 1, + 'mail_limit' => 5, + 'mail_quantity' => 5, + 'gravatarEnabled' => 1, + 'gravatarOverride' => 0, + 'gravatarAllowExtraEmail' => 1, + 'gravatarMaxRating' => 'PG', + 'tfa_mode' => 1, + 'cal_disable_prev_next' => 0, + 'cal_week_links' => 2, + 'cal_prev_next_links' => 1, + 'cal_short_days' => 0, + 'cal_short_months' => 0, + 'cal_week_numbers' => 0, + ]; + + protected array $removedSettings = [ + 'enableStickyTopics', + 'guest_hideContacts', + 'notify_new_registration', + 'attachmentEncryptFilenames', + 'hotTopicPosts', + 'hotTopicVeryPosts', + 'fixLongWords', + 'admin_feature', + 'log_ban_hits', + 'topbottomEnable', + 'simpleSearch', + 'enableVBStyleLogin', + 'admin_bbc', + 'enable_unwatch', + 'cache_memcached', + 'cache_enable', + 'cookie_no_auth_secret', + 'time_offset', + 'autoOptMaxOnline', + 'enableOpenID', + 'dh_keys', + 'cal_allowspan', + ]; + + /**************** + * Public methods + ****************/ + + /** + * + */ + public function execute(): bool + { + $newSettings = []; + + // Copying the current package backup setting. + if (!isset(Config::$modSettings['package_make_full_backups']) && isset(Config::$modSettings['package_make_backups'])) { + $newSettings['package_make_full_backups'] = Config::$modSettings['package_make_backups']; + } + + // Copying the current "allow users to disable word censor" setting. + if (!isset(Config::$modSettings['allow_no_censored'])) { + $request = $this->query( + '', + 'SELECT value + FROM {db_prefix}themes + WHERE variable={string:allow_no_censored} + AND id_theme = 1 OR id_theme = {int:default_theme}', + [ + 'allow_no_censored' => 'allow_no_censored', + 'default_theme' => Config::$modSettings['theme_default'] ?? 1, + ], + ); + + // Is it set for either "default" or the one they've set as default? + while ($row = Db::$db->fetch_assoc($request)) { + if ($row['value'] == 1) { + $newSettings['allow_no_censored'] = 1; + + // Don't do this twice... + break; + } + } + } + + // Add all any settings to the settings table. + foreach ($this->newSettings as $key => $default) { + if (!isset(Config::$modSettings[$key])) { + $newSettings[$key] = $default; + } + } + + // Enable some settings we ripped from Theme settings. + $ripped_settings = ['show_modify', 'show_user_images', 'show_blurb', 'show_profile_buttons', 'subject_toggle', 'hide_post_group']; + + $request = $this->query( + '', + 'SELECT variable, value + FROM {db_prefix}themes + WHERE variable IN({array_string:ripped_settings}) + AND id_member = 0 + AND id_theme = 1', + [ + 'ripped_settings' => $ripped_settings, + ], + ); + + $inserts = []; + + while ($row = Db::$db->fetch_assoc($request)) { + if (!isset(Config::$modSettings[$row['variable']])) { + $newSettings[$row['variable']] = $row['value']; + } + } + Db::$db->free_result($request); + + // Calculate appropriate hash cost. + if (!isset(Config::$modSettings['bcrypt_hash_cost'])) { + $newSettings['bcrypt_hash_cost'] = Security::hashBenchmark(); + } + + // Adding new profile data export settings. + if (!isset(Config::$modSettings['export_dir'])) { + $newSettings['export_dir'] = Config::$boarddir . '/exports'; + } + + // Deleting integration hooks. + foreach (Config::$modSettings as $key => $val) { + if (substr((string) $key, 0, strlen('integrate_')) == 'integrate_') { + $newSettings[(string) $key] = null; + } + } + + // Fixing a deprecated option. + if (isset(Config::$modSettings['avatar_action_too_large']) && (Config::$modSettings['avatar_action_too_large'] == 'option_html_resize' || Config::$modSettings['avatar_action_too_large'] == 'option_js_resize')) { + $newSettings['avatar_action_too_large'] = 'option_css_resize'; + } + + // Cleaning up the old Core Features page. + if (isset(Config::$modSettings['admin_features'])) { + $admin_features = explode(',', Config::$modSettings['admin_features']); + + // cd = calendar, should also have set cal_enabled already + // cp = custom profile fields, which already has several fields that cover tracking + // ps = paid subs, should also have set paid_enabled already + // rg = reports generation, which is now permanently on + // sp = spider tracking, should also have set spider_mode already + // w = warning system, which will be covered with warning_settings + + // The rest we have to deal with manually. + // Moderation log - modlog_enabled itself should be set but we have others now + if (in_array('ml', $admin_features)) { + $newSettings[] = ['adminlog_enabled', '1']; + $newSettings[] = ['userlog_enabled', '1']; + } + + // Post moderation + if (in_array('pm', $admin_features)) { + $newSettings[] = ['postmod_active', '1']; + } + } + + // Renamed setting. + if (isset(Config::$modSettings['allow_sm_stats'])) { + $newSettings['sm_stats_key'] = Config::$modSettings['allow_sm_stats']; + $newSettings['allow_sm_stats'] = null; + $newSettings['enable_sm_stats'] = 1; + } + + // Calendar max cols data correction. + if (!isset(Config::$modSettings['cal_allowspan'])) { + $newSettings['cal_maxspan'] = 0; + } elseif (Config::$modSettings['cal_allowspan'] == false) { + $newSettings['cal_maxspan'] = 1; + } else { + $newSettings['cal_maxspan'] = (int) (Config::$modSettings['cal_maxspan'] > 1) ? Config::$modSettings['cal_maxspan'] : 0; + } + + // Update the max year for the calendar + $newSettings['cal_maxyear'] = 2030; + + // TimeZone support. + if (!empty(Config::$modSettings['time_offset'])) { + Config::$modSettings['default_timezone'] = empty(Config::$modSettings['default_timezone']) || !in_array(Config::$modSettings['default_timezone'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)) ? 'UTC' : Config::$modSettings['default_timezone']; + + $now = date_create('now', timezone_open(Config::$modSettings['default_timezone'])); + + if (($new_tzid = timezone_name_from_abbr('', date_offset_get($now) + Config::$modSettings['time_offset'] * 3600, (int) date_format($now, 'I'))) !== false) { + $newSettings['default_timezone'] = $new_tzid; + } + } + + // Removed settings. + foreach ($this->removedSettings as $key) { + $newSettings[$key] = null; + } + + Config::updateModSettings($newSettings); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/Smileys.php b/Sources/Maintenance/Migration/v2_1/Smileys.php new file mode 100644 index 00000000000..f632d7ff1e5 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/Smileys.php @@ -0,0 +1,172 @@ +list_tables(); + + if (!in_array(Config::$db_prefix . $table->name, $existing_tables)) { + $table->create(); + } + + $this->handleTimeout(++$start); + } + + // Cleaning up unused smiley sets and adding the lovely new ones + if ($start <= 1) { + // Start with the prior values... + $dirs = explode(',', Config::$modSettings['smiley_sets_known']); + $setnames = explode("\n", Config::$modSettings['smiley_sets_names']); + + // Build combined pairs of folders and names + $combined = []; + + foreach ($dirs as $ix => $dir) { + if (!empty($setnames[$ix])) { + $combined[$dir] = [$setnames[$ix], '']; + } + } + + // Add our lovely new 2.1 smiley sets if not already there + $combined['fugue'] = [Lang::$txt['default_fugue_smileyset_name'], 'png']; + $combined['alienine'] = [Lang::$txt['default_alienine_smileyset_name'], 'png']; + + // Add/fix our 2.0 sets (to correct past problems where these got corrupted) + $combined['default'] = [Lang::$txt['default_legacy_smileyset_name'], 'gif']; + $combined['aaron'] = [Lang::$txt['default_aaron_smileyset_name'], 'gif']; + $combined['akyhne'] = [Lang::$txt['default_akyhne_smileyset_name'], 'gif']; + + // Confirm they exist in the filesystem + $filtered = []; + + foreach ($combined as $dir => $attrs) { + if (is_dir(Config::$modSettings['smileys_dir'] . '/' . $dir . '/')) { + $filtered[$dir] = $attrs[0]; + } + } + + // Update the Settings Table... + Config::updateModSettings(['smiley_sets_known' => implode(',', array_keys($filtered))]); + Config::updateModSettings(['smiley_sets_names' => implode("\n", $filtered)]); + + // Populate the smiley_files table + $smileys_columns = Db::$db->list_columns('{db_prefix}smileys'); + + if (in_array('filename', $smileys_columns)) { + $inserts = []; + + $request = $this->query('', ' + SELECT id_smiley, filename + FROM {db_prefix}smileys'); + + while ($row = Db::$db->fetch_assoc($request)) { + $pathinfo = pathinfo($row['filename']); + + foreach ($filtered as $set => $dummy) { + $ext = $pathinfo['extension'] ?? ''; + + // If we have a default extension for this set, check if we can switch to it. + if (isset($combined[$set]) && !empty($combined[$set][1])) { + if (file_exists(Config::$modSettings['smileys_dir'] . '/' . $set . '/' . $pathinfo['filename'] . '.' . $combined[$set][1])) { + $ext = $combined[$set][1]; + } + } + // In a custom set and no extension specified? Ugh... + elseif (empty($ext)) { + // Any files matching this name? + $found = glob(Config::$modSettings['smileys_dir'] . '/' . $set . '/' . $pathinfo['filename'] . '.*'); + $ext = !empty($found) ? pathinfo($found[0], PATHINFO_EXTENSION) : 'gif'; + } + + $inserts[] = [$row['id_smiley'], $set, $pathinfo['filename'] . '.' . $ext]; + } + } + Db::$db->free_result($request); + + if (!empty($inserts)) { + Db::$db->insert( + 'ignore', + '{db_prefix}smiley_files', + ['id_smiley' => 'int', 'smiley_set' => 'string-48', 'filename' => 'string-48'], + $inserts, + ['id_smiley', 'smiley_set'], + ); + + // Unless something went horrifically wrong, drop the defunct column + if (count($inserts) == Db::$db->affected_rows()) { + $table = new \SMF\Db\Schema\v2_1\Smileys(); + $existing_structure = $table->getCurrentStructure(); + + if (isset($existing_structure['columns']['filename'])) { + $oldColumn = new Column('filename', 'varchar'); + $table->dropColumn($oldColumn); + } + } + } + } + + // Set new default if the old one doesn't exist + // If fugue exists, use that. Otherwise, what the heck, just grab the first one... + if (!array_key_exists(Config::$modSettings['smiley_sets_default'], $filtered)) { + if (array_key_exists('fugue', $filtered)) { + $newdefault = 'fugue'; + } elseif (!empty($filtered) && is_array($filtered)) { + $newdefault = array_keys($filtered)[0]; + } else { + $newdefault = ''; + } + + Config::updateModSettings(['smiley_sets_default' => $newdefault]); + } + + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/ThemeSettings.php b/Sources/Maintenance/Migration/v2_1/ThemeSettings.php new file mode 100644 index 00000000000..23db83b95e2 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ThemeSettings.php @@ -0,0 +1,194 @@ + '3000', + ]; + + protected array $removedThemeSettings = [ + 'show_board_desc', + 'display_quick_reply', + 'show_mark_read', + 'show_member_bar', + 'linktree_link', + 'show_bbc', + 'additional_options_collapsable', + 'subject_toggle', + 'show_modify', + 'show_profile_buttons', + 'show_user_images', + 'show_blurb', + 'show_gender', + 'hide_post_group', + 'drafts_autosave_enabled', + 'forum_width', + ]; + + /**************** + * Public methods + ****************/ + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + if ($start === 0) { + + $this->query( + '', + 'UPDATE {db_prefix}themes + SET value = {string:new_theme_name} + WHERE value LIKE {string:old_theme_name}', + [ + 'new_theme_name' => 'SMF Default Theme - Curve2', + 'old_theme_name' => 'SMF Default Theme%', + ], + ); + + $this->handleTimeout(++$start); + } + + if ($start <= 1) { + foreach ($this->updatedThemeSettings as $key => $value) { + $this->query( + '', + 'UPDATE {db_prefix}themes + SET value = {string:value} + WHERE value = {string:key}', + [ + 'value' => $value, + 'key' => $key, + ], + ); + } + + $this->handleTimeout(++$start); + } + + if ($start <= 2) { + $this->query( + '', + 'UPDATE {db_prefix}boards + SET id_theme = 0', + ); + + $this->handleTimeout(++$start); + } + + if ($start <= 3) { + $this->query( + '', + 'UPDATE {db_prefix}members + SET id_theme = 0', + ); + + $this->handleTimeout(++$start); + } + + if ($start <= 4) { + // Fetch list of theme directories + $request = $this->query( + '', + 'SELECT id_theme, variable, value + FROM {db_prefix}themes + WHERE variable = {string:theme_dir} + AND id_theme != {int:default_theme};', + [ + 'default_theme' => 1, + 'theme_dir' => 'theme_dir', + ], + ); + + // Check which themes exist in the filesystem & save off their IDs + // Don't delete default theme(start with 1 in the array), & make sure to delete old core theme + $known_themes = ['1']; + $core_dir = Config::$boarddir . '/Themes/core'; + + while ($row = Db::$db->fetch_assoc($request)) { + if ($row['value'] != $core_dir && is_dir($row['value'])) { + $known_themes[] = $row['id_theme']; + } + } + + // Cleanup unused theme settings + $this->query( + '', + 'DELETE FROM {db_prefix}themes + WHERE id_theme NOT IN ({array_int:known_themes});', + [ + 'known_themes' => $known_themes, + ], + ); + + // Set knownThemes + $known_themes = implode(',', $known_themes); + $this->query( + '', + 'UPDATE {db_prefix}settings + SET value = {string:known_themes} + WHERE variable = {string:known_theme_str};', + [ + 'known_theme_str' => 'knownThemes', + 'known_themes' => $known_themes, + ], + ); + + $this->handleTimeout(++$start); + } + + if ($start <= 5) { + $this->query( + '', + 'DELETE FROM {db_prefix}themes + WHERE variable IN ({array_string:removed_settings})', + [ + 'removed_settings' => $this->removedThemeSettings, + ], + ); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/TopicUnwatch.php b/Sources/Maintenance/Migration/v2_1/TopicUnwatch.php new file mode 100644 index 00000000000..9ca53223730 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/TopicUnwatch.php @@ -0,0 +1,60 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + // Add the unwatched column. + if ($column->name === 'unwatched' && !isset($existing_structure['columns'][$column->name])) { + $table->addColumn($column); + continue; + } + + // Remove the disregarded column + if ($column->name === 'disregarded' && isset($existing_structure['columns'][$column->name])) { + $table->dropColumn($column); + continue; + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/UserDrafts.php b/Sources/Maintenance/Migration/v2_1/UserDrafts.php new file mode 100644 index 00000000000..21269cf1847 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/UserDrafts.php @@ -0,0 +1,173 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'user_drafts', $tables) || Maintenance::getCurrentStart() > 0; + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $DraftsTable = new \SMF\Db\Schema\v2_1\UserDrafts(); + + $tables = Db::$db->list_tables(); + + // Creating draft table. + if ($start <= 0 && !in_array(Config::$db_prefix . 'user_drafts', $tables)) { + $DraftsTable->create(); + + $this->handleTimeout(++$start); + } + + // Adding draft permissions. + if ($start <= 1 && version_compare(trim(strtolower(@Config::$modSettings['smfVersion'])), '2.1.foo', '<')) { + // 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 + FROM {db_prefix}board_permissions + WHERE permission = {literal:post_unapproved_topics}', + [], + ); + + $inserts = []; + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [ + (int) $row['id_group'], + (int) $row['id_board'], + 'post_draft', + (int) $row['add_deny'], + ]; + } + Db::$db->free_result($request); + + if (!empty($inserts)) { + Db::$db->insert( + 'ignore', + '{db_prefix}board_permissions', + [ + 'id_group' => 'int', + 'id_board' => 'int', + 'permission' => 'string', + 'add_deny' => 'int', + ], + $inserts, + ['id_member', 'alert_pref'], + ); + } + + // Next we find people who can send PMs, and assume they can save pm_drafts as well + $request = $this->query( + '', + 'SELECT id_group, add_deny, permission + FROM {db_prefix}permissions + WHERE permission = {literal:pm_send}', + [], + ); + + $inserts = []; + + while ($row = Db::$db->fetch_assoc($request)) { + $inserts[] = [ + (int) $row['id_group'], + 'pm_draft', + (int) $row['add_deny'], + ]; + } + Db::$db->free_result($request); + + if (!empty($inserts)) { + Db::$db->insert( + 'ignore', + '{db_prefix}permissions', + [ + 'id_group' => 'int', + 'permission' => 'string', + 'add_deny' => 'int', + ], + $inserts, + ['id_group', 'permission'], + ); + } + + $this->handleTimeout(++$start); + } + + if ($start <= 2) { + Config::updateModSettings([ + 'drafts_autosave_enabled' => Config::$modSettings['drafts_autosave_enabled'] ?? 1, + 'drafts_show_saved_enabled' => Config::$modSettings['drafts_show_saved_enabled'] ?? 1, + 'drafts_keep_days' => Config::$modSettings['drafts_keep_days'] ?? 7, + ]); + + Db::$db->insert( + 'ignore', + '{db_prefix}themes', + [ + 'id_member' => 'int', + 'id_theme' => 'int', + 'variable' => 'string', + 'value' => 'string', + ], + [ + [ + -1, + 1, + 'drafts_show_saved_enabled', + '1', + ], + ], + ['id_member', 'id_theme', 'variable'], + ); + + $this->handleTimeout(++$start); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/ValidationServers.php b/Sources/Maintenance/Migration/v2_1/ValidationServers.php new file mode 100644 index 00000000000..46a2e8324bc --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/ValidationServers.php @@ -0,0 +1,127 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + // Column exists, don't need to do this. + if (!in_array($column->name, $this->newColumns) || isset($existing_structure['columns'][$column->name])) { + continue; + } + + $table->addColumn($column); + } + + $request = $this->query( + '', + 'SELECT id_server + FROM {db_prefix}{raw:table_name} + WHERE url LIKE {string:downloads_site}', + [ + 'table_name' => $table->name, + 'downloads_site' => 'https://download.simplemachines.org%', + ], + ); + + if (Db::$db->num_rows($request) != 0) { + list($downloads_server) = Db::$db->fetch_row($request); + } + Db::$db->free_result($request); + + if (empty($downloads_server)) { + Db::$db->insert( + '', + '{db_prefix}' . $table->name, + [ + 'name' => 'string', + 'url' => 'string', + 'validation_url' => 'string', + ], + [ + [ + 'Simple Machines Download Site', + 'https://download.simplemachines.org/browse.php?api=v1;smf_version={SMF_VERSION}', + 'https://download.simplemachines.org/validate.php?api=v1;smf_version={SMF_VERSION}', + ], + ], + ['id_server'], + ); + } + + // Ensure The Simple Machines Customize Site is https + $this->query( + '', + 'UPDATE {db_prefix}{raw:table_name} + SET url = {string:current_url} + WHERE url = {string:old_url}', + [ + 'table_name' => $table->name, + 'old_url' => 'http://custom.simplemachines.org/packages/mods', + 'current_url' => 'https://custom.simplemachines.org/packages/mods', + ], + ); + + // Add validation to Simple Machines Customize Site + $this->query( + '', + 'UPDATE {db_prefix}{raw:table_name} + SET url = {string:validation_url} + WHERE url = {string:custom_site}', + [ + 'table_name' => $table->name, + 'validation_url' => 'https://custom.simplemachines.org/api.php?action=validate;version=v1;smf_version={SMF_VERSION}', + 'custom_site' => 'https://custom.simplemachines.org/packages/mods', + ], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php b/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php new file mode 100644 index 00000000000..f8fcaf63e79 --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/VerificationQuestions.php @@ -0,0 +1,111 @@ +list_tables(); + + return !in_array(Config::$db_prefix . 'qanda', $tables); + } + + /** + * + */ + public function execute(): bool + { + $start = Maintenance::getCurrentStart(); + + $QandaTable = new \SMF\Db\Schema\v2_1\Qanda(); + + $tables = Db::$db->list_tables(); + + // Creating draft table. + if ($start <= 0 && !in_array(Config::$db_prefix . 'qanda', $tables)) { + $QandaTable->create(); + + $this->handleTimeout(++$start); + } + + $questions = []; + + $get_questions = $this->query( + '', + 'SELECT body AS question, recipient_name AS answer + FROM {db_prefix}log_comments + WHERE comment_type = {literal:ver_test}', + [], + ); + + while ($row = Db::$db->fetch_assoc($get_questions)) { + $questions[] = [ + Maintenance::getRequestedLanguage(), + $row['question'], + serialize([$row['answer']]), + ]; + } + + Db::$db->free_result($get_questions); + + if (!empty($questions)) { + Db::$db->insert( + '', + '{db_prefix}qanda', + [ + 'lngfile' => 'string', + 'question' => 'string', + 'answers' => 'string', + ], + $questions, + ['id_question'], + ); + + // Delete the questions from log_comments now + $this->query( + '', + 'DELETE FROM {db_prefix}log_comments + WHERE comment_type = {literal:ver_test}', + ); + } + + $this->handleTimeout(++$start); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v2_1/index.php b/Sources/Maintenance/Migration/v2_1/index.php new file mode 100644 index 00000000000..ee0549de33f --- /dev/null +++ b/Sources/Maintenance/Migration/v2_1/index.php @@ -0,0 +1,8 @@ +title !== MYSQL_TITLE) { + return true; + } + + $tables = Db::$db->list_tables(false, Db::$db->prefix . '%'); + + foreach ($tables as $table) { + $structure = Db::$db->table_structure($table); + + if ($structure['engine'] !== 'InnoDB') { + Db::$db->query( + '', + 'ALTER TABLE {identifier:table} + ENGINE {literal:InnoDB} + ROW_FORMAT=DYNAMIC', + [ + 'table' => $table, + ], + ); + } elseif ($structure['row_format'] !== 'Dynamic') { + Db::$db->query( + '', + 'ALTER TABLE {identifier:table} + ROW_FORMAT=DYNAMIC', + [ + 'table' => $table, + ], + ); + } + } + + // Try to ensure all future tables use dynamic row format. + Db::$db->query( + '', + 'SET GLOBAL innodb_default_row_format=DYNAMIC', + [ + 'db_error_skip' => true, + ], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/ErrorLogSession.php b/Sources/Maintenance/Migration/v3_0/ErrorLogSession.php new file mode 100644 index 00000000000..b6224f25c56 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/ErrorLogSession.php @@ -0,0 +1,45 @@ +change_column('{db_prefix}log_errors', 'session', ['default' => '']); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/EventUids.php b/Sources/Maintenance/Migration/v3_0/EventUids.php new file mode 100644 index 00000000000..5d904c573d7 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/EventUids.php @@ -0,0 +1,71 @@ +query( + '', + 'SELECT id_event, uid + FROM {db_prefix}calendar', + [], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + if ($row['uid'] === '') { + $calendar_updates[] = ['id_event' => $row['id_event'], 'uid' => (string) new Uuid()]; + } + } + + Db::$db->free_result($request); + + foreach ($calendar_updates as $calendar_update) { + Db::$db->query( + '', + 'UPDATE {db_prefix}calendar + SET uid = {string:uid} + WHERE id_event = {int:id_event}', + $calendar_update, + ); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php b/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php new file mode 100644 index 00000000000..131feb6d31c --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/HolidaysToEvents.php @@ -0,0 +1,683 @@ + [ + 'title' => "April Fools' Day", + 'start_date' => '2000-04-01', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + 'Christmas' => [ + 'start_date' => '2000-12-25', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + 'Cinco de Mayo' => [ + 'start_date' => '2000-05-05', + 'recurrence_end' => '9999-12-31', + 'location' => 'Mexico, USA', + 'rrule' => 'FREQ=YEARLY', + ], + 'D-Day' => [ + 'start_date' => '2000-06-06', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + 'Easter' => [ + 'start_date' => '2000-04-23', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'EASTER_W', + ], + 'Earth Day' => [ + 'start_date' => '2000-04-22', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + "Father's Day" => [ + 'start_date' => '2000-06-19', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY;BYMONTH=6;BYDAY=3SU', + ], + 'Flag Day' => [ + 'start_date' => '2000-06-14', + 'recurrence_end' => '9999-12-31', + 'location' => 'USA', + 'rrule' => 'FREQ=YEARLY', + ], + 'Good Friday' => [ + 'start_date' => '2000-04-21', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'EASTER_W-P2D', + ], + 'Groundhog Day' => [ + 'start_date' => '2000-02-02', + 'recurrence_end' => '9999-12-31', + 'location' => 'Canada, USA', + 'rrule' => 'FREQ=YEARLY', + ], + 'Halloween' => [ + 'start_date' => '2000-10-31', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + 'Independence Day' => [ + 'start_date' => '2000-07-04', + 'recurrence_end' => '9999-12-31', + 'location' => 'USA', + 'rrule' => 'FREQ=YEARLY', + ], + 'Labor Day' => [ + 'start_date' => '2000-09-03', + 'recurrence_end' => '9999-12-31', + 'location' => 'USA', + 'rrule' => 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', + ], + 'Labour Day' => [ + 'start_date' => '2000-09-03', + 'recurrence_end' => '9999-12-31', + 'location' => 'Canada', + 'rrule' => 'FREQ=YEARLY;BYMONTH=9;BYDAY=1MO', + ], + 'Memorial Day' => [ + 'start_date' => '2000-05-31', + 'recurrence_end' => '9999-12-31', + 'location' => 'USA', + 'rrule' => 'FREQ=YEARLY;BYMONTH=5;BYDAY=-1MO', + ], + "Mother's Day" => [ + 'start_date' => '2000-05-08', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY;BYMONTH=5;BYDAY=2SU', + ], + "New Year's" => [ + 'title' => "New Year's Day", + 'start_date' => '2000-01-01', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + 'Remembrance Day' => [ + 'start_date' => '2000-11-11', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + "St. Patrick's Day" => [ + 'start_date' => '2000-03-17', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + 'Thanksgiving' => [ + 'start_date' => '2000-11-26', + 'recurrence_end' => '9999-12-31', + 'location' => 'USA', + 'rrule' => 'FREQ=YEARLY;BYMONTH=11;BYDAY=4TH', + ], + 'United Nations Day' => [ + 'start_date' => '2000-10-24', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + "Valentine's Day" => [ + 'start_date' => '2000-02-14', + 'recurrence_end' => '9999-12-31', + 'rrule' => 'FREQ=YEARLY', + ], + 'Veterans Day' => [ + 'start_date' => '2000-11-11', + 'recurrence_end' => '9999-12-31', + 'location' => 'USA', + 'rrule' => 'FREQ=YEARLY', + ], + + // Astronomical events + 'Vernal Equinox' => [ + 'start_date' => '2000-03-20', + 'recurrence_end' => '2100-01-01', + 'start_time' => '07:30:00', + 'timezone' => 'UTC', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => [ + '20000320T073000Z', + '20010320T131900Z', + '20020320T190800Z', + '20030321T005800Z', + '20040320T064700Z', + '20050320T123600Z', + '20060320T182500Z', + '20070321T001400Z', + '20080320T060400Z', + '20090320T115300Z', + '20100320T174200Z', + '20110320T233100Z', + '20120320T052000Z', + '20130320T111000Z', + '20140320T165900Z', + '20150320T224800Z', + '20160320T043700Z', + '20170320T102600Z', + '20180320T161600Z', + '20190320T220500Z', + '20200320T035400Z', + '20210320T094300Z', + '20220320T153200Z', + '20230320T212200Z', + '20240320T031100Z', + '20250320T090000Z', + '20260320T144900Z', + '20270320T203800Z', + '20280320T022800Z', + '20290320T081700Z', + '20300320T140600Z', + '20310320T195500Z', + '20320320T014400Z', + '20330320T073400Z', + '20340320T132300Z', + '20350320T191200Z', + '20360320T010100Z', + '20370320T065000Z', + '20380320T124000Z', + '20390320T182900Z', + '20400320T001800Z', + '20410320T060700Z', + '20420320T115600Z', + '20430320T174600Z', + '20440319T233500Z', + '20450320T052400Z', + '20460320T111300Z', + '20470320T170200Z', + '20480319T225200Z', + '20490320T044100Z', + '20500320T103000Z', + '20510320T161900Z', + '20520319T220800Z', + '20530320T035800Z', + '20540320T094700Z', + '20550320T153600Z', + '20560319T212500Z', + '20570320T031400Z', + '20580320T090400Z', + '20590320T145300Z', + '20600319T204200Z', + '20610320T023100Z', + '20620320T082000Z', + '20630320T141000Z', + '20640319T195900Z', + '20650320T014800Z', + '20660320T073700Z', + '20670320T132600Z', + '20680319T191600Z', + '20690320T010500Z', + '20700320T065400Z', + '20710320T124300Z', + '20720319T183200Z', + '20730320T002200Z', + '20740320T061100Z', + '20750320T120000Z', + '20760319T174900Z', + '20770319T233800Z', + '20780320T052800Z', + '20790320T111700Z', + '20800319T170600Z', + '20810319T225500Z', + '20820320T044400Z', + '20830320T103400Z', + '20840319T162300Z', + '20850319T221200Z', + '20860320T040100Z', + '20870320T095000Z', + '20880319T154000Z', + '20890319T212900Z', + '20900320T031800Z', + '20910320T090700Z', + '20920319T145600Z', + '20930319T204600Z', + '20940320T023500Z', + '20950320T082400Z', + '20960319T141300Z', + '20970319T200200Z', + '20980320T015200Z', + '20990320T074100Z', + ], + ], + 'Summer Solstice' => [ + 'start_date' => '2000-06-21', + 'recurrence_end' => '2100-01-01', + 'start_time' => '01:44:00', + 'timezone' => 'UTC', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => [ + '20000621T014400Z', + '20010621T073200Z', + '20020621T132000Z', + '20030621T190800Z', + '20040621T005600Z', + '20050621T064400Z', + '20060621T123200Z', + '20070621T182100Z', + '20080621T000900Z', + '20090621T055700Z', + '20100621T114500Z', + '20110621T173300Z', + '20120620T232100Z', + '20130621T050900Z', + '20140621T105700Z', + '20150621T164600Z', + '20160620T223400Z', + '20170621T042200Z', + '20180621T101000Z', + '20190621T155800Z', + '20200620T214600Z', + '20210621T033400Z', + '20220621T092300Z', + '20230621T151100Z', + '20240620T205900Z', + '20250621T024700Z', + '20260621T083500Z', + '20270621T142300Z', + '20280620T201100Z', + '20290621T015900Z', + '20300621T074800Z', + '20310621T133600Z', + '20320620T192400Z', + '20330621T011200Z', + '20340621T070000Z', + '20350621T124800Z', + '20360620T183600Z', + '20370621T002400Z', + '20380621T061300Z', + '20390621T120100Z', + '20400620T174900Z', + '20410620T233700Z', + '20420621T052500Z', + '20430621T111300Z', + '20440620T170100Z', + '20450620T224900Z', + '20460621T043700Z', + '20470621T102600Z', + '20480620T161400Z', + '20490620T220200Z', + '20500621T035000Z', + '20510621T093800Z', + '20520620T152600Z', + '20530620T211400Z', + '20540621T030200Z', + '20550621T085100Z', + '20560620T143900Z', + '20570620T202700Z', + '20580621T021500Z', + '20590621T080300Z', + '20600620T135100Z', + '20610620T193900Z', + '20620621T012700Z', + '20630621T071600Z', + '20640620T130400Z', + '20650620T185200Z', + '20660621T004000Z', + '20670621T062800Z', + '20680620T121600Z', + '20690620T180400Z', + '20700620T235200Z', + '20710621T054100Z', + '20720620T112900Z', + '20730620T171700Z', + '20740620T230500Z', + '20750621T045300Z', + '20760620T104100Z', + '20770620T162900Z', + '20780620T221700Z', + '20790621T040500Z', + '20800620T095400Z', + '20810620T154200Z', + '20820620T213000Z', + '20830621T031800Z', + '20840620T090600Z', + '20850620T145400Z', + '20860620T204200Z', + '20870621T023000Z', + '20880620T081900Z', + '20890620T140700Z', + '20900620T195500Z', + '20910621T014300Z', + '20920620T073100Z', + '20930620T131900Z', + '20940620T190700Z', + '20950621T005500Z', + '20960620T064300Z', + '20970620T123200Z', + '20980620T182000Z', + '20990621T000800Z', + ], + ], + 'Autumnal Equinox' => [ + 'start_date' => '2000-09-22', + 'recurrence_end' => '2100-01-01', + 'start_time' => '17:16:00', + 'timezone' => 'UTC', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => [ + '20000922T171600Z', + '20010922T230500Z', + '20020923T045400Z', + '20030923T104200Z', + '20040922T163100Z', + '20050922T222000Z', + '20060923T040800Z', + '20070923T095700Z', + '20080922T154600Z', + '20090922T213400Z', + '20100923T032300Z', + '20110923T091200Z', + '20120922T150100Z', + '20130922T204900Z', + '20140923T023800Z', + '20150923T082700Z', + '20160922T141500Z', + '20170922T200400Z', + '20180923T015300Z', + '20190923T074100Z', + '20200922T133000Z', + '20210922T191900Z', + '20220923T010700Z', + '20230923T065600Z', + '20240922T124500Z', + '20250922T183300Z', + '20260923T002200Z', + '20270923T061100Z', + '20280922T115900Z', + '20290922T174800Z', + '20300922T233700Z', + '20310923T052600Z', + '20320922T111400Z', + '20330922T170300Z', + '20340922T225200Z', + '20350923T044000Z', + '20360922T102900Z', + '20370922T161800Z', + '20380922T220600Z', + '20390923T035500Z', + '20400922T094400Z', + '20410922T153200Z', + '20420922T212100Z', + '20430923T031000Z', + '20440922T085800Z', + '20450922T144700Z', + '20460922T203600Z', + '20470923T022400Z', + '20480922T081300Z', + '20490922T140200Z', + '20500922T195000Z', + '20510923T013900Z', + '20520922T072800Z', + '20530922T131600Z', + '20540922T190500Z', + '20550923T005400Z', + '20560922T064200Z', + '20570922T123100Z', + '20580922T182000Z', + '20590923T000800Z', + '20600922T055700Z', + '20610922T114600Z', + '20620922T173400Z', + '20630922T232300Z', + '20640922T051200Z', + '20650922T110000Z', + '20660922T164900Z', + '20670922T223800Z', + '20680922T042600Z', + '20690922T101500Z', + '20700922T160400Z', + '20710922T215200Z', + '20720922T034100Z', + '20730922T093000Z', + '20740922T151800Z', + '20750922T210700Z', + '20760922T025600Z', + '20770922T084400Z', + '20780922T143300Z', + '20790922T202200Z', + '20800922T021000Z', + '20810922T075900Z', + '20820922T134800Z', + '20830922T193600Z', + '20840922T012500Z', + '20850922T071400Z', + '20860922T130200Z', + '20870922T185100Z', + '20880922T003900Z', + '20890922T062800Z', + '20900922T121700Z', + '20910922T180500Z', + '20920921T235400Z', + '20930922T054300Z', + '20940922T113100Z', + '20950922T172000Z', + '20960921T230900Z', + '20970922T045700Z', + '20980922T104600Z', + '20990922T163500Z', + ], + ], + 'Winter Solstice' => [ + 'start_date' => '2000-12-21', + 'recurrence_end' => '2100-01-01', + 'start_time' => '13:27:00', + 'timezone' => 'UTC', + 'duration' => 'PT1M', + 'rrule' => 'FREQ=YEARLY;COUNT=1', + 'rdates' => [ + '20001221T132700Z', + '20011221T191600Z', + '20021222T010600Z', + '20031222T065600Z', + '20041221T124600Z', + '20051221T183500Z', + '20061222T002500Z', + '20071222T061500Z', + '20081221T120400Z', + '20091221T175400Z', + '20101221T234400Z', + '20111222T053400Z', + '20121221T112300Z', + '20131221T171300Z', + '20141221T230300Z', + '20151222T045300Z', + '20161221T104200Z', + '20171221T163200Z', + '20181221T222200Z', + '20191222T041100Z', + '20201221T100100Z', + '20211221T155100Z', + '20221221T214100Z', + '20231222T033000Z', + '20241221T092000Z', + '20251221T151000Z', + '20261221T205900Z', + '20271222T024900Z', + '20281221T083900Z', + '20291221T142900Z', + '20301221T201800Z', + '20311222T020800Z', + '20321221T075800Z', + '20331221T134800Z', + '20341221T193700Z', + '20351222T012700Z', + '20361221T071700Z', + '20371221T130600Z', + '20381221T185600Z', + '20391222T004600Z', + '20401221T063600Z', + '20411221T122500Z', + '20421221T181500Z', + '20431222T000500Z', + '20441221T055400Z', + '20451221T114400Z', + '20461221T173400Z', + '20471221T232400Z', + '20481221T051300Z', + '20491221T110300Z', + '20501221T165300Z', + '20511221T224200Z', + '20521221T043200Z', + '20531221T102200Z', + '20541221T161200Z', + '20551221T220100Z', + '20561221T035100Z', + '20571221T094100Z', + '20581221T153000Z', + '20591221T212000Z', + '20601221T031000Z', + '20611221T090000Z', + '20621221T144900Z', + '20631221T203900Z', + '20641221T022900Z', + '20651221T081800Z', + '20661221T140800Z', + '20671221T195800Z', + '20681221T014700Z', + '20691221T073700Z', + '20701221T132700Z', + '20711221T191700Z', + '20721221T010600Z', + '20731221T065600Z', + '20741221T124600Z', + '20751221T183500Z', + '20761221T002500Z', + '20771221T061500Z', + '20781221T120500Z', + '20791221T175400Z', + '20801220T234400Z', + '20811221T053400Z', + '20821221T112300Z', + '20831221T171300Z', + '20841220T230300Z', + '20851221T045200Z', + '20861221T104200Z', + '20871221T163200Z', + '20881220T222200Z', + '20891221T041100Z', + '20901221T100100Z', + '20911221T155100Z', + '20921220T214000Z', + '20931221T033000Z', + '20941221T092000Z', + '20951221T150900Z', + '20961220T205900Z', + '20971221T024900Z', + '20981221T083900Z', + '20991221T142800Z', + ], + ], + ]; + + /**************** + * Public methods + ****************/ + + /** + * + */ + public function execute(): bool + { + $exists = count(Db::$db->list_tables(false, Config::$db_prefix . 'calendar_holidays')) > 0; + + if ($exists) { + if (!isset(User::$me)) { + User::load(); + } + + $request = Db::$db->query( + '', + 'SELECT title, GROUP_CONCAT(event_date) as rdates + FROM {db_prefix}calendar_holidays + GROUP BY title', + [], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + if (isset($this->known_holidays[$row['title']])) { + $holiday = &$this->known_holidays[$row['title']]; + + $holiday['type'] = 1; + $holiday['title'] = $holiday['title'] ?? $row['title']; + $holiday['allday'] = !isset($holiday['start_time']) || !isset($holiday['timezone']) || !in_array($holiday['timezone'], timezone_identifiers_list(\DateTimeZone::ALL_WITH_BC)); + $holiday['start'] = new Time($holiday['start_date'] . (!$holiday['allday'] ? ' ' . $holiday['start_time'] . ' ' . $holiday['timezone'] : '')); + $holiday['duration'] = new \DateInterval($holiday['duration'] ?? 'P1D'); + $holiday['recurrence_end'] = new Time($holiday['recurrence_end']); + unset($holiday['start_date'], $holiday['start_time'], $holiday['timezone']); + + $event = new Event(0, $this->known_holidays[$row['title']]); + } else { + $row['type'] = 1; + $row['allday'] = true; + $row['recurrence_end'] = new Time('9999-12-31'); + $row['duration'] = new \DateInterval('P1D'); + $row['rdates'] = explode(',', $row['rdates']); + + $row['start'] = array_shift($row['rdates']); + + if (preg_match('/^100\d-/', $row['start'])) { + $row['start'] = new Time(preg_replace('/^100\d-/', '2000-', $row['start'])); + $row['rrule'] = 'FREQ=YEARLY'; + } else { + $row['start'] = new Time($row['start']); + $row['rrule'] = 'FREQ=DAILY;COUNT=1'; + } + + $event = new Event(0, $row); + } + + $event->save(); + } + + Db::$db->free_result($request); + + Db::$db->drop_table('{db_prefix}calendar_holidays'); + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php b/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php new file mode 100644 index 00000000000..b3b672b60e4 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/LanguageDirectory.php @@ -0,0 +1,148 @@ + 'en_US']; + $members = []; + + // Setup the case statement. + foreach (Lang::LANG_TO_LOCALE as $lang => $locale) { + $statements[] = ' WHEN lngfile = {string:lang_' . $lang . '} THEN {string:locale_' . $locale . '}'; + $args['lang_' . $lang] = $lang; + $args['locale_' . $locale] = $locale; + $langs[] = $lang; + } + + $is_done = false; + + while (!$is_done) { + // @@ TODO: Handle sub steps. + $this->handleTimeout(); + + // Skip errors here so we don't croak if the columns don't exist... + $request = Db::$db->query( + '', + 'SELECT id_member + FROM {db_prefix}members + WHERE lngfile IN ({array_string:possible_languages}) + ORDER BY id_member + LIMIT {int:limit}', + [ + 'limit' => $this->limit, + 'possible_languages' => $langs, + ], + ); + + if (Db::$db->num_rows($request) == 0) { + $is_done = true; + break; + } + + while ($row = Db::$db->fetch_assoc($request)) { + $members[] = $row['id_member']; + } + + Db::$db->free_result($request); + + + // Nobody to convert, woohoo! + if (empty($members)) { + $is_done = true; + break; + } + + $args['search_members'] = $members; + + + Db::$db->query( + '', + 'UPDATE {db_prefix}members + SET lngfile = CASE + ' . implode(' ', $statements) . ' + ELSE {string:defaultLang} END + WHERE id_member IN ({array_int:search_members})', + $args, + ); + + Maintenance::setCurrentStart(); + } + + // Rename the privacy policy records. + foreach (Config::$modSettings as $variable => $value) { + if (is_int($variable) || !str_starts_with($variable, 'policy_')) { + continue; + } + + if (str_starts_with($variable, 'policy_updated_')) { + $locale = Lang::getLocaleFromLanguageName(substr($variable, 15)); + $new_variable = isset($locale) ? 'policy_updated_' . $locale : $variable; + } else { + $locale = 'policy_' . Lang::getLocaleFromLanguageName(substr($variable, 7)); + $new_variable = isset($locale) ? 'policy_' . $locale : $variable; + } + + if ($variable !== $new_variable) { + Config::updateModSettings([ + $new_variable => $value, + $variable => null, + ]); + + unset($new_variable); + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/MailType.php b/Sources/Maintenance/Migration/v3_0/MailType.php new file mode 100644 index 00000000000..604bda94e1c --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/MailType.php @@ -0,0 +1,68 @@ +query( + '', + 'UPDATE {db_prefix}settings + SET value = + CASE + WHEN value = 0 + THEN {string:SendMail} + WHEN value = 1 + THEN {string:SMTP} + WHEN value = 2 + THEN {string:SMTPTLS} + ELSE + value + END + WHERE variable = {string:mail_type} + AND value IN (0,1,2)', + [ + 'SendMail' => 'SendMail', + 'SMTP' => 'SMTP', + 'SMTPTLS' => 'SMTPTLS', + 'mail_type' => 'mail_type', + 'int_values' => [0, 1, 2], + ], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/MessageVersion.php b/Sources/Maintenance/Migration/v3_0/MessageVersion.php new file mode 100644 index 00000000000..0d3ed4bd7a8 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/MessageVersion.php @@ -0,0 +1,68 @@ +getCurrentStructure(); + + if (!isset($existing_structure['columns']['version'])) { + foreach ($table->columns as $column) { + if ($column->name === 'version') { + $table->addColumn($column); + break; + } + } + } + + $this->handleTimeout(); + + $table = new \SMF\Db\Schema\v3_0\PersonalMessages(); + $existing_structure = $table->getCurrentStructure(); + + if (!isset($existing_structure['columns']['version'])) { + foreach ($table->columns as $column) { + if ($column->name === 'version') { + $table->addColumn($column); + break; + } + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/PackageVersion.php b/Sources/Maintenance/Migration/v3_0/PackageVersion.php new file mode 100644 index 00000000000..dbcc3a58f94 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/PackageVersion.php @@ -0,0 +1,54 @@ +getCurrentStructure(); + + if (!isset($existing_structure['columns']['smf_version'])) { + foreach ($table->columns as $column) { + if ($column->name === 'smf_version') { + $table->addColumn($column); + break; + } + } + } + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/RecurringEvents.php b/Sources/Maintenance/Migration/v3_0/RecurringEvents.php new file mode 100644 index 00000000000..637ee96481c --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/RecurringEvents.php @@ -0,0 +1,138 @@ +getCurrentStructure(); + + foreach ($table->columns as $column) { + if (!isset($existing_structure['columns'][$column->name])) { + $table->addColumn($column); + } + + $this->handleTimeout(); + } + + if (Db::$db->title === MYSQL_TITLE) { + Db::$db->query( + '', + 'ALTER TABLE {db_prefix}calendar + MODIFY COLUMN start_date DATE AFTER id_member', + [], + ); + + Db::$db->query( + '', + 'ALTER TABLE {db_prefix}calendar + MODIFY COLUMN end_date DATE AFTER start_date', + [], + ); + + } + + $updates = []; + + $request = Db::$db->query( + '', + 'SELECT id_event, start_date, end_date, start_time, end_time, timezone + FROM {db_prefix}calendar', + [], + ); + + while ($row = Db::$db->fetch_assoc($request)) { + $row = array_diff($row, array_filter($row, 'is_null')); + + $allday = ( + !isset($row['start_time']) + || !isset($row['end_time']) + || !isset($row['timezone']) + || !in_array($row['timezone'], timezone_identifiers_list(\DateTimeZone::ALL_WITH_BC)) + ); + + $start = new \DateTime($row['start_date'] . (!$allday ? ' ' . $row['start_time'] . ' ' . $row['timezone'] : '')); + $end = new \DateTime($row['end_date'] . (!$allday ? ' ' . $row['end_time'] . ' ' . $row['timezone'] : '')); + + if ($allday) { + $end->modify('+1 day'); + } + + $duration = date_diff($start, $end); + + $format = ''; + + foreach (['y', 'm', 'd', 'h', 'i', 's'] as $part) { + if ($part === 'h') { + $format .= 'T'; + } + + if (!empty($duration->{$part})) { + $format .= '%' . $part . ($part === 'i' ? 'M' : strtoupper($part)); + } + } + $format = rtrim('P' . $format, 'PT'); + + // TODO: Fix it right. + if ((int) $end->format('Y') > 9999) { + $end->setDate(9999, (int) $end->format('m'), (int) $end->format('d')); + } + + $updates[$row['id_event']] = [ + 'id_event' => $row['id_event'], + 'duration' => $duration->format($format), + 'end_date' => $end->format('Y-m-d'), + 'rrule' => 'FREQ=YEARLY;COUNT=1', + ]; + } + Db::$db->free_result($request); + + foreach ($updates as $id_event => $changes) { + Db::$db->query( + '', + 'UPDATE {db_prefix}calendar + SET duration = {string:duration}, end_date = {date:end_date}, rrule = {string:rrule} + WHERE id_event = {int:id_event}', + $changes, + ); + } + + Db::$db->remove_column('{db_prefix}calendar', 'end_time'); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/RemoveCookieTime.php b/Sources/Maintenance/Migration/v3_0/RemoveCookieTime.php new file mode 100644 index 00000000000..379d9b96563 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/RemoveCookieTime.php @@ -0,0 +1,45 @@ + null]); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/SearchResultsPrimaryKey.php b/Sources/Maintenance/Migration/v3_0/SearchResultsPrimaryKey.php new file mode 100644 index 00000000000..c01759a1d59 --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/SearchResultsPrimaryKey.php @@ -0,0 +1,55 @@ +query( + '', + 'ALTER TABLE {db_prefix}log_search_results DROP PRIMARY KEY', + [], + ); + + Db::$db->query( + '', + 'ALTER TABLE {db_prefix}log_search_results ADD PRIMARY KEY (id_search, id_topic, id_msg)', + [], + ); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/SpoofDetector.php b/Sources/Maintenance/Migration/v3_0/SpoofDetector.php new file mode 100644 index 00000000000..977db3f96ee --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/SpoofDetector.php @@ -0,0 +1,67 @@ +getCurrentStructure(); + + // Add the spoofdetector_name column. + foreach ($table->columns as $column) { + if (!isset($existing_structure['columns'][$column->name])) { + $table->addColumn($column); + + $this->handleTimeout(); + } + } + + // Add indexes for the spoofdetector_name column. + foreach ($table->indexes as $index) { + if (!isset($existing_structure['indexes'][$index->name])) { + $table->addIndex($index); + + $this->handleTimeout(); + } + } + + // Add the new "spoofdetector_censor" setting + Config::updateModSettings(['spoofdetector_censor' => 1]); + + return true; + } +} diff --git a/Sources/Maintenance/Migration/v3_0/index.php b/Sources/Maintenance/Migration/v3_0/index.php new file mode 100644 index 00000000000..ee0549de33f --- /dev/null +++ b/Sources/Maintenance/Migration/v3_0/index.php @@ -0,0 +1,8 @@ + 'v2_1', + '2.1.99' => 'v3_0', + ]; + + /** + * Migration substeps to perform, listed in order. + */ + public const MIGRATIONS = [ + // Migration steps for 2.0 -> 2.1 + 'v2_1' => [ + Migration\v2_1\PostgreSQLSequences::class, + Migration\v2_1\PostgreSQLFindInSet::class, + Migration\v2_1\SettingsUpdate::class, + Migration\v2_1\RemoveKarma::class, + 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, + Migration\v2_1\CreateLogGroupRequests::class, + Migration\v2_1\PackageManager::class, + Migration\v2_1\ValidationServers::class, + Migration\v2_1\SessionIDs::class, + Migration\v2_1\MovedTopics::class, + Migration\v2_1\ScheduledTasks::class, + Migration\v2_1\CreateBackgroundTasks::class, + Migration\v2_1\CategoryDescrptions::class, + Migration\v2_1\CreateAlerts::class, + Migration\v2_1\AutoNotify::class, + Migration\v2_1\AlertsWatchedTopics::class, + Migration\v2_1\AlertsWatchedBoards::class, + Migration\v2_1\AlertsObsolete::class, + Migration\v2_1\TopicUnwatch::class, + Migration\v2_1\MailQueue::class, + Migration\v2_1\MembergroupIcon::class, + Migration\v2_1\ThemeSettings::class, + Migration\v2_1\CustomFieldsPart1::class, + Migration\v2_1\CustomFieldsPart2::class, + Migration\v2_1\CustomFieldsPart3::class, + Migration\v2_1\UserDrafts::class, + Migration\v2_1\Likes::class, + Migration\v2_1\Mentions::class, + Migration\v2_1\ModeratorGroups::class, + Migration\v2_1\AdminInfoFiles::class, + Migration\v2_1\VerificationQuestions::class, + Migration\v2_1\Permissions::class, + Migration\v2_1\PersonalMessageLabels::class, + Migration\v2_1\MessagesModifiedReason::class, + Migration\v2_1\MembersTimezone::class, + Migration\v2_1\MembersHideEmail::class, + Migration\v2_1\LogReportedCommentsEmail::class, + Migration\v2_1\MembersOpenID::class, + Migration\v2_1\OpenID::class, + Migration\v2_1\LogSpiderHitsURL::class, + Migration\v2_1\LogOnlineURL::class, + Migration\v2_1\MembersTfaSecret::class, + Migration\v2_1\MembersTfaBackup::class, + Migration\v2_1\PostgreSQLUnlogged::class, + Migration\v2_1\PostgreSQLIPv6Helper::class, + Migration\v2_1\Ipv6BanItem::class, + Migration\v2_1\Ipv6LogAction::class, + Migration\v2_1\Ipv6LogBanned::class, + Migration\v2_1\Ipv6LogErrors::class, + Migration\v2_1\Ipv6MembersIP::class, + Migration\v2_1\Ipv6MembersIP2::class, + Migration\v2_1\Ipv6Messages::class, + Migration\v2_1\Ipv6LogFloodControl::class, + Migration\v2_1\Ipv6LogOnline::class, + Migration\v2_1\Ipv6LogReportedComments::class, + Migration\v2_1\Ipv6MemberLogins::class, + Migration\v2_1\PersonalMessageNotification::class, + Migration\v2_1\CalendarEvents::class, + Migration\v2_1\IdxMessages::class, + Migration\v2_1\IdxTopics::class, + Migration\v2_1\IdxMembers::class, + Migration\v2_1\IdxLogActivity::class, + Migration\v2_1\IdxLogActions::class, + Migration\v2_1\IdxLogSubscribed::class, + Migration\v2_1\IdxLogPackages::class, + Migration\v2_1\IdxScheduledTasks::class, + Migration\v2_1\IdxBoards::class, + Migration\v2_1\IdxLogComments::class, + Migration\v2_1\IdxAttachments::class, + Migration\v2_1\MysqlLegacyData::class, + Migration\v2_1\Smileys::class, + Migration\v2_1\LogErrorsBacktrace::class, + Migration\v2_1\BoardPermissionsView::class, + Migration\v2_1\PostgresqlSchemaDiff::class, + Migration\v2_1\PostgreSqlTime::class, + Migration\v2_1\CalendarUpdates::class, + Migration\v2_1\MysqlModFixes::class, + ], + // Migration steps for 2.1 -> 3.0 + 'v3_0' => [ + Migration\v3_0\ConvertToInnoDb::class, + Migration\v3_0\LanguageDirectory::class, + Migration\v3_0\ErrorLogSession::class, + Migration\v3_0\MessageVersion::class, + Migration\v3_0\RecurringEvents::class, + Migration\v3_0\HolidaysToEvents::class, + Migration\v3_0\EventUids::class, + Migration\v3_0\SpoofDetector::class, + Migration\v3_0\SearchResultsPrimaryKey::class, + Migration\v3_0\MailType::class, + Migration\v3_0\RemoveCookieTime::class, + ], + ]; + + /** + * Cleanups that do not require database maintenance tasks. + */ + public const CLEANUPS = [ + // Cleanup steps for 2.0 -> 2.1 + 'v2_1' => [ + Cleanup\v2_1\OldFiles::class, + ], + // Cleanup steps for 2.1 -> 3.0 + 'v3_0' => [ + Cleanup\v3_0\TasksDirCase::class, + Cleanup\v3_0\OldFiles::class, + ], + ]; + + /******************* + * Public properties + *******************/ + + /** + * @var bool + * + * When true, we can continue, when false the continue button is removed. + */ + public bool $continue = true; + + /** + * @var bool + * + * When true, we can skip this step, otherwise false and no skip option. + */ + public bool $skip = false; + + /** + * @var string + * + * The name of the script this tool uses. This is used by various actions and links. + */ + public string $script_name = 'upgrade.php'; + + /** + * @var int + * + * The time we last updated the upgrade, populated by upgrade itself. + */ + public int $time_updated = 0; + + /** + * @var bool + * + * Debugging the upgrade. + */ + public bool $debug = false; + + /** + * @var array + * + * User performing upgrade. + */ + public array $user = [ + 'id' => 0, + 'name' => 'Guest', + 'maint' => 0, + ]; + + /** + * @var array + * + * Migrations we skipped. + */ + public array $skipped_migrations = []; + + /** + * @var int + * + * The amount of seconds allowed between logins. + * + * If the first user to login is inactive for this amount of seconds, + * a second login is allowed. + */ + public int $inactive_timeout = 10; + + /********************* + * Internal properties + *********************/ + + /** + * @var array + * + * Upgrade data stored in our Settings.php as we progress through the upgrade. + */ + protected array $upgradeData = []; + + /** + * @var int + * + * The time we started the upgrade, populated by upgrade itself. + */ + protected int $time_started = 0; + + /** + * @var string + * + * English is the default language. + */ + protected string $default_language = 'en_US'; + + /** + * @var array + * + * Maps old cache accelerator settings to new ones. + */ + protected array $cache_migration = [ + 'smf' => 'FileBase', + 'apc' => 'FileBase', + 'apcu' => 'Apcu', + 'memcache' => 'MemcacheImplementation', + 'memcached' => 'MemcachedImplementation', + 'postgres' => 'Postgres', + 'sqlite' => 'Sqlite', + 'xcache' => 'FileBase', + 'zend' => 'Zend', + ]; + + /** + * @var string + * + * SMF Version we started on. + */ + protected string $start_smf_version = ''; + + /** + * @var null|string + * + * Custom page title, otherwise we send the defaults. + */ + private ?string $page_title = null; + + /** + * @var bool + * + * Additional safety measures for timeout protection are done for large forums. + */ + private bool $is_large_forum = false; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + */ + public function __construct() + { + Maintenance::$languages = $this->detectLanguages(['General', 'Maintenance']); + + if (empty(Maintenance::$languages)) { + if (!Sapi::isCLI()) { + Template::missingLanguages(); + } + + throw new \Exception('This script was unable to find this tools\'s language file or files.'); + } else { + $requested_lang = Maintenance::getRequestedLanguage(); + + // Ensure SMF\Lang knows the path to the language directory. + Lang::addDirs(Config::$languagesdir); + + // And now load the language file. + Lang::load('General+Maintenance+Errors', $requested_lang); + + // Assume that the admin likes that language. + if ($requested_lang !== $this->default_language) { + Config::$language = $requested_lang; + } + } + + // Secure some resources. + try { + if (Config::$db_type == MYSQL_TITLE) { + @ini_set('mysql.connect_timeout', '-1'); + } + + @ini_set('default_socket_timeout', '900'); + Sapi::setTimeLimit(600); + Sapi::setMemoryLimit('512M'); + + // Better to upgrade cleanly and fall apart than to screw everything up if things take too long. + ignore_user_abort(true); + } catch (\Throwable $e) { + } + + // SMF\Config, and SMF\Utils. + Config::load(); + Utils::load(); + Session::load(); + + $this->prepareUpgrade(); + + // If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars. + if (class_exists('SMF\\QueryString')) { + QueryString::cleanRequest(); + } + + // Is this a large (and old) forum? We may do special logic then. + Maintenance::$context['is_large_forum'] = $this->is_large_forum = ( + version_compare( + str_replace(' ', '.', strtolower($this->start_smf_version)), + '1.1.rc.1', + '<=', + ) + && !empty(Config::$modSettings['totalMessages']) + && Config::$modSettings['totalMessages'] > 75000 + ); + + // Should we check that they are logged in? + if (Maintenance::getCurrentSubStep() > 0 && !isset($_SESSION['is_logged'])) { + Maintenance::setCurrentSubStep(0); + } + } + + /** + * Get the script name + * + * @return string Page Title + */ + public function getScriptName(): string + { + return Lang::$txt['smf_upgrade']; + } + + /** + * Gets our page title to be sent to the template. + * + * Selection is in the following order: + * 1. A custom page title. + * 2. Step has provided a title. + * 3. Default for the installer tool. + * + * @return string Page Title + */ + public function getPageTitle(): string + { + return $this->page_title ?? $this->getSteps()[Maintenance::getCurrentStep()]->getTitle() ?? $this->getScriptName(); + } + + /** + * If a tool does not contain steps, this should be false, true otherwise. + * + * @return bool Whether or not a tool has steps. + */ + public function hasSteps(): bool + { + return true; + } + + /** + * Upgrade Steps + * + * @return \SMF\Maintenance\Step[] + */ + public function getSteps(): array + { + return [ + new Step( + id: 1, + name: Lang::$txt['upgrade_step_login'], + function: 'welcomeLogin', + template: 'welcomeLogin', + progress: 2, + ), + new Step( + id: 2, + name: Lang::$txt['upgrade_step_options'], + function: 'upgradeOptions', + template: 'upgradeOptions', + progress: 3, + ), + new Step( + id: 3, + name: Lang::$txt['upgrade_step_backup'], + function: 'backupDatabase', + template: 'backupDatabase', + progress: 10, + ), + new Step( + id: 4, + name: Lang::$txt['upgrade_step_migration'], + function: 'migrations', + template: 'migrations', + progress: 45, + ), + new Utf8ConverterStep( + // Note: Utf8ConverterStep does not take a function argument. + id: 5, + name: Lang::$txt['upgrade_step_convertutf'], + template: 'convertUtf8', + progress: 30, + ), + new Step( + id: 6, + name: Lang::$txt['upgrade_step_cleanup'], + function: 'cleanup', + template: 'cleanup', + progress: 10, + ), + new Step( + id: 7, + name: Lang::$txt['upgrade_step_finalize'], + function: 'finalize', + template: 'finalize', + progress: 0, + ), + ]; + } + + /** + * Gets the title for the step we are performing + * + * @return string + */ + public function getStepTitle(): string + { + return $this->getSteps()[Maintenance::getCurrentStep()]->getName(); + } + + /** + * Welcome action. + * + * @return bool True if we can continue, false otherwise. + */ + public function welcomeLogin(): bool + { + if (!empty($_SESSION['is_logged'])) { + return true; + } + + // Needs to at least meet our minium version. + if (version_compare(Maintenance::PHP_MIN_VERSION, PHP_VERSION, '>=')) { + Maintenance::$fatal_error = Lang::$txt['error_php_too_low']; + + return false; + } + + // Form submitted, but no javascript support. + if (isset($_POST['contbutt']) && !isset($_POST['js_support'])) { + Maintenance::$fatal_error = Lang::$txt['error_no_javascript']; + + return false; + } + + // Check for some key files - one template, one language, and a new and an old source file. + $check = ( + @file_exists(Maintenance::$theme_dir . '/index.template.php') + && @file_exists(Config::$sourcedir . '/QueryString.php') + && @file_exists(Config::$sourcedir . '/Db/APIs/' . Db::getClass(Config::$db_type) . '.php') + && @file_exists(Config::$sourcedir . '/Maintenance/Migration/v3_0/MessageVersion.php') + ); + + // Need legacy scripts? + if ( + !isset(Config::$modSettings['smfVersion']) + || version_compare( + str_replace(' ', '.', strtolower(Config::$modSettings['smfVersion'])), + substr(SMF_VERSION, 0, strpos(SMF_VERSION, '.') + 1 + strspn(SMF_VERSION, '1234567890', strpos(SMF_VERSION, '.') + 1)) . '.dev.0', + '<', + ) + ) { + $check &= @file_exists(Config::$sourcedir . '/Maintenance/Migration/v2_1/SettingsUpdate.php'); + } + + if (!$check) { + // Don't tell them what files exactly because it's a spot check - just like teachers don't tell which problems they are spot checking, that's dumb. + Maintenance::$fatal_error = Lang::$txt['error_upgrade_files_missing']; + + return false; + } + + Db::load(); + + if ( + version_compare( + preg_replace('~^\D*|\-.+?$~', '', Db::$db->get_version()), + Db::$db->getMinimumVersion(), + '<', + ) + ) { + Maintenance::$fatal_error = Lang::getTxt('error_db_too_low', ['name' => Db::$db->getTitle()]); + + return false; + } + + // Check that we have database permissions. + // CREATE + $create = Db::$db->create_table('{db_prefix}priv_check', [['name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true]], [['columns' => ['id_test'], 'type' => 'primary']], [], 'overwrite'); + + // ALTER + $alter = Db::$db->add_column('{db_prefix}priv_check', ['name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => '']); + + // DROP + $drop = Db::$db->drop_table('{db_prefix}priv_check'); + + // Sorry... we need CREATE, ALTER and DROP + if (!$create || !$alter || !$drop) { + Maintenance::$fatal_error = Lang::getTxt('error_db_privileges', ['name' => Config::$db_type]); + + return false; + } + + // Do a quick version spot check. + $temp = substr(@implode('', @file(Config::$boarddir . '/index.php')), 0, 4096); + preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match); + + if (empty($match[1]) || (trim($match[1]) != SMF_VERSION)) { + Maintenance::$fatal_error = Lang::$txt['error_upgrade_old_files']; + + return false; + } + + // What absolutely needs to be writable? + $writable_files = [ + SMF_SETTINGS_FILE, + SMF_SETTINGS_BACKUP_FILE, + ]; + + // Try to make all the files writable. If we cannot, we will display a chmod page to attempt this with additional permissions. + if (!$this->makeFilesWritable($writable_files)) { + Maintenance::$context['chmod']['files'] = $writable_files; + + return false; + } + + // Do we need to add this setting? + $need_settings_update = empty(Config::$modSettings['custom_avatar_dir']); + + $custom_av_dir = !empty(Config::$modSettings['custom_avatar_dir']) ? Config::$modSettings['custom_avatar_dir'] : Config::$boarddir . '/custom_avatar'; + $custom_av_url = !empty(Config::$modSettings['custom_avatar_url']) ? Config::$modSettings['custom_avatar_url'] : Config::$boardurl . '/custom_avatar'; + + $this->quickFileWritable($custom_av_dir); + + // Are we good now? + if (!is_writable($custom_av_dir)) { + Maintenance::$fatal_error = Lang::getTxt('error_dir_not_writable', ['dir' => $custom_av_dir]); + + return false; + } + + if ($need_settings_update) { + Config::updateModSettings(['custom_avatar_dir' => $custom_av_dir]); + Config::updateModSettings(['custom_avatar_url' => $custom_av_url]); + } + + // Check the cache directory. + $cache_dir_temp = empty(Config::$cachedir) ? Config::$boarddir . '/cache' : Config::$cachedir; + + if (!file_exists($cache_dir_temp)) { + @mkdir($cache_dir_temp); + } + + if (!file_exists($cache_dir_temp)) { + Maintenance::$fatal_error = Lang::$txt['error_cache_not_found']; + + return false; + } + + $this->quickFileWritable($cache_dir_temp . '/db_last_error.php'); + + if (!is_writable($cache_dir_temp . '/db_last_error.php')) { + Maintenance::$fatal_error = Lang::getTxt('error_dir_not_writable', ['dir' => $cache_dir_temp]); + + return false; + } + + // Do we need to update our Settings file with the new language locale? + $current_language = Config::$language; + $new_locale = Lang::getLocaleFromLanguageName($current_language); + + if ($new_locale !== null && $new_locale != Config::$language) { + Config::updateSettingsFile(['language' => $new_locale]); + } + + if (empty(Config::$languagesdir)) { + Config::updateSettingsFile(['languagesdir' => Config::$boarddir . '/Languages']); + } + + // Check agreement.txt. It may not exist, in which case $boarddir must be writable. + if ( + isset(Config::$modSettings['agreement']) + && ( + !is_writable(Config::$languagesdir) + || file_exists(Config::$languagesdir . '/' . $this->default_language . '/agreement.txt') + ) + && !is_writable(Config::$languagesdir . '/' . $this->default_language . '/agreement.txt') + ) { + Maintenance::$fatal_error = Lang::$txt['error_agreement_not_writable']; + + return false; + } + + // Confirm mbstring is loaded... + if (!extension_loaded('mbstring')) { + Maintenance::$errors[] = Lang::$txt['install_no_mbstring']; + } + + // Confirm fileinfo is loaded... + if (!extension_loaded('fileinfo')) { + Maintenance::$errors[] = Lang::$txt['install_no_fileinfo']; + } + + // Check for https stream support. + $supported_streams = stream_get_wrappers(); + + if (!in_array('https', $supported_streams)) { + Maintenance::$warnings[] = Lang::$txt['install_no_https']; + } + + // First, check the avatar directory... + // Note it wasn't specified in YabbSE, but there was no smfVersion either. + if (!empty(Config::$modSettings['smfVersion']) && !is_dir(Config::$modSettings['avatar_directory'])) { + Maintenance::$warnings[] = Lang::$txt['warning_av_missing']; + } + + // Next, check the custom avatar directory... Note this is optional in 2.0. + if (!empty(Config::$modSettings['custom_avatar_dir']) && !is_dir(Config::$modSettings['custom_avatar_dir'])) { + Maintenance::$warnings[] = Lang::$txt['warning_custom_av_missing']; + } + + // Ensure we have a valid attachment directory. + if ($this->attachmentDirectoryIsValid()) { + Maintenance::$warnings[] = Lang::$txt['warning_att_dir_missing']; + } + + if (Sapi::isCLI()) { + return true; + } + + // Attempting to login. + if ( + empty(Maintenance::$errors) + && isset($_POST['contbutt']) + && ( + !empty($_POST['db_pass']) + || ( + !empty($_POST['user']) + && !empty($_POST['passwrd']) + ) + ) + ) { + if (!SecurityToken::validate('login', 'post', false)) { + Maintenance::$errors[] = Lang::$txt['token_verify_fail']; + Maintenance::$context += SecurityToken::create('login'); + + return false; + } + + // Let them login, if they know the database password. + if ( + !empty($_POST['db_pass']) + && Maintenance::loginWithDatabasePassword((string) $_POST['db_pass']) + ) { + $this->user = [ + 'id' => 0, + 'name' => 'Database Admin', + ]; + + $_SESSION['is_logged'] = true; + + return true; + } + + $use_old_hashing = version_compare(str_replace(' ', '.', strtolower(Config::$modSettings['smfVersion'])), '2.1.dev.0', '<'); + + if (($id = Maintenance::loginAdmin((string) $_POST['user'], (string) $_POST['passwrd'], $use_old_hashing)) > 0) { + $this->user = [ + 'id' => $id, + 'name' => (string) $_POST['user'], + ]; + + $_SESSION['is_logged'] = true; + + return true; + } + } elseif (empty(Maintenance::$errors)) { + Maintenance::$context['continue'] = true; + } + + Maintenance::$context += SecurityToken::create('login'); + + return false; + } + + /** + * Allow the administrator to select options for the upgrade. + * + * @return bool True if we are continuing, false we are presenting upgrade options. + */ + public function upgradeOptions(): bool + { + $member_columns = Db::$db->list_columns('{db_prefix}members'); + + Maintenance::$context['karma_installed'] = [ + 'good' => in_array('karma_good', $member_columns), + 'bad' => in_array('karma_bad', $member_columns), + ]; + + unset($member_columns); + + // Figure out a couple of recommendations. + Maintenance::$context['backup_recommended'] = $this->backupRecommended(); + + Maintenance::$context['migrate_settings_recommended'] = ( + empty(Config::$modSettings['smfVersion']) + || version_compare( + str_replace(' ', '.', strtolower(Config::$modSettings['smfVersion'])), + preg_replace('/^(\d+\.\d+).*/', '$1.dev.0', SMF_VERSION), + '<', + ) + ); + + Maintenance::$context['db_prefix'] = Config::$db_prefix; + + Maintenance::$context['message_title'] = htmlspecialchars(Config::$mtitle); + Maintenance::$context['message_body'] = htmlspecialchars(Config::$mmessage); + + Maintenance::$context['attachment_conversion'] = isset(Config::$modSettings['attachments_21_done']); + + Maintenance::$context['sm_stats_configured'] = !empty(Config::$modSettings['allow_sm_stats']) || !empty(Config::$modSettings['enable_sm_stats']); + + // If we've not submitted then we're done. + if (!Sapi::isCLI() && empty($_POST['upcont'])) { + Maintenance::$context['continue'] = true; + + return false; + } + + Db::load(); + Db::$db->setSqlMode('strict'); + + $file_settings = []; + $db_settings = []; + + // Firstly, if they're enabling SM stat collection just do it. + $this->toggleSmStats($db_settings); + + // Deleting old karma stuff? + $_SESSION['delete_karma'] = !empty($_POST['delete_karma']); + + // Emptying the error log? + $_SESSION['empty_error'] = !empty($_POST['empty_error']); + + // Reprocessing attachments? + $_SESSION['reprocess_attachments'] = !empty($_POST['reprocess_attachments']); + + // Add proxy settings. + if (!isset(Config::$image_proxy_secret) || Config::$image_proxy_secret == 'smfisawesome') { + $file_settings['image_proxy_secret'] = bin2hex(random_bytes(10)); + } + + if (!isset(Config::$image_proxy_maxsize)) { + $file_settings['image_proxy_maxsize'] = 5190; + } + + if (!isset(Config::$image_proxy_enabled)) { + $file_settings['image_proxy_enabled'] = false; + } + + if (stripos(Config::$boardurl, 'https://') !== false && !isset(Config::$modSettings['force_ssl'])) { + $db_settings['force_ssl'] = 1; + } + + // If we're overriding the language follow it through. + // @todo This gets overwritten below. + if (Maintenance::getRequestedLanguage() != Config::$language) { + $file_settings['language'] = Maintenance::getRequestedLanguage(); + } + + // Put the forum into maintenance mode. + if (!empty($_POST['maint'])) { + $file_settings['maintenance'] = 2; + + // Remember what it was... + $this->user['maint'] = Config::$maintenance; + + if (!empty($_POST['maintitle'])) { + $file_settings['mtitle'] = $_POST['maintitle']; + $file_settings['mmessage'] = $_POST['mainmessage']; + } else { + $file_settings['mtitle'] = Lang::$txt['mtitle']; + $file_settings['mmessage'] = Lang::$txt['mmessage']; + } + } + + // Fix some old paths. + if (substr(Config::$boarddir, 0, 1) == '.') { + $file_settings['boarddir'] = $this->fixRelativePath(Config::$boarddir); + } + + if (substr(Config::$sourcedir, 0, 1) == '.') { + $file_settings['sourcedir'] = $this->fixRelativePath(Config::$sourcedir); + } + + if (empty(Config::$cachedir) || substr(Config::$cachedir, 0, 1) == '.') { + $file_settings['cachedir'] = $this->fixRelativePath(Config::$boarddir) . '/cache'; + } + + // Maybe we haven't had this option yet? + if (empty(Config::$packagesdir)) { + $file_settings['packagesdir'] = $this->fixRelativePath(Config::$boarddir) . '/Packages'; + } + + // Languages have moved! + if (empty(Config::$languagesdir)) { + $file_settings['languagesdir'] = $this->fixRelativePath(Config::$boarddir) . '/Languages'; + } + + // Make sure we fix the language as well. + if (stristr(Config::$language, '-utf8')) { + $file_settings['language'] = str_ireplace('-utf8', '', Config::$language); + } + + // Maybe we are on the old language naming? User settings will get fixed up later. + if (isset(Lang::LANG_TO_LOCALE[Config::$language])) { + $file_settings['language'] = Lang::LANG_TO_LOCALE[Config::$language]; + } + + // Migrate cache settings. + // Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled. + if (!isset(Config::$cache_enable)) { + $file_settings += [ + 'cache_accelerator' => $this->cache_migration[Config::$cache_accelerator] ?? Config::$cache_accelerator, + 'cache_enable' => !empty(Config::$modSettings['cache_enable']) ? Config::$modSettings['cache_enable'] : 0, + 'cache_memcached' => !empty(Config::$modSettings['cache_memcached']) ? Config::$modSettings['cache_memcached'] : '', + ]; + } + + // 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); + + $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; + } + } + + // 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()) { + $file_settings['db_port'] = 0; + } + + // Update the database with new settings. + Config::updateModSettings($db_settings); + + // Update Settings.php with the new settings, and rebuild if they selected that option. + $res = Config::updateSettingsFile($file_settings, false, !empty($_POST['migrateSettings'])); + + if (Sapi::isCLI() && !$res) { + die('FAILURE: Could not update ' . basename(SMF_SETTINGS_FILE) . "\n"); + } + + // Empty our error log. + if (!empty($_POST['empty_error'])) { + Db::$db->query( + 'truncate_table', + ' + TRUNCATE {db_prefix}log_errors', + [], + ); + } + + // Are we doing debug? + if (isset($_POST['debug'])) { + $this->debug = true; + } + + // If we've got here then let's proceed to the next step! + return true; + } + + /** + * Backup our database. + * + * @return bool True if we are done backing up or skipped. False otherwise. + */ + public function backupDatabase(): bool + { + // Done it already - js wise? + if (!empty($_POST['backup_done'])) { + return true; + } + + // If we're not backing up then jump one. + if (!Maintenance::isJson() && empty($_POST['backup'])) { + return true; + } + + Db::load(); + Db::$db->setSqlMode('default'); + + // Get all the table names. + $filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', Config::$db_prefix, $match) != 0 ? $match[2] : Config::$db_prefix) . '%'; + + $db = preg_match('~^`(.+?)`\.(.+?)$~', Config::$db_prefix, $match) != 0 ? strtr($match[1], ['`' => '']) : false; + + $tables = Db::$db->list_tables($db, $filter); + + // Filter out backup tables. + $table_names = array_filter($tables, function ($table) { + return !str_starts_with($table, 'backup_'); + }); + + Maintenance::$total_substeps = count($table_names); + + // Template things. + Maintenance::$context['table_count'] = Maintenance::$total_substeps; + Maintenance::$context['cur_table_num'] = Maintenance::getCurrentSubStep(); + Maintenance::$context['cur_table_name'] = str_replace(Config::$db_prefix, '', $table_names[Maintenance::getCurrentSubStep()]); + Maintenance::$context['continue'] = true; + + if (Sapi::isCLI()) { + echo 'Backing Up Tables.'; + } + + // We are set up for backing up. + if (!Sapi::isCLI() && !Maintenance::isJson()) { + return false; + } + + // Back up each table! + while (Maintenance::getCurrentSubStep() <= Maintenance::$total_substeps) { + $current_table = $table_names[Maintenance::getCurrentSubStep()]; + $this->doBackupTable($current_table); + + // Increase our current substep by 1. + Maintenance::setCurrentSubStep(); + + // If this is JSON to keep it nice for the user do one table at a time anyway! + if (Maintenance::isJson()) { + Maintenance::jsonResponse( + [ + 'current_table_name' => str_replace(Config::$db_prefix, '', $current_table), + 'current_table_index' => Maintenance::getCurrentSubStep(), + 'substep_progress' => Maintenance::getSubStepProgress(), + ], + ); + } + } + + if (Sapi::isCLI()) { + echo "\n" . ' Successful.\'' . "\n"; + flush(); + } + + // Make sure we move on! + return true; + } + + /** + * Perform database migration actions. + * + * - This performs steps as required to make changes safely to the database. + * - Each migration is tracked as a substep. + * - We check if the migration is a candidate, if it is not, we skip the + * substep. + * - The migration may loop over multiple times, returning false. In such + * cases, it will use the start to check its offset. + * + * @return bool True if we are done, false if we need to time out and wait. + */ + public function migrations(): bool + { + // Have we just completed this? + if (!empty($_POST['database_done'])) { + return true; + } + + $substeps = []; + + foreach (self::VERSION_MAP as $search => $ns) { + if (version_compare($this->start_smf_version, $search, '>')) { + continue; + } + + $substeps = array_merge($substeps, self::MIGRATIONS[$ns]); + } + + $this->performSubsteps($substeps); + + return Sapi::isCLI(); + } + + /** + * Perform cleanup actions. + * + * - This operates similarly to migrations, but is designed for operations + * against the file system to optimize the installation. + * - Each cleanup is tracked as a substep. + * - We check if the cleanup is a candidate. If not, we skip the substep. + * - The cleanup may loop over multiple times, returning false. In such + * cases, it will use the start to check its offset. + * + * @return bool True if we are done, false if we need to timeout and wait. + */ + public function cleanup(): bool + { + // Have we just completed this? + if (!empty($_POST['cleanup_done'])) { + return true; + } + + $substeps = []; + + foreach (self::VERSION_MAP as $search => $ns) { + if (version_compare($this->start_smf_version, $search, '>')) { + continue; + } + + $substeps = array_merge($substeps, self::CLEANUPS[$ns]); + } + + $this->performSubsteps($substeps); + + return Sapi::isCLI(); + } + + /** + * Upgrade is completed, offer help if things went wrong, or congrats if + * everything upgraded. Offers a option to delete the upgrade file. + * + * @return bool + */ + public function finalize(): bool + { + Maintenance::$context['form_action'] = Config::$boardurl . '/index.php'; + + // Finalize some settings in the settings file. + $file_settings = [ + 'maintenance' => $this->user['maint'] ?? 0, + ]; + + // Delete all the obsolete settings. + foreach (Config::getSettingsDefs() as $var => $setting_def) { + if (is_string($var) && ($setting_def['auto_delete'] ?? null) === 3) { + $file_settings[$var] = $setting_def['default']; + } + } + + $res = Config::updateSettingsFile($file_settings); + + if (Sapi::isCLI() && !$res) { + die('FAILURE: Could not update ' . basename(SMF_SETTINGS_FILE) . "\n"); + } + + // Update the database with the new SMF version. + Config::updateModSettings(['smfVersion' => SMF_VERSION]); + + // Clean any old cache files away. + CacheApi::load(); + CacheApi::clean(); + + // Queue up some background tasks that we want to run soon after upgrading. + Db::$db->insert( + 'insert', + '{db_prefix}background_tasks', + [ + 'task_class' => 'string', + 'task_data' => 'string', + 'claimed_time' => 'int', + ], + [ + [ + 'SMF\\Tasks\\FetchSMfiles', + '', + 0, + ], + ], + ['id_task'], + ); + + Db::$db->insert( + 'insert', + '{db_prefix}background_tasks', + [ + 'task_class' => 'string', + 'task_data' => 'string', + 'claimed_time' => 'int', + ], + [ + [ + 'SMF\\Tasks\\UpdateSpoofDetectorNames', + json_encode(['last_member_id' => 0]), + 0, + ], + ], + ['id_task'], + ); + + // Log what we've done. + if (!isset(User::$me)) { + User::load(); + } + + if (empty(User::$me->id) && !empty($this->user['id'])) { + User::setMe($this->user['id']); + } + + User::$me->ip = Sapi::isCLI() || empty($_SERVER['REMOTE_ADDR']) ? '127.0.0.1' : $_SERVER['REMOTE_ADDR']; + + // Log the action manually, so CLI still works. + Db::$db->insert( + '', + '{db_prefix}log_actions', + [ + 'log_time' => 'int', + 'id_log' => 'int', + 'id_member' => 'int', + 'ip' => 'inet', + 'action' => 'string', + 'id_board' => 'int', + 'id_topic' => 'int', + 'id_msg' => 'int', + 'extra' => 'string-65534', + ], + [ + [ + time(), + 3, + User::$me->id, + User::$me->ip, + 'upgrade', + 0, + 0, + 0, + json_encode(['version' => SMF_FULL_VERSION, 'member' => User::$me->id]), + ], + ], + ['id_action'], + ); + + User::setMe(0); + + if (Sapi::isCLI()) { + echo "\n"; + echo 'Upgrade Complete!', "\n"; + echo 'Please delete this file as soon as possible for security reasons.', "\n"; + + exit; + } + + // Can we delete the file? + Maintenance::$context['can_delete_script'] = is_writable(Config::$boarddir) || is_writable(Config::$boarddir . '/' . $this->script_name); + + // Show Upgrade time in debug mode when we completed the upgrade process totally + if ($this->debug) { + $active = time() - (int) $this->time_started; + + Maintenance::$context['upgrade_completed_time'] = Lang::getTxt( + $active >= 3600 ? 'upgrade_completed_time_hms' : ($active >= 60 ? 'upgrade_completed_time_ms' : 'upgrade_completed_time_s'), + [ + 'h' => (int) ($active / 3600), + 'm' => (int) ((int) ($active / 60) % 60), + 's' => (int) ($active % 60), + ], + file: 'Maintenance', + ); + } + + // Make sure it says we're done. + Maintenance::$overall_percent = 100; + + // Wipe this out... + $this->user = []; + + Maintenance::setCurrentSubStep(0); + + return Sapi::isCLI(); + } + + /** + * Write out our current information to our settings file to track the + * upgrade progress. + */ + public function preExit(): void + { + $this->saveUpgradeData(); + } + + /** + * Figures out whether to make "yes" or "no" the default value for the + * option to create backups before upgrading. + * + * In nearly all situations the admin should be encouraged to make a backup. + * However, if the admin is re-running the upgrader and a recent backup + * already exists, we shouldn't overwrite it unless the admin intentionally + * tells us to do so. + * + * @return bool Whether creating a backup is recommended in this case. + */ + public function backupRecommended(): bool + { + $tables = Db::$db->list_tables(); + + // Filter out backup tables. + $table_names = array_filter($tables, function ($table) { + return !str_starts_with($table, 'backup_'); + }); + + // If there is no existing backup, recommend that they make one now. + if ($tables === $table_names) { + return true; + } + + return (Config::$modSettings['smfVersion'] ?? null) !== SMF_VERSION; + } + + /** + * Actually backup a table. + * + * @param mixed $table_name Name of the table to be backed up + * @return bool True if succesfull, false otherwise. + */ + public function doBackupTable($table): bool + { + if (Sapi::isCLI()) { + echo "\n" . ' +++ Backing up \"' . str_replace(Config::$db_prefix, '', $table) . '"...'; + flush(); + } + + // @@TODO: Check result? Should be a object, false if it failed. + Db::$db->backup_table($table, 'backup_' . $table); + + if (Sapi::isCLI()) { + echo ' done.'; + } + + return true; + } + + /****************** + * Internal methods + ******************/ + + /** + * Prepare the configuration to handle support with some older installs. + */ + private function prepareUpgrade(): void + { + // SMF 2.1: We don't use "-utf8" anymore... Tweak the entry that may have been loaded by Settings.php + if (isset(Config::$language)) { + Config::$language = str_ireplace('-utf8', '', basename(Config::$language, '.lng')); + } + + // SMF 1.x didn't support multiple database types. + // SMF 2.0 used 'mysqli' for a short time. + if (empty(Config::$db_type) || Config::$db_type == 'mysqli') { + Config::$db_type = 'mysql'; + // If overriding Config::$db_type, need to set its Settings.php entry, too. + Config::updateSettingsFile(['db_type' => 'mysql']); + } + + try { + Maintenance::loadDatabase(); + Maintenance::loadModSettings(); + Maintenance::setThemeData(); + } catch (\Throwable $e) { + die($e->getMessage()); + } + + + $this->getUpgradeData(); + + // Template needs to know about this. + Maintenance::$context['started'] = &$this->time_started; + Maintenance::$context['updated'] = &$this->time_updated; + Maintenance::$context['user'] = &$this->user; + } + + /** + * Get our upgrade data. + */ + private function getUpgradeData(): void + { + try { + $data = isset(Config::$custom['upgradeData']) ? Utils::jsonDecode(base64_decode(Config::$custom['upgradeData']), true) : []; + } catch (\Throwable $e) { + $data = []; + } + + $this->time_started = (int) ($data['started'] ?? time()); + $this->time_updated = (int) ($data['updated'] ?? time()); + $this->debug = !empty($data['debug']); + $this->skipped_migrations = (array) ($data['skipped'] ?? []); + $this->user['id'] = (int) ($data['user_id'] ?? 0); + $this->user['name'] = (string) ($data['user_name'] ?? ''); + $this->user['maint'] = (int) ($data['maint'] ?? Config::$maintenance); + $this->start_smf_version = str_replace(' ', '.', strtolower($data['smf_version'] ?? Config::$modSettings['smfVersion'] ?? '0.0.dev.0')); + } + + /** + * Save our data. + * + * @return bool True if we could update our settings file, false otherwise. + */ + private function saveUpgradeData(): bool + { + if (Maintenance::$overall_percent < 100) { + $data = base64_encode(json_encode([ + 'started' => $this->time_started, + 'updated' => $this->time_updated, + 'debug' => $this->debug, + 'skipped' => $this->skipped_migrations, + 'user_id' => $this->user['id'], + 'user_name' => $this->user['name'], + 'maint' => $this->user['maint'] ?? 0, + 'smf_version' => $this->start_smf_version, + ])); + } else { + $data = ''; + } + + if (!Config::updateSettingsFile(['upgradeData' => $data])) { + Maintenance::$fatal_error = Lang::getTxt('upgrade_writable_files', file: 'Maintenance') . ': ' . basename(SMF_SETTINGS_FILE); + } + + return true; + } + + /** + * Verify that the attachment directory is valid during the upgrade. + * + * This function safely checks both a serialized and json encoded attachment + * directory information. + * + * When multiple attachment directories exist, all are checked. + * + * @return bool True if no errors found, false otherwise. + */ + private function attachmentDirectoryIsValid(): bool + { + // A bit more complex, since it may be json or serialized, and it may be + // an array or just a string... + + // PHP 8.0-8.2 has a terrible handling with unserialize in which + // errors are fatal and not catch-able. Lets borrow some code from the + // RFC that intends to fix this: + // https://wiki.php.net/rfc/improve_unserialize_error_handling + try { + set_error_handler(static function ($severity, $message, $file, $line) { + throw new \ErrorException($message, 0, $severity, $file, $line); + }); + $ser_test = @unserialize(Config::$modSettings['attachmentUploadDir']); + } catch (\Throwable $e) { + $ser_test = false; + } finally { + restore_error_handler(); + } + + // Json is simple, it can be caught. + try { + $json_test = @json_decode(Config::$modSettings['attachmentUploadDir'], true); + } catch (\Throwable $e) { + $json_test = null; + } + + $attach_directory_problem_found = false; + + // String? + if ( + !empty(Config::$modSettings['attachmentUploadDir']) + && is_string(Config::$modSettings['attachmentUploadDir']) + && is_dir(Config::$modSettings['attachmentUploadDir']) + ) { + // OK... + } + // An array already? + elseif (is_array(Config::$modSettings['attachmentUploadDir'])) { + foreach (Config::$modSettings['attachmentUploadDir'] as $dir) { + if (!empty($dir) && !is_dir($dir)) { + $attach_directory_problem_found = true; + } + } + } + // Serialized? + elseif ($ser_test !== false) { + if (is_array($ser_test)) { + foreach ($ser_test as $dir) { + if (!empty($dir) && !is_dir($dir)) { + $attach_directory_problem_found = true; + } + } + } else { + if (!empty($ser_test) && !is_dir($ser_test)) { + $attach_directory_problem_found = true; + } + } + } + // JSON? Note the test returns null if encoding was unsuccessful. + elseif ($json_test !== null) { + if (is_array($json_test)) { + foreach ($json_test as $dir) { + if (!is_dir($dir)) { + $attach_directory_problem_found = true; + } + } + } else { + if (!is_dir($json_test)) { + $attach_directory_problem_found = true; + } + } + } + // Unclear, needs a look... + else { + $attach_directory_problem_found = true; + } + + return $attach_directory_problem_found; + } + + /** + * Determine if we need to enable or disable (during upgrades) SMF stat collection. + * + * @param array $settings Settings array, passed by reference. + */ + private function toggleSmStats(array &$settings): void + { + if ( + !empty($_POST['stats']) + && substr(Config::$boardurl, 0, 16) != 'http://localhost' + && empty(Config::$modSettings['allow_sm_stats']) + && empty(Config::$modSettings['enable_sm_stats']) + ) { + Maintenance::$context['allow_sm_stats'] = true; + + // Attempt to register the site etc. + $fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr); + + if (!$fp) { + $fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr); + } + + if (!$fp) { + return; + } + + $out = 'GET /smf/stats/register_stats.php?site=' . base64_encode(Config::$boardurl) . ' HTTP/1.1' . "\r\n"; + $out .= 'Host: www.simplemachines.org' . "\r\n"; + $out .= 'Connection: Close' . "\r\n\r\n"; + fwrite($fp, $out); + + $return_data = ''; + + while (!feof($fp)) { + $return_data .= fgets($fp, 128); + } + + fclose($fp); + + // Get the unique site ID. + preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID); + + if (!empty($ID[1])) { + $settings['sm_stats_key'] = $ID[1]; + $settings['enable_sm_stats'] = 1; + } + } + // Don't remove stat collection unless we unchecked the box for real, not from the loop. + elseif (empty($_POST['stats']) && empty(Maintenance::$context['allow_sm_stats'])) { + $settings['enable_sm_stats'] = null; + } + } + + /** + * Get the name of the next substep, if any. + * + * @param int $num Number of the substep we are looking for + * @param array $substeps All substep classes that we are running. + */ + private function getSubstepName(int $num, array $substeps): string + { + try { + if (!isset($substeps[$num])) { + return ''; + } + + $class = $substeps[$num]; + $obj = new $class(); + + return $obj->name; + } catch (\Throwable $e) { + return ''; + } + } + + /** + * Performs a series of substeps. + * + * @param array $substeps All substep classes that we are running. + */ + private function performSubsteps(array $substeps): void + { + Maintenance::$total_substeps = count($substeps); + + // We are preparing for templating. + if (!Sapi::isCLI() && !Maintenance::isJson()) { + Maintenance::$context['continue'] = true; + Maintenance::$context['current_substep'] = $this->getSubstepName(Maintenance::getCurrentSubStep(), $substeps); + + return; + } + + // Load up the current user safely. + if (!isset(User::$me)) { + User::setMe($this->user['id']); + + if ($this->user['id'] === 0 && $this->user['name'] === 'Database Admin') { + User::$me->username = User::$me->name = $this->user['name']; + } + } + + if (Maintenance::$total_substeps === 0) { + Maintenance::jsonResponse([ + 'name' => '', + 'skipped' => true, + 'substep' => 0, + 'start' => 0, + 'total' => 0, + 'debug' => [ + 'call' => '', + ], + ]); + + return; + } + + /* + * When SKIP occurs, note it in JS and continue to next step. + * When success occurs, ensure it moves to next stesp. + * When error occurs, ensure we properly show the error. + */ + while (Maintenance::getCurrentSubStep() < Maintenance::$total_substeps) { + $substep_class = $substeps[Maintenance::getCurrentSubStep()]; + $substep = new $substep_class(); + + if (Sapi::isCLI()) { + echo "\n" . ' +++ ' . $substep->name . '... '; + } + + // If this is not a canidate for us to execute, skip it. + try { + if (!$substep->isCandidate()) { + Maintenance::setCurrentSubStep(); + + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'next' => $this->getSubstepName(Maintenance::getCurrentSubStep(), $substeps), + 'skipped' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => basename($substep_class), + ], + ]); + + if (Sapi::isCLI()) { + echo 'skipped.'; + } + + continue; + } + } catch (\Throwable $e) { + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'failed' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => basename($substep_class), + 'msg' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], + ]); + + return; + } + + $res = false; + + try { + $res = $substep->execute(); + } catch (\Throwable $e) { + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'failed' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => basename($substep_class), + 'msg' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], + ]); + + if (Sapi::isCLI()) { + echo 'failed with error: "' . $e->getMessage() . '"' . "\n"; + } + + return; + } + + // If not ready yet, fail. + if (!$res) { + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'completed' => false, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => basename($substep_class), + ], + ]); + + if (Sapi::isCLI()) { + echo 'failed.'; + } + + return; + } + + if (Sapi::isCLI()) { + echo 'done.'; + } + + // Increase our current substep by 1. + Maintenance::setCurrentSubStep(); + Maintenance::setCurrentStart(0); + + // If this is JSON to keep it nice for the user do one table at a time anyway! + if (Maintenance::isJson()) { + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'next' => $this->getSubstepName(Maintenance::getCurrentSubStep(), $substeps), + 'completed' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => basename($substep_class), + ], + ]); + } + } + } +} diff --git a/Sources/Maintenance/Utf8ConverterStep.php b/Sources/Maintenance/Utf8ConverterStep.php new file mode 100644 index 00000000000..6ab9618342e --- /dev/null +++ b/Sources/Maintenance/Utf8ConverterStep.php @@ -0,0 +1,934 @@ + 'ISO-8859-1', + 'albanian' => 'ISO-8859-1', + 'arabic' => 'windows-1256', + 'armenian_east' => 'armscii-8', + 'armenian_west' => 'armscii-8', + 'azerbaijani_latin' => 'ISO-8859-9', + 'bangla' => 'UTF-8', + 'basque' => 'ISO-8859-1', + 'belarusian' => 'ISO-8859-5', + 'bosnian' => 'ISO-8859-1', + 'bulgarian' => 'windows-1251', + 'cambodian' => 'UTF-8', + 'catalan' => 'ISO-8859-1', + 'chinese_simplified' => 'gbk', + 'chinese_traditional' => 'big5', + 'croatian' => 'ISO-8859-2', + 'czech' => 'ISO-8859-2', + 'czech_informal' => 'ISO-8859-2', + 'danish' => 'ISO-8859-1', + 'dutch' => 'ISO-8859-1', + 'english' => 'ISO-8859-1', + 'english_british' => 'ISO-8859-1', + 'english_pirate' => 'UTF-8', + 'esperanto' => 'ISO-8859-3', + 'estonian' => 'ISO-8859-15', + 'filipino_tagalog' => 'UTF-8', + 'filipino_visayan' => 'UTF-8', + 'finnish' => 'ISO-8859-1', + 'french' => 'ISO-8859-1', + 'galician' => 'ISO-8859-1', + 'georgian' => 'UTF-8', + 'german' => 'ISO-8859-1', + 'german_informal' => 'ISO-8859-1', + 'greek' => 'windows-1253', + 'hebrew' => 'windows-1255', + 'hindi' => 'ISO-8859-1', + 'hungarian' => 'ISO-8859-2', + 'icelandic' => 'ISO-8859-1', + 'indonesian' => 'ISO-8859-1', + 'irish' => 'UTF-8', + 'italian' => 'ISO-8859-1', + 'japanese' => 'UTF-8', + 'khmer' => 'UTF-8', + 'korean' => 'UTF-8', + 'kurdish_kurmanji' => 'ISO-8859-9', + 'kurdish_sorani' => 'windows-1256', + 'lao' => 'tis-620', + 'latvian' => 'ISO-8859-13', + 'macedonian' => 'UTF-8', + 'malay' => 'ISO-8859-1', + 'malayalam' => 'UTF-8', + 'mongolian' => 'UTF-8', + 'nepali' => 'UTF-8', + 'norwegian' => 'ISO-8859-1', + 'persian' => 'UTF-8', + 'polish' => 'ISO-8859-2', + 'portuguese_brazilian' => 'ISO-8859-1', + 'portuguese_pt' => 'ISO-8859-1', + 'romanian' => 'ISO-8859-2', + 'russian' => 'windows-1251', + 'sakha' => 'UTF-8', + 'serbian_cyrillic' => 'ISO-8859-5', + 'serbian_latin' => 'ISO-8859-2', + 'sinhala' => 'UTF-8', + 'slovak' => 'ISO-8859-2', + 'slovenian' => 'ISO-8859-2', + 'spanish' => 'ISO-8859-1', + 'spanish_es' => 'ISO-8859-1', + 'spanish_latin' => 'ISO-8859-1', + 'swedish' => 'ISO-8859-1', + 'telugu' => 'UTF-8', + 'thai' => 'tis-620', + 'turkish' => 'ISO-8859-9', + 'turkmen' => 'ISO-8859-9', + 'ukrainian' => 'windows-1251', + 'urdu' => 'UTF-8', + 'uzbek_cyrillic' => 'ISO-8859-5', + 'uzbek_latin' => 'ISO-8859-5', + 'vietnamese' => 'UTF-8', + 'welsh' => 'ISO-8859-1', + 'yoruba' => 'UTF-8', + ]; + + /** + * @var array + * + * Maps character sets used in old, non-Unicode SMF language files to the + * corresponding MySQL aliases for those character sets. This list only + * includes exact matches. + */ + public const CHARSET_MAPS = [ + // Armenian + 'armscii-8' => 'armscii8', + // Chinese-traditional. + 'big5' => 'big5', + // Chinese-simplified. + 'gbk' => 'gbk', + // West European. + 'ISO-8859-1' => 'latin1', + // Romanian. + 'ISO-8859-2' => 'latin2', + // Turkish. + 'ISO-8859-9' => 'latin5', + // Latvian + 'ISO-8859-13' => 'latin7', + // Thai. + 'tis-620' => 'tis620', + // Persian, Chinese, etc. + 'UTF-8' => 'utf8mb3', + // Russian. + 'windows-1251' => 'cp1251', + // Arabic. + 'windows-1256' => 'cp1256', + ]; + + /** + * @var array + * + * Manual character translation for a couple of rare character sets that old + * SMF language files might have used. + */ + public const TRANSLATION_TABLES = [ + 'windows-1253' => [ + '0x80' => '0xE282AC', + '0x81' => '\'\'', + '0x82' => '0xE2809A', + '0x83' => '0xC692', + '0x84' => '0xE2809E', + '0x85' => '0xE280A6', + '0x86' => '0xE280A0', + '0x87' => '0xE280A1', + '0x88' => '\'\'', + '0x89' => '0xE280B0', + '0x8A' => '\'\'', + '0x8B' => '0xE280B9', + '0x8C' => '\'\'', + '0x8D' => '\'\'', + '0x8E' => '\'\'', + '0x8F' => '\'\'', + '0x90' => '\'\'', + '0x91' => '0xE28098', + '0x92' => '0xE28099', + '0x93' => '0xE2809C', + '0x94' => '0xE2809D', + '0x95' => '0xE280A2', + '0x96' => '0xE28093', + '0x97' => '0xE28094', + '0x98' => '\'\'', + '0x99' => '0xE284A2', + '0x9A' => '\'\'', + '0x9B' => '0xE280BA', + '0x9C' => '\'\'', + '0x9D' => '\'\'', + '0x9E' => '\'\'', + '0x9F' => '\'\'', + '0xA0' => '0xC2A0', + '0xA1' => '0xCE85', + '0xA2' => '0xCE86', + '0xA3' => '0xC2A3', + '0xA4' => '0xC2A4', + '0xA5' => '0xC2A5', + '0xA6' => '0xC2A6', + '0xA7' => '0xC2A7', + '0xA8' => '0xC2A8', + '0xA9' => '0xC2A9', + '0xAA' => '\'\'', + '0xAB' => '0xC2AB', + '0xAC' => '0xC2AC', + '0xAD' => '0xC2AD', + '0xAE' => '0xC2AE', + '0xAF' => '0xE28095', + '0xB0' => '0xC2B0', + '0xB1' => '0xC2B1', + '0xB2' => '0xC2B2', + '0xB3' => '0xC2B3', + '0xB4' => '0xCE84', + '0xB5' => '0xC2B5', + '0xB6' => '0xC2B6', + '0xB7' => '0xC2B7', + '0xB8' => '0xCE88', + '0xB9' => '0xCE89', + '0xBA' => '0xCE8A', + '0xBB' => '0xC2BB', + '0xBC' => '0xCE8C', + '0xBD' => '0xC2BD', + '0xBE' => '0xCE8E', + '0xBF' => '0xCE8F', + '0xC0' => '0xCE90', + '0xC1' => '0xCE91', + '0xC2' => '0xCE92', + '0xC3' => '0xCE93', + '0xC4' => '0xCE94', + '0xC5' => '0xCE95', + '0xC6' => '0xCE96', + '0xC7' => '0xCE97', + '0xC8' => '0xCE98', + '0xC9' => '0xCE99', + '0xCA' => '0xCE9A', + '0xCB' => '0xCE9B', + '0xCC' => '0xCE9C', + '0xCD' => '0xCE9D', + '0xCE' => '0xCE9E', + '0xCF' => '0xCE9F', + '0xD0' => '0xCEA0', + '0xD1' => '0xCEA1', + '0xD2' => '0xEFBFBD', + '0xD3' => '0xCEA3', + '0xD4' => '0xCEA4', + '0xD5' => '0xCEA5', + '0xD6' => '0xCEA6', + '0xD7' => '0xCEA7', + '0xD8' => '0xCEA8', + '0xD9' => '0xCEA9', + '0xDA' => '0xCEAA', + '0xDB' => '0xCEAB', + '0xDC' => '0xCEAC', + '0xDD' => '0xCEAD', + '0xDE' => '0xCEAE', + '0xDF' => '0xCEAF', + '0xE0' => '0xCEB0', + '0xE1' => '0xCEB1', + '0xE2' => '0xCEB2', + '0xE3' => '0xCEB3', + '0xE4' => '0xCEB4', + '0xE5' => '0xCEB5', + '0xE6' => '0xCEB6', + '0xE7' => '0xCEB7', + '0xE8' => '0xCEB8', + '0xE9' => '0xCEB9', + '0xEA' => '0xCEBA', + '0xEB' => '0xCEBB', + '0xEC' => '0xCEBC', + '0xED' => '0xCEBD', + '0xEE' => '0xCEBE', + '0xEF' => '0xCEBF', + '0xF0' => '0xCF80', + '0xF1' => '0xCF81', + '0xF2' => '0xCF82', + '0xF3' => '0xCF83', + '0xF4' => '0xCF84', + '0xF5' => '0xCF85', + '0xF6' => '0xCF86', + '0xF7' => '0xCF87', + '0xF8' => '0xCF88', + '0xF9' => '0xCF89', + '0xFA' => '0xCF8A', + '0xFB' => '0xCF8B', + '0xFC' => '0xCF8C', + '0xFD' => '0xCF8D', + '0xFE' => '0xCF8E', + ], + 'windows-1255' => [ + '0x80' => '0xE282AC', + '0x81' => '\'\'', + '0x82' => '0xE2809A', + '0x83' => '0xC692', + '0x84' => '0xE2809E', + '0x85' => '0xE280A6', + '0x86' => '0xE280A0', + '0x87' => '0xE280A1', + '0x88' => '0xCB86', + '0x89' => '0xE280B0', + '0x8A' => '\'\'', + '0x8B' => '0xE280B9', + '0x8C' => '\'\'', + '0x8D' => '\'\'', + '0x8E' => '\'\'', + '0x8F' => '\'\'', + '0x90' => '\'\'', + '0x91' => '0xE28098', + '0x92' => '0xE28099', + '0x93' => '0xE2809C', + '0x94' => '0xE2809D', + '0x95' => '0xE280A2', + '0x96' => '0xE28093', + '0x97' => '0xE28094', + '0x98' => '0xCB9C', + '0x99' => '0xE284A2', + '0x9A' => '\'\'', + '0x9B' => '0xE280BA', + '0x9C' => '\'\'', + '0x9D' => '\'\'', + '0x9E' => '\'\'', + '0x9F' => '\'\'', + '0xA0' => '0xC2A0', + '0xA1' => '0xC2A1', + '0xA2' => '0xC2A2', + '0xA3' => '0xC2A3', + '0xA4' => '0xE282AA', + '0xA5' => '0xC2A5', + '0xA6' => '0xC2A6', + '0xA7' => '0xC2A7', + '0xA8' => '0xC2A8', + '0xA9' => '0xC2A9', + '0xAA' => '0xC397', + '0xAB' => '0xC2AB', + '0xAC' => '0xC2AC', + '0xAD' => '0xC2AD', + '0xAE' => '0xC2AE', + '0xAF' => '0xC2AF', + '0xB0' => '0xC2B0', + '0xB1' => '0xC2B1', + '0xB2' => '0xC2B2', + '0xB3' => '0xC2B3', + '0xB4' => '0xC2B4', + '0xB5' => '0xC2B5', + '0xB6' => '0xC2B6', + '0xB7' => '0xC2B7', + '0xB8' => '0xC2B8', + '0xB9' => '0xC2B9', + '0xBA' => '0xC3B7', + '0xBB' => '0xC2BB', + '0xBC' => '0xC2BC', + '0xBD' => '0xC2BD', + '0xBE' => '0xC2BE', + '0xBF' => '0xC2BF', + '0xC0' => '0xD6B0', + '0xC1' => '0xD6B1', + '0xC2' => '0xD6B2', + '0xC3' => '0xD6B3', + '0xC4' => '0xD6B4', + '0xC5' => '0xD6B5', + '0xC6' => '0xD6B6', + '0xC7' => '0xD6B7', + '0xC8' => '0xD6B8', + '0xC9' => '0xD6B9', + '0xCA' => '0xEFBFBD', + '0xCB' => '0xD6BB', + '0xCC' => '0xD6BC', + '0xCD' => '0xD6BD', + '0xCE' => '0xD6BE', + '0xCF' => '0xD6BF', + '0xD0' => '0xD780', + '0xD1' => '0xD781', + '0xD2' => '0xD782', + '0xD3' => '0xD783', + '0xD4' => '0xD7B0', + '0xD5' => '0xD7B1', + '0xD6' => '0xD7B2', + '0xD7' => '0xD7B3', + '0xD8' => '0xD7B4', + '0xD9' => '\'\'', + '0xDA' => '\'\'', + '0xDB' => '\'\'', + '0xDC' => '\'\'', + '0xDD' => '\'\'', + '0xDE' => '\'\'', + '0xDF' => '\'\'', + '0xE0' => '0xD790', + '0xE1' => '0xD791', + '0xE2' => '0xD792', + '0xE3' => '0xD793', + '0xE4' => '0xD794', + '0xE5' => '0xD795', + '0xE6' => '0xD796', + '0xE7' => '0xD797', + '0xE8' => '0xD798', + '0xE9' => '0xD799', + '0xEA' => '0xD79A', + '0xEB' => '0xD79B', + '0xEC' => '0xD79C', + '0xED' => '0xD79D', + '0xEE' => '0xD79E', + '0xEF' => '0xD79F', + '0xF0' => '0xD7A0', + '0xF1' => '0xD7A1', + '0xF2' => '0xD7A2', + '0xF3' => '0xD7A3', + '0xF4' => '0xD7A4', + '0xF5' => '0xD7A5', + '0xF6' => '0xD7A6', + '0xF7' => '0xD7A7', + '0xF8' => '0xD7A8', + '0xF9' => '0xD7A9', + '0xFA' => '0xD7AA', + '0xFB' => '\'\'', + '0xFC' => '\'\'', + '0xFD' => '0xE2808E', + '0xFE' => '0xE2808F', + ], + ]; + + /******************* + * Public properties + *******************/ + + /** + * Character sets supported by the database. + */ + public array $supported_charsets = []; + + /** + * Character set that SMF has been using to interact with the browser. + * + * This will typically (but not necessarily) have been the character set + * that was specified in the forum's language files. + */ + public string $lang_charset = 'UTF-8'; + + /** + * The subset of self::CHARSET_MAPS that is supported by the database. + */ + public array $charset_maps = []; + + /**************** + * Public methods + ****************/ + + /** + * Constructor. + * + * @param int $id ID of the step. + * @param string $name Name of the step. + * @param int $progress The amount of progress to be made when this step + * completes. + * @param ?string $title The page title we will display for this step. + * If null, defaults to $name. + * @param ?string $title The sub-template to use to display for this step. + * If null, will default to 'convertDatabase'. + */ + public function __construct(int $id, string $name, int $progress, ?string $title = null, ?string $template = null) + { + parent::__construct($id, $name, [$this, 'convertDatabase'], $progress, $title, $template); + + // PostgreSQL databases don't need to do this. + if (Db::$db->title === POSTGRE_TITLE) { + return; + } + + // Get all the characters sets that are supported by this MySQL server. + $request = Db::$db->query('', 'SHOW CHARACTER SET'); + $this->supported_charsets = array_map(fn($row) => $row['Charset'], Db::$db->fetch_all($request)); + Db::$db->free_result($request); + + // Which character set have they been using for interacting with the browser? + if (isset(Config::$modSettings['global_character_set'])) { + $this->lang_charset = Config::$modSettings['global_character_set']; + } elseif (version_compare(strtolower(str_replace(' ', '.', Config::$modSettings['smfVersion'])), '3.0.dev.1', '>=')) { + $this->lang_charset = 'UTF-8'; + } else { + // Figure it out the hard way. + // Map in the new locales. We do it like this because we want to try + // our best to capture the correct charset no matter what the status of + // the language upgrade is. + foreach (self::LANG_CHARSETS as $key => $value) { + if (Lang::getLocaleFromLanguageName($key) === Config::$language) { + $this->lang_charset = $value; + break; + } + } + } + + // Remove any mapped character sets that are unsupported by this MySQL server. + $this->charset_maps = array_intersect(self::CHARSET_MAPS, $this->supported_charsets); + } + + /** + * Gets the substeps for this task. + * + * There will be one substep per table. + * + * @return array Instances of SMF\Maintenance\SubStepInterface. + */ + public function getSubSteps(): array + { + // PostgreSQL databases don't need to do this. + if (Db::$db->title === POSTGRE_TITLE) { + return []; + } + + $substeps = []; + + foreach (Db::$db->list_tables() as $table_name) { + if (str_starts_with($table_name, 'backup_')) { + continue; + } + + $substeps[] = new GenericSubStep( + name: Lang::getTxt('converting_table_to_utf8mb4', [$table_name], file: 'Maintenance'), + test: [$this, 'isCandidateTable'], + test_args: [$table_name], + exec: [$this, 'convertTable'], + exec_args: [$table_name], + ); + } + + return $substeps; + } + + /** + * Checks whether the specified table is a candidate for conversion. + * + * @return bool Whether the table needs to be converted to utf8mb4. + */ + public function isCandidateTable(string $table_name): bool + { + if ( + // PostgreSQL databases don't need to do this. + Db::$db->title === POSTGRE_TITLE + // Ignore backup tables. + || str_starts_with($table_name, 'backup_') + ) { + return false; + } + + // Must convert if the table's default character set isn't utf8mb4. + if (Db::$db->detect_charset($table_name) !== 'utf8mb4') { + return true; + } + + // Must convert if any string column's character set isn't utf8mb4. + foreach (Db::$db->list_columns($table_name, true) as $column) { + if (!in_array($column['type'], self::STRING_COLUMN_TYPES)) { + continue; + } + + if (Db::$db->detect_charset($table_name, $column['name']) !== 'utf8mb4') { + return true; + } + } + + // Table does not need to be converted to utf8mb4. + return false; + } + + /** + * Converts all SMF tables to utf8mb4. + * + * @return bool Whether the operation was successful. + */ + public function convertDatabase(): bool + { + if (!empty($_POST['utf8_done'])) { + return true; + } + + // PostgreSQL databases don't need to do this. + if (Db::$db->title === POSTGRE_TITLE) { + if (Maintenance::isJson()) { + Maintenance::jsonResponse([ + 'name' => '', + 'skipped' => true, + 'substep' => 0, + 'start' => 0, + 'total' => 0, + 'debug' => [ + 'call' => '', + ], + ]); + } + + return true; + } + + $substeps = $this->getSubSteps(); + + Maintenance::$total_substeps = count($substeps); + + // Template things. + Maintenance::$context['table_count'] = Maintenance::$total_substeps; + Maintenance::$context['cur_table_num'] = Maintenance::getCurrentSubStep(); + Maintenance::$context['cur_table_name'] = str_replace(Config::$db_prefix, '', $substeps[Maintenance::getCurrentSubStep()]->test_args[0]); + Maintenance::$context['continue'] = true; + + // We are set up for conversion. + if (!Sapi::isCLI() && !Maintenance::isJson()) { + return false; + } + + if (Maintenance::$total_substeps === 0) { + if (Maintenance::isJson()) { + Maintenance::jsonResponse([ + 'name' => '', + 'skipped' => true, + 'substep' => 0, + 'start' => 0, + 'total' => 0, + 'debug' => [ + 'call' => '', + ], + ]); + } + + return true; + } + + while (Maintenance::getCurrentSubStep() <= Maintenance::$total_substeps) { + $substep = $substeps[Maintenance::getCurrentSubStep()]; + + if (Sapi::isCLI()) { + echo "\n" . ' +++ ' . $substep->name . '... '; + } + + Maintenance::$context['cur_table_num'] = Maintenance::getCurrentSubStep(); + Maintenance::$context['cur_table_name'] = $substep->test_args[0]; + + if ($substep->isCandidate()) { + $table_success = $substep->execute(); + + if (Sapi::isCLI()) { + echo $table_success ? 'done.' : 'failed.'; + } + } elseif (Sapi::isCLI()) { + echo 'skipped.'; + } + + // Increase our current substep by 1. + Maintenance::setCurrentSubStep(); + + if (Maintenance::isJson()) { + Maintenance::jsonResponse( + [ + 'current_table_name' => str_replace(Config::$db_prefix, '', Maintenance::$context['cur_table_name']), + 'current_table_index' => Maintenance::getCurrentSubStep(), + 'substep_progress' => Maintenance::getSubStepProgress(), + ], + ); + } + } + + return true; + } + + /** + * Converts the specified table, and all applicable columns in that table, + * to utf8mb4. + * + * @return bool Whether the operation was successful. + */ + public function convertTable(string $table_name): bool + { + // PostgreSQL databases don't need to do this. + if (Db::$db->title === POSTGRE_TITLE) { + return true; + } + + // Just to make sure it doesn't time out. + Sapi::setTimeLimit(); + + // Get the structural info about the table. + $table = Db::$db->table_structure($table_name); + + // Get the character set for the table. + $table['charset'] = Db::$db->detect_charset($table_name); + + // Get the character set for each column. + foreach ($table['columns'] as $c => $column) { + if (!in_array($column['type'], self::STRING_COLUMN_TYPES)) { + continue; + } + + $table['columns'][$c]['charset'] = Db::$db->detect_charset($table_name, $column['name']); + } + + // If there's a fulltext index, we need to drop it first... + foreach ($table['indexes'] as $i => $index) { + if ($index['type'] === 'fulltext') { + Db::$db->remove_index( + table_name: $table_name, + index_name: $index['name'], + ); + + if ( + $table_name === 'messages' + && (Config::$modSettings['search_index'] ?? null) === 'fulltext' + ) { + Config::updateModSettings(['search_index' => '']); + Maintenance::$context['dropping_index'] = true; + } + } + } + + // Is the table already using some version of Unicode? + $table_is_unicode = str_starts_with($table['charset'], 'utf') || $table['charset'] === 'ucs2'; + + // We might need to do each column individually. + $convert_columns_individually = !( + // Probably don't need to if the table uses the expected charset. + $table['charset'] === ($this->charset_maps[$this->lang_charset] ?? null) + // Probably don't need to if they're just different versions of Unicode. + || ( + $table_is_unicode + && ( + !isset($this->charset_maps[$this->lang_charset]) + || str_starts_with($this->charset_maps[$this->lang_charset], 'utf') + || $this->charset_maps[$this->lang_charset] === 'ucs2' + ) + ) + ); + + $string_columns = []; + + foreach ($table['columns'] as $c => $column) { + if (!in_array($column['type'], self::STRING_COLUMN_TYPES)) { + continue; + } + + $string_columns[] = $column['name']; + + // We need to do each column individually if any of them use a + // different character set than the table as a whole. + if ($column['charset'] !== $table['charset']) { + $convert_columns_individually = true; + } + } + + // Keep track of whether all columns are prepared for conversion. + $prepared_columns = []; + + // Convert each column from text to binary and maybe do other stuff. + if ($convert_columns_individually) { + foreach ($string_columns as $column_name) { + if ($this->prepareColumn($table, $column_name)) { + $prepared_columns[] = $column_name; + } + } + } else { + $prepared_columns = $string_columns; + } + + // Change the table's character set to utf8mb4. + $result = Db::$db->query( + '', + 'ALTER TABLE {identifier:table_name} + CONVERT TO CHARACTER SET utf8mb4', + [ + 'table_name' => $table_name, + 'db_error_skip' => true, + ], + ); + + // Convert each column from binary back to text. + if ($convert_columns_individually) { + foreach ($prepared_columns as $column) { + Db::$db->change_column( + $table_name, + $column, + [ + 'type' => $table['columns'][$column]['type'], + ], + ); + } + } + + // @todo Restore any fulltext indexes we deleted above. + + // If the conversion failed, return false now. + if ($result === false) { + return false; + } + + // Create a background task to convert entities to characters. + Db::$db->insert( + 'insert', + '{db_prefix}background_tasks', + [ + 'task_class' => 'string-255', + 'task_data' => 'string', + 'claimed_time' => 'int', + ], + [ + [ + '\\SMF\\Tasks\\Utf8EntityDecode', + json_encode([ + 'table' => $table_name, + 'offset' => 0, + ]), + 0, + ], + ], + [], + ); + + return true; + } + + /****************** + * Internal methods + ******************/ + + /** + * Converts a column from text to binary and, if necessary, manually + * converts byte sequences in data stored using the wrong character set. + * + * @return bool Whether the column is now ready for conversion. + */ + protected function prepareColumn(array $table, string $column): bool + { + // We don't need to do anything if either of the following are true: + if ( + // The table and column both use the same character set. + $table['columns'][$column]['charset'] === $table['charset'] + // The column already uses utf8mb4. + || $table['columns'][$column]['charset'] === 'utf8mb4' + ) { + return true; + } + + // First, convert the column to binary. + Db::$db->change_column( + '{db_prefix}' . $table['name'], + $table['columns'][$column]['name'], + [ + 'type' => strtr($table['columns'][$column]['type'], ['text' => 'blob', 'char' => 'binary']), + ], + ); + + // Which encoding should we be converting from? + if (!isset($this->charset_maps[$this->lang_charset])) { + // $this->lang_charset doesn't map to a supported database charset, + // which means that the string was stored using the wrong charset but + // still would have been interpreted as $this->lang_charset once + // retrieved. + $from_charset = $this->lang_charset; + } else { + // This column simply isn't using the table's default character set. + $from_charset = $table['columns'][$column]['charset']; + } + + // If $from_charset is already some variant of UTF-8, we don't need to + // deal with the byte-level conversion step. + if (str_starts_with(strtolower($from_charset), 'utf8')) { + return true; + } + + // If the data was stored in the wrong charset, we must convert it manually. + if (!in_array($from_charset, $this->supported_charsets)) { + // Build a huge REPLACE statement. + $replace = '{identifier:column}'; + + if (isset($translation_tables[$from_charset])) { + foreach ($translation_tables[$from_charset] as $from => $to) { + $replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')'; + } + } else { + try { + for ($i = 0; $i <= 0xFF; $i++) { + $from = '0x' . strtoupper(dechex($i)); + $to = '0x' . strtoupper(bin2hex(mb_convert_encoding(chr($i), 'UTF-8', $from_charset))); + + if ($from !== $to) { + $replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')'; + } + } + } catch (\Throwable $e) { + // mb_convert_encoding will throw a ValueError if + // either encoding is unrecognized. + return false; + } + } + + // Convert the characters to UTF-8, using raw bytes. + $result = Db::$db->query( + '', + 'UPDATE {identifier:table} + SET {identifier:column} = ' . $replace, + [ + 'table' => Db::$db->quote('{db_prefix}' . $table['name']), + 'column' => $table['columns'][$column]['name'], + 'db_error_skip' => true, + ], + ); + + if ($result === false) { + return false; + } + } + + return true; + } +} diff --git a/Sources/Tasks/Utf8EntityDecode.php b/Sources/Tasks/Utf8EntityDecode.php new file mode 100644 index 00000000000..f8c2ba8d48e --- /dev/null +++ b/Sources/Tasks/Utf8EntityDecode.php @@ -0,0 +1,398 @@ +_details['table'], + Db::$db->list_tables(false, Db::$db->prefix . '%'), + ) + ) { + return true; + } + + // Get the structure of this table. + $structure = Db::$db->table_structure($this->_details['table']); + + // Which columns contain string data? + $string_columns = array_map( + fn($col) => $col['name'], + array_filter( + $structure['columns'], + fn($col) => ( + !str_ends_with($col['name'], '_utf8entitydecode') + && in_array($col['type'], ['varchar', 'char', 'tinytext', 'text', 'mediumtext', 'longtext', 'enum', 'set']) + ), + ), + ); + + // We need to fetch rows in a consistent order. There are several options: + // First and best option is to use the primary key, if there is one. + if (array_filter($structure['indexes'], fn($idx) => $idx['type'] === 'primary') !== []) { + $idx = current(array_filter($structure['indexes'], fn($idx) => $idx['type'] === 'primary')); + $order_by = array_map( + fn($col) => preg_replace('/\(\d+\)$/', '', $col), + $idx['columns'], + ); + } + // Next best is some other unique index, if there is one. + elseif (array_filter($structure['indexes'], fn($idx) => $idx['type'] === 'unique') !== []) { + $idx = current(array_filter($structure['indexes'], fn($idx) => $idx['type'] === 'unique')); + $order_by = array_map( + fn($col) => preg_replace('/\(\d+\)$/', '', $col), + $idx['columns'], + ); + } + // If there are no indexes, try the sequentially increasing column, if there is one. + elseif (array_filter($structure['columns'], fn($col) => !empty($col['auto'])) !== []) { + $col = current(array_filter($structure['columns'], fn($col) => !empty($col['auto']))); + $order_by = [$col['name']]; + } + // This is inefficient, but we have no better option remaining. + else { + $order_by = array_map( + fn($col) => $col['name'], + $structure['columns'], + ); + } + + // Can we update the table directly, or do we need to use a temp table? + $update_directly = array_intersect($string_columns, $order_by) === []; + + if ($update_directly) { + // Use the ORDER BY columns for the WHERE clause that updates data. + foreach ($order_by as $col) { + if (str_contains($structure['columns'][$col]['type'], 'int')) { + $type = 'int'; + } elseif (in_array($structure['columns'][$col]['type'], ['decimal', 'numeric', 'float', 'double'])) { + $type = 'float'; + } else { + $type = 'string'; + } + + $where[$col] = $col . ' = {' . $type . ':' . $col . '}'; + } + } else { + // When the ORDER BY clause contains one or more of the columns that + // need to be updated, we must use a multi-step process. + $this->createTempTable($string_columns); + } + + // Work in batches until we run close to the time limit. + while (microtime(true) < TIME_START + $time_limit) { + // Fetch the rows. + $request = Db::$db->query( + '', + 'SELECT {raw:columns} + FROM {identifier:table} + ORDER BY {raw:order_by} + LIMIT {int:limit} + OFFSET {int:offset}', + [ + 'table' => $this->_details['table'], + 'columns' => implode(', ', array_unique(array_merge($order_by, $string_columns))), + 'order_by' => implode(', ', $order_by), + 'limit' => self::LIMIT, + 'offset' => $this->_details['offset'], + ], + ); + + $num_rows = Db::$db->num_rows($request); + + while ($row = Db::$db->fetch_assoc($request)) { + $this->_details['offset']++; + + if ($update_directly) { + $this->updateDirectly($row, $order_by, $where, $string_columns); + } else { + $this->recordInTempTable($row, $string_columns); + } + } + + Db::$db->free_result($request); + + if ($num_rows < self::LIMIT) { + break; + } + } + + // If we have more rows to process, respawn this task. + if ($num_rows >= self::LIMIT) { + $this->respawn(); + + return true; + } + + // If we used a temp table and all rows have been processed, + // then we're now ready to update the main table. + if (!$update_directly) { + $this->updateFromTempTable($string_columns); + $this->dropTempTable($string_columns); + } + + return true; + } + + /****************** + * Internal methods + ******************/ + + /** + * Decodes numeric entities for four-byte UTF-8 characters in a string. + * + * @param string $string A UTF-8 string. + * @return string A UTF-8 string. + */ + protected function decode(string $string): string + { + return str_contains($string, '&') ? mb_decode_numericentity($string, [0x010000, 0x10FFFF, 0, 0xFFFFFF], 'UTF-8') : $string; + } + + /** + * Decodes numeric entities for four-byte UTF-8 characters in the data of + * each string column in a row and then updates the table with the new data. + * + * @param array $row A row of data that was retrieved from the table. + * @param array $order_by The columns used to order the rows during retrieval. + * @param array $where Conditions used to find the correct row to update. + * @param array $string_columns The columns whose data needs to be updated. + */ + private function updateDirectly(array $row, array $order_by, array $where, array $string_columns): void + { + $params = [ + 'table' => $this->_details['table'], + ]; + + $set = []; + + foreach ($row as $col => $value) { + if (!is_string($value)) { + unset($where[$col]); + continue; + } + + if (in_array($col, $order_by)) { + $params[$col] = $value; + } + + if (in_array($col, $string_columns)) { + $set[] = $col . ' = {string:decoded_' . $col . '}'; + $params['decoded_' . $col] = $this->decode($value); + } + } + + if (empty($set)) { + return; + } + + Db::$db->query( + '', + 'UPDATE {identifier:table} + SET ' . implode(', ', $set) . ' + WHERE (' . implode(') AND (', $where) . ')', + $params, + ); + } + + /** + * Creates a temporary table to store updated data and adds some temporary + * columns to the permanent table to link one to the other. + * + * @param array $string_columns The columns whose data needs to be updated. + */ + private function createTempTable(array $string_columns): void + { + Db::$db->create_table( + table_name: $this->_details['table'] . '_utf8entitydecode', + columns: [ + [ + 'name' => 'hash', + 'type' => 'char', + 'size' => 40, + ], + [ + 'name' => 'string', + 'type' => 'longtext', + ], + ], + if_exists: 'ignore', + ); + + foreach ($string_columns as $string_column) { + $added = Db::$db->add_column( + table_name: $this->_details['table'], + column_info: [ + 'name' => $string_column . '_utf8entitydecode', + 'type' => 'char', + 'size' => 40, + 'default' => '', + ], + if_exists: 'ignore', + ); + + if ($added) { + Db::$db->query( + '', + 'UPDATE {identifier:table} + SET {identifier:hash_col} = SHA1({identifier:col})', + [ + 'table' => $this->_details['table'], + 'col' => $string_column, + 'hash_col' => $string_column . '_utf8entitydecode', + ], + ); + } + } + } + + /** + * Decodes numeric entities in the data of each string column in the row and + * then writes the updated data to a temporary table. + * + * @param array $row A row of data that was retrieved from the table. + * @param array $string_columns The columns whose data needs to be updated. + */ + private function recordInTempTable(array $row, array $string_columns): void + { + $data = []; + + foreach ($string_columns as $col) { + if (!is_string($row[$col])) { + continue; + } + + $data[] = [ + sha1($row[$col]), + $this->decode($row[$col]), + ]; + } + + if (!empty($data)) { + Db::$db->insert( + method: 'ignore', + table: $this->_details['table'] . '_utf8entitydecode', + columns: [ + 'hash' => 'string', + 'string' => 'string', + ], + data: $data, + keys: [], + ); + } + } + + /** + * Updates the permanent table with the data from the temp table. + * + * @param array $string_columns The columns whose data needs to be updated. + */ + private function updateFromTempTable(array $string_columns): void + { + foreach ($string_columns as $string_column) { + Db::$db->update_from( + table: [ + 'name' => $this->_details['table'], + 'alias' => 't', + ], + from_tables: [ + [ + 'name' => $this->_details['table'] . '_utf8entitydecode', + 'alias' => 'u', + 'condition' => 't.' . $string_column . '_utf8entitydecode = u.hash', + ], + ], + set: 't.' . $string_column . ' = u.string', + where: '', + db_values: [], + ); + } + } + + /** + * Drops the temporary table and removes the temporary columns from the + * permanent table. + */ + private function dropTempTable(array $string_columns): void + { + foreach ($string_columns as $string_column) { + Db::$db->remove_column( + table_name: $this->_details['table'], + column_name: $string_column . '_utf8entitydecode', + ); + } + + Db::$db->drop_table( + table_name: $this->_details['table'] . '_utf8entitydecode', + ); + } + + /** + * Adds a new instance of this task to the task list. + */ + private function respawn(): void + { + Db::$db->insert( + 'insert', + '{db_prefix}background_tasks', + [ + 'task_class' => 'string-255', + 'task_data' => 'string', + 'claimed_time' => 'int', + ], + [ + [ + get_class($this), + json_encode($this->_details), + 0, + ], + ], + [], + ); + } +} diff --git a/Themes/default/MaintenanceTemplate.php b/Themes/default/MaintenanceTemplate.php index 5883b073e56..039e9e05abc 100644 --- a/Themes/default/MaintenanceTemplate.php +++ b/Themes/default/MaintenanceTemplate.php @@ -263,4 +263,123 @@ public static function missingLanguages(): void die; } + + /** + * Shows the template for the Utf8ConverterStep. + * + * This template is here rather than in UpgradeTemplate because it might + * also be needed for converters and other tools. + */ + public static function convertUtf8(): void + { + MaintenanceTemplate::warningsAndErrors(); + + if (!empty(Maintenance::$fatal_error)) { + return; + } + + // Show the continue button. + Maintenance::$context['continue'] = true; + + echo ' +

', Lang::getTxt('upgrade_wait2', file: 'Maintenance'), '

+ + ', Lang::getTxt('upgrade_completedtables_outof', Maintenance::$context), ' +
+ +
+

+ ', Lang::getTxt('upgrade_current_table', file: 'Maintenance'), ' "', Maintenance::$context['cur_table_name'], '" +

'; + + // If we dropped their index, let's let them know. + if (!empty(Maintenance::$context['dropping_index'])) { + echo ' +

', Lang::getTxt('upgrade_conversion_proceed', file: 'Maintenance'), '

'; + + echo ' + '; + + // Pour me a cup of javascript. + echo ' + '; + } } diff --git a/Themes/default/UpgradeTemplate.php b/Themes/default/UpgradeTemplate.php new file mode 100644 index 00000000000..664085d1f48 --- /dev/null +++ b/Themes/default/UpgradeTemplate.php @@ -0,0 +1,966 @@ +'; + } + + /** + * Lower template for upgrader. + */ + public static function lower(): void + { + if (!empty(Maintenance::$context['pause'])) { + echo ' + ', Lang::getTxt('upgrade_incomplete', file: 'Maintenance'), '.
+ +

', Lang::getTxt('upgrade_not_quite_done', file: 'Maintenance'), '

+

+ ', Lang::getTxt('upgrade_paused_overload', file: 'Maintenance'), ' +

'; + } + + + if (!empty(Maintenance::$context['continue']) || !empty(Maintenance::$context['skip']) || !empty(Maintenance::$context['try_again'])) { + echo ' +
'; + + if (!empty(Maintenance::$context['continue'])) { + echo ' + '; + } + + if (!empty(Maintenance::$context['try_again'])) { + echo ' + '; + } + + if (!empty(Maintenance::$context['skip'])) { + echo ' + '; + } + + echo ' +
'; + } + + echo ' + '; + + echo ' + '; + + // Are we on a pause? + if (!empty(Maintenance::$context['pause'])) { + echo ' + '; + } + } + + /** + * Welcome page for upgrader. + */ + public static function welcomeLogin(): void + { + echo ' +
+
+

', Lang::getTxt('critical_error', file: 'Maintenance'), '

+ ', Lang::getTxt('error_no_javascript', file: 'Maintenance'), ' +
+
+ '; + + echo ' + +

', Lang::getTxt('upgrade_ready_proceed', ['SMF_VERSION' => SMF_VERSION]), '

+ + '; + + MaintenanceTemplate::warningsAndErrors(); + + if (!empty(Maintenance::$fatal_error)) { + return; + } + + // Show a CHMOD form. + self::chmod(); + + if (!empty(Maintenance::$context['chmod']['files'])) { + return; + } + + // For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade! + if (Maintenance::$context['is_large_forum']) { + echo ' +
+

', Lang::getTxt('error_warning_notice', file: 'Maintenance'), '

+ ', Lang::getTxt('upgrade_warning_lots_data', file: 'Maintenance'), ' +
'; + } + + // Paths are incorrect? + echo ' +
+

', Lang::getTxt('critical_error', file: 'Maintenance'), '

+ ', Lang::getTxt('upgrade_error_script_js', ['url' => 'https://download.simplemachines.org/?tools']), ' +
'; + + // Is there someone already doing this? + if ( + !empty(Maintenance::$context['user']['id']) + && ( + time() - Maintenance::$context['started'] < 72600 + || time() - Maintenance::$context['updated'] < 3600 + ) + ) { + echo ' +
+

', Lang::getTxt('upgrade_warning', file: 'Maintenance'), '

+

', Lang::getTxt('upgrade_time_user', Maintenance::$context['user']), '

+

', self::timeAgo(Maintenance::$context['started'], 'upgrade_time'), '

+

', self::timeAgo(Maintenance::$context['updated'], 'upgrade_time_updated'), '

'; + + if (time() - Maintenance::$context['updated'] < 600) { + echo ' +

', Lang::getTxt('upgrade_run_script', file: 'Maintenance'), ' ', Maintenance::$context['user']['name'], ' ', Lang::getTxt('upgrade_run_script2', file: 'Maintenance'), '

'; + } + + if ((time() - Maintenance::$context['updated']) > Maintenance::$tool->inactive_timeout) { + echo ' +

', Lang::getTxt('upgrade_run', file: 'Maintenance'), '

'; + } elseif (Maintenance::$tool->inactive_timeout > 120) { + echo ' +

', Lang::getTxt('upgrade_script_timeout_minutes', ['name' => Maintenance::$context['user']['name'], 'timeout' => round(Maintenance::$tool->inactive_timeout / 60, 1)]), '

'; + } else { + echo ' +

', Lang::getTxt('upgrade_script_timeout_seconds', ['name' => Maintenance::$context['user']['name'], 'timeout' => Maintenance::$tool->inactive_timeout]), '

'; + } + + echo ' +
'; + } + + echo ' +

+ ', Lang::getTxt('upgrade_admin_login', file: 'Maintenance'), ' ', Maintenance::$disable_security ? Lang::getTxt('upgrade_admin_disabled', file: 'Maintenance') : '', ' +
+ ', Lang::getTxt('upgrade_sec_login', file: 'Maintenance'), ' +

+
+
+ +
+
+ '; + + if (!empty($upcontext['username_incorrect'])) { + echo ' +
', Lang::getTxt('upgrade_wrong_username', file: 'Maintenance'), '
'; + } + + echo ' +
+
+ +
+
+ '; + + if (!empty($upcontext['password_failed'])) { + echo ' +
', Lang::getTxt('upgrade_wrong_password', file: 'Maintenance'), '
'; + } + + echo ' +
'; + + // Can they continue? + if ( + !empty(Maintenance::$context['user']['id']) + && time() - (Maintenance::$context['user']['updated'] ?? 0) >= Maintenance::$tool->inactive_timeout + && (Maintenance::$context['user']['step'] ?? 0) > 1 + ) { + echo ' +
+ +
'; + } + + echo ' +
+

+ ', Lang::getTxt('upgrade_bypass', file: 'Maintenance'), ' +

'; + + if (!empty(Maintenance::$context['login_token_var'])) { + echo ' + '; + } + + echo ' + + '; + + // Say we want the continue button! + Maintenance::$context['continue'] = !empty(Maintenance::$context['user']['id']) && time() - Maintenance::$context['updated'] < Maintenance::$tool->inactive_timeout ? 2 : 1; + + // This defines whether javascript is going to work elsewhere :D + echo ' + '; + } + + /** + * Upgrade options template. + */ + public static function upgradeOptions(): void + { + echo ' +

', Lang::getTxt('upgrade_areyouready', file: 'Maintenance'), '

'; + + MaintenanceTemplate::warningsAndErrors(); + + if (!empty(Maintenance::$fatal_error)) { + return; + } + + echo ' +
+
+ +
+ ', Lang::getTxt(empty(Maintenance::$context['backup_recommended']) ? 'upgrade_backup_already_exists' : 'upgrade_recommended', file: 'Maintenance'), ' +
+
+ +
+
+ + (', Lang::getTxt('upgrade_customize', file: 'Maintenance'), ') +
+
+ +
+ + + + +
+ +
+
+ +
+
+ +
+
+ +
'; + + if (!empty(Maintenance::$context['karma_installed']['good']) || !empty(Maintenance::$context['karma_installed']['bad'])) { + echo ' +
+ +
+
+ +
'; + } + + // If attachment step has been run previously, offer an option to do it again. + // Helpful if folks had improper attachment folders specified previously. + if (!empty(Maintenance::$context['attachment_conversion'])) { + echo ' +
+ +
+
+ +
'; + } + + echo ' +
+ +
+ ', Lang::getTxt('upgrade_stats_info', ['url' => 'https://www.simplemachines.org/about/stats.php']), ' +
+
+ +
+
+ +
+
+ +
+
+ '; + } + + /** + * Backup database template. + */ + public static function backupDatabase(): void + { + MaintenanceTemplate::warningsAndErrors(); + + if (!empty(Maintenance::$fatal_error)) { + return; + } + + // Show the continue button. + Maintenance::$context['continue'] = true; + + echo ' +

', Lang::getTxt('upgrade_wait', file: 'Maintenance'), '

+ + ', Lang::getTxt('upgrade_completedtables_outof', Maintenance::$context), ' +
+ +
+

+ ', Lang::getTxt('upgrade_current_table', file: 'Maintenance'), ' "', Maintenance::$context['cur_table_name'], '" +

+

', Lang::getTxt('upgrade_backup_complete', file: 'Maintenance'), '

+ '; + + // Pour me a cup of javascript. + echo ' + '; + } + + /** + * Migrations template. + */ + public static function migrations(): void + { + MaintenanceTemplate::warningsAndErrors(); + + if (!empty(Maintenance::$fatal_error)) { + return; + } + + // Continue please! + Maintenance::$context['continue'] = true; + Maintenance::$context['try_again'] = true; + + self::showStepWithSubSteps('migration', 'database_done'); + } + + /** + * Cleanup template. + */ + public static function cleanup(): void + { + MaintenanceTemplate::warningsAndErrors(); + + if (!empty(Maintenance::$fatal_error)) { + return; + } + + // Show the continue button. + Maintenance::$context['continue'] = true; + + self::showStepWithSubSteps('cleanup', 'cleanup_done'); + } + + /** + * Finalization template. + */ + public static function finalize(): void + { + Maintenance::$context['continue'] = true; + + MaintenanceTemplate::warningsAndErrors(); + + if (!empty(Maintenance::$fatal_error)) { + return; + } + + echo ' +

', Lang::getTxt('upgrade_done', ['boardurl' => Config::$boardurl]), '

'; + + if (!empty(Maintenance::$context['can_delete_script'])) { + echo ' +

+ +

+ '; + } + + // Show Upgrade time in debug mode when we completed the upgrade process totally + if (isset(Maintenance::$context['upgrade_completed_time'])) { + echo ' +

' . Maintenance::$context['upgrade_completed_time'] . '

'; + } + + echo ' +

+ ', Lang::getTxt('upgrade_problems', ['url' => 'https://www.simplemachines.org'], file: 'Maintenance'), ' +
+ ', Lang::getTxt('upgrade_luck', file: 'Maintenance'), '
+ Simple Machines +

'; + } + + /************************* + * Internal static methods + *************************/ + + /** + * Shows the HTML for a step that has substeps. + */ + protected static function showStepWithSubSteps(string $type, string $done_param): void + { + echo ' +

', Lang::getTxt('upgrade_executing_substeps', ['type' => $type], file: 'Maintenance'), '

+

', Lang::getTxt('upgrade_please_be_patient', file: 'Maintenance'), '

+ +
+ +
'; + + echo ' +

', + Lang::getTxt( + 'upgrade_current_substep', + [ + 'substep' => '' . (Maintenance::$context['current_substep'] ?? '') . '', + ], + file: 'Maintenance', + ), + '

'; + + echo ' + ', + Lang::getTxt( + 'upgrade_substep_progress', + [ + 'substep_done' => '' . Maintenance::getCurrentSubStep() . '', + 'total_substeps' => Maintenance::$total_substeps, + 'type' => $type, + ], + file: 'Maintenance', + ), + ''; + + echo ' +

', Lang::getTxt('upgrade_step_complete', ['step' => Lang::getTxt('upgrade_step_' . $type, file: 'Maintenance')], file: 'Maintenance'), '

'; + + echo ' + '; + + // Pour me a cup of javascript. + echo ' + '; + } + + /** + * Template for CHMOD. + */ + protected static function chmod() + { + // Don't call me twice! + if (self::$chmod_called) { + return; + } + + self::$chmod_called = true; + + // Nothing? + if ( + empty(Maintenance::$context['chmod']['files']) + && empty(Maintenance::$context['chmod']['ftp_error']) + ) { + return; + } + + // Was it a problem with Windows? + if ( + !empty(Maintenance::$context['chmod']['ftp_error']) + && Maintenance::$context['chmod']['ftp_error'] == 'total_mess' + ) { + echo ' +
+

', Lang::getTxt('upgrade_writable_files', file: 'Maintenance'), '

+
    +
  • ' . implode('
  • +
  • ', Maintenance::$context['chmod']['files']) . '
  • +
+
'; + + return false; + } + + echo ' +
+

', Lang::getTxt('upgrade_ftp_login', file: 'Maintenance'), '

+

', Lang::getTxt('upgrade_ftp_perms', file: 'Maintenance'), '

+ '; + + if (!empty(Maintenance::$context['chmod']['ftp_error'])) { + echo ' +
+

', Lang::getTxt('upgrade_ftp_error', file: 'Maintenance'), '

+ ', Maintenance::$context['chmod']['ftp_error'], ' +

'; + } + + echo ' +
+
+ +
+
+
+ + +
+ +
', Lang::getTxt('ftp_server_info', file: 'Maintenance'), '
+
+
+ +
+
+ +
', Lang::getTxt('ftp_username_info', file: 'Maintenance'), '
+
+
+ +
+
+ +
', Lang::getTxt('ftp_password_info', file: 'Maintenance'), '
+
+
+ +
+
+ +
', !empty(Maintenance::$context['chmod']['path']) ? Lang::getTxt('ftp_path_found_info', file: 'Maintenance') : Lang::getTxt('ftp_path_info', file: 'Maintenance'), '
+
+
+ +
+ +
+
'; + } + + /** + * Provide a simple interface for showing time ago. + */ + protected static function timeAgo(int $timestamp, string $base_key): string + { + $ago = time() - $timestamp; + $ago_hours = floor($ago / 3600); + $ago_minutes = (int) (((int) ($ago / 60)) % 60); + $ago_seconds = intval($ago % 60); + $txt_suffix = $ago < 60 ? '_s' : ($ago < 3600 ? '_ms' : '_hms'); + + return Lang::getTxt($base_key . $txt_suffix, ['s' => $ago_seconds, 'm' => $ago_minutes, 'h' => $ago_hours]); + } +} diff --git a/Themes/default/css/maintenance.css b/Themes/default/css/maintenance.css index 016f9c2e9cb..21dacdf6e15 100644 --- a/Themes/default/css/maintenance.css +++ b/Themes/default/css/maintenance.css @@ -131,7 +131,7 @@ ul.steps_list .stepcurrent ~ li { max-height: 8.4em; /* 6 lines of text */ line-height: 1.4em; } -dl.settings dt, dl.settings dd { +dl.settings.adminlogin dt, dl.settings.adminlogin dd { width: 50%; } dl.settings.adminlogin dt { @@ -177,4 +177,4 @@ dl.settings.adminlogin dd { dl.settings dt { margin: 10px 0 0; } -} \ No newline at end of file +} diff --git a/other/upgrade-helper.php b/other/upgrade-helper.php deleted file mode 100644 index cd58ebeb8ad..00000000000 --- a/other/upgrade-helper.php +++ /dev/null @@ -1,500 +0,0 @@ -query( - '', - 'SELECT groupName, id_group - FROM {db_prefix}membergroups - WHERE id_group = {int:admin_group} OR id_group > {int:old_group}', - [ - 'admin_group' => 1, - 'old_group' => 7, - 'db_error_skip' => true, - ], - ); - - if ($request === false) { - $request = SMF\Db\DatabaseApi::$db->query( - '', - 'SELECT membergroup, id_group - FROM {db_prefix}membergroups - WHERE id_group = {int:admin_group} OR id_group > {int:old_group}', - [ - 'admin_group' => 1, - 'old_group' => 7, - 'db_error_skip' => true, - ], - ); - } - - while ($row = SMF\Db\DatabaseApi::$db->fetch_row($request)) { - $member_groups[trim($row[0])] = $row[1]; - } - SMF\Db\DatabaseApi::$db->free_result($request); - - return $member_groups; -} - -/** - * Make files writable. First try to use regular chmod, but if that fails, try to use FTP. - * - * @param $files - * @return bool - */ -function makeFilesWritable(&$files) -{ - global $upcontext; - - if (empty($files)) { - return true; - } - - $failure = false; - - // On linux, it's easy - just use is_writable! - if (substr(__FILE__, 1, 2) != ':\\') { - $upcontext['systemos'] = 'linux'; - - foreach ($files as $k => $file) { - // Some files won't exist, try to address up front - if (!file_exists($file)) { - @touch($file); - } - - // NOW do the writable check... - if (!is_writable($file)) { - @chmod($file, 0755); - - // Well, 755 hopefully worked... if not, try 777. - if (!is_writable($file) && !@chmod($file, 0777)) { - $failure = true; - } - // Otherwise remove it as it's good! - else { - unset($files[$k]); - } - } else { - unset($files[$k]); - } - } - } - // Windows is trickier. Let's try opening for r+... - else { - $upcontext['systemos'] = 'windows'; - - foreach ($files as $k => $file) { - // Folders can't be opened for write... but the index.php in them can ;). - if (is_dir($file)) { - $file .= '/index.php'; - } - - // Funny enough, chmod actually does do something on windows - it removes the read only attribute. - @chmod($file, 0777); - $fp = @fopen($file, 'r+'); - - // Hmm, okay, try just for write in that case... - if (!$fp) { - $fp = @fopen($file, 'w'); - } - - if (!$fp) { - $failure = true; - } else { - unset($files[$k]); - } - @fclose($fp); - } - } - - if (empty($files)) { - return true; - } - - if (!isset($_SERVER)) { - return !$failure; - } - - // What still needs to be done? - $upcontext['chmod']['files'] = $files; - - // If it's windows it's a mess... - if ($failure && substr(__FILE__, 1, 2) == ':\\') { - $upcontext['chmod']['ftp_error'] = 'total_mess'; - - return false; - } - - // We're going to have to use... FTP! - if ($failure) { - // Load any session data we might have... - if (!isset($_POST['ftp_username']) && isset($_SESSION['installer_temp_ftp'])) { - $upcontext['chmod']['server'] = $_SESSION['installer_temp_ftp']['server']; - $upcontext['chmod']['port'] = $_SESSION['installer_temp_ftp']['port']; - $upcontext['chmod']['username'] = $_SESSION['installer_temp_ftp']['username']; - $upcontext['chmod']['password'] = $_SESSION['installer_temp_ftp']['password']; - $upcontext['chmod']['path'] = $_SESSION['installer_temp_ftp']['path']; - } - // Or have we submitted? - elseif (isset($_POST['ftp_username'])) { - $upcontext['chmod']['server'] = $_POST['ftp_server']; - $upcontext['chmod']['port'] = $_POST['ftp_port']; - $upcontext['chmod']['username'] = $_POST['ftp_username']; - $upcontext['chmod']['password'] = $_POST['ftp_password']; - $upcontext['chmod']['path'] = $_POST['ftp_path']; - } - - require_once \SMF\Config::$sourcedir . '/PackageManager/FtpConnection.php'; - - if (isset($upcontext['chmod']['username'])) { - $ftp = new \SMF\PackageManager\FtpConnection($upcontext['chmod']['server'], $upcontext['chmod']['port'], $upcontext['chmod']['username'], $upcontext['chmod']['password']); - - if ($ftp->error === false) { - // Try it without /home/abc just in case they messed up. - if (!$ftp->chdir($upcontext['chmod']['path'])) { - $upcontext['chmod']['ftp_error'] = $ftp->last_message; - $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $upcontext['chmod']['path'])); - } - } - } - - if (!isset($ftp) || $ftp->error !== false) { - if (!isset($ftp)) { - $ftp = new \SMF\PackageManager\FtpConnection(null); - } - // Save the error so we can mess with listing... - elseif ($ftp->error !== false && !isset($upcontext['chmod']['ftp_error'])) { - $upcontext['chmod']['ftp_error'] = $ftp->last_message === null ? '' : $ftp->last_message; - } - - list($username, $detect_path, $found_path) = $ftp->detect_path(dirname(__FILE__)); - - if ($found_path || !isset($upcontext['chmod']['path'])) { - $upcontext['chmod']['path'] = $detect_path; - } - - if (!isset($upcontext['chmod']['username'])) { - $upcontext['chmod']['username'] = $username; - } - - // Don't forget the login token. - $upcontext += \SMF\SecurityToken::create('login'); - - return false; - } - - - // We want to do a relative path for FTP. - if (!in_array($upcontext['chmod']['path'], ['', '/'])) { - $ftp_root = strtr(\SMF\Config::$boarddir, [$upcontext['chmod']['path'] => '']); - - if (str_ends_with($ftp_root, '/') && ($upcontext['chmod']['path'] == '' || $upcontext['chmod']['path'][0] === '/')) { - $ftp_root = substr($ftp_root, 0, -1); - } - } else { - $ftp_root = \SMF\Config::$boarddir; - } - - // Save the info for next time! - $_SESSION['installer_temp_ftp'] = [ - 'server' => $upcontext['chmod']['server'], - 'port' => $upcontext['chmod']['port'], - 'username' => $upcontext['chmod']['username'], - 'password' => $upcontext['chmod']['password'], - 'path' => $upcontext['chmod']['path'], - 'root' => $ftp_root, - ]; - - foreach ($files as $k => $file) { - if (!is_writable($file)) { - $ftp->chmod($file, 0755); - } - - if (!is_writable($file)) { - $ftp->chmod($file, 0777); - } - - // Assuming that didn't work calculate the path without the boarddir. - if (!is_writable($file)) { - if (str_starts_with($file, \SMF\Config::$boarddir)) { - $ftp_file = strtr($file, [$_SESSION['installer_temp_ftp']['root'] => '']); - $ftp->chmod($ftp_file, 0755); - - if (!is_writable($file)) { - $ftp->chmod($ftp_file, 0777); - } - // Sometimes an extra slash can help... - $ftp_file = '/' . $ftp_file; - - if (!is_writable($file)) { - $ftp->chmod($ftp_file, 0755); - } - - if (!is_writable($file)) { - $ftp->chmod($ftp_file, 0777); - } - } - } - - if (is_writable($file)) { - unset($files[$k]); - } - } - - $ftp->close(); - - } - - // What remains? - $upcontext['chmod']['files'] = $files; - - return (bool) (empty($files)); -} - -/** - * The quick version of makeFilesWritable, which does not support FTP. - * - * @param string $file - * @return bool - */ -function quickFileWritable($file) -{ - // Some files won't exist, try to address up front - if (!file_exists($file)) { - @touch($file); - } - - // NOW do the writable check... - if (is_writable($file)) { - return true; - } - - @chmod($file, 0755); - - // Try 755 and 775 first since 777 doesn't always work and could be a risk... - $chmod_values = [0755, 0775, 0777]; - - foreach ($chmod_values as $val) { - // If it's writable, break out of the loop - if (is_writable($file)) { - break; - } - - - @chmod($file, $val); - } - - return is_writable($file); -} - -/** - * Delete a file. Check permissions first, just in case. - * - * @param string $file - */ -function deleteFile($file) -{ - if (!file_exists($file)) { - return; - } - - quickFileWritable($file); - - @unlink($file); - - -} - -/** - * Prints an error to stderr. - * - * @param $message - * @param bool $fatal - */ -function print_error($message, $fatal = false) -{ - static $fp = null; - - if ($fp === null) { - $fp = fopen('php://stderr', 'wb'); - } - - fwrite($fp, $message . "\n"); - - if ($fatal) { - exit; - } -} - -/** - * Throws a graphical error message. - * - * @param $message - * @return bool - */ -function throw_error($message) -{ - global $upcontext; - - $upcontext['error_msg'] = $message; - $upcontext['sub_template'] = 'error_message'; - - return false; -} - -/** - * Database functions below here. - */ -/** - * @param $rs - * @return array|null - */ -function smf_mysql_fetch_assoc($rs) -{ - return mysqli_fetch_assoc($rs); -} - -/** - * @param $rs - * @return array|null - */ -function smf_mysql_fetch_row($rs) -{ - return mysqli_fetch_row($rs); -} - -/** - * @param $rs - */ -function smf_mysql_free_result($rs) -{ - mysqli_free_result($rs); -} - -/** - * @param $rs Ignored - * @return int|string - */ -function smf_mysql_insert_id($rs = null) -{ - return mysqli_insert_id(SMF\Db\DatabaseApi::$db_connection); -} - -/** - * @param $rs - * @return int - */ -function smf_mysql_num_rows($rs) -{ - return mysqli_num_rows($rs); -} - -/** - * @param $string - */ -function smf_mysql_real_escape_string($string) -{ - return mysqli_real_escape_string(SMF\Db\DatabaseApi::$db_connection, $string); -} - -/* - * Substitute for array_column() for use in php 5.4 - * - * @param $array to search - * @param $col to select - * @param $index to use as index if specified - * @return array of values of specified $col from $array - */ -if (!function_exists('array_column')) { - function array_column($input, $column_key, $index_key = null) - { - $arr = array_map( - function ($d) use ($column_key, $index_key) { - if (!isset($d[$column_key])) { - return; - } - - if ($index_key !== null) { - return [$d[$index_key] => $d[$column_key]]; - } - - return $d[$column_key]; - }, - $input, - ); - - if ($index_key !== null) { - $tmp = []; - - foreach ($arr as $ar) { - $tmp[key($ar)] = current($ar); - } - $arr = $tmp; - } - - return $arr; - } -} - -/** - * Creates the json_encoded array for the current cache option. - * - * @return string a json_encoded array with the selected API options - */ -function upgradeCacheSettings() -{ - $cache_options = [ - 'smf' => 'FileBase', - 'apc' => 'FileBase', - 'apcu' => 'Apcu', - 'memcache' => 'MemcacheImplementation', - 'memcached' => 'MemcachedImplementation', - 'postgres' => 'Postgres', - 'sqlite' => 'Sqlite', - 'xcache' => 'FileBase', - 'zend' => 'Zend', - ]; - - $current_cache = !empty($GLOBALS['cache_accelerator']) ? $GLOBALS['cache_accelerator'] : 'smf'; - - return $cache_options[$current_cache]; -} diff --git a/other/upgrade.php b/other/upgrade.php index c1df58329c8..7f5ee03532e 100644 --- a/other/upgrade.php +++ b/other/upgrade.php @@ -11,5971 +11,14 @@ * @version 3.0 Alpha 3 */ -use SMF\Config; -use SMF\Db\DatabaseApi as Db; -use SMF\Lang; -use SMF\QueryString; -use SMF\Sapi; -use SMF\Security; -use SMF\SecurityToken; -use SMF\TaskRunner; -use SMF\User; -use SMF\Utils; -use SMF\Uuid; -use SMF\WebFetch\WebFetchApi; +declare(strict_types=1); -// Version information... -define('SMF_VERSION', '3.0 Alpha 3'); -define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION); -define('SMF_SOFTWARE_YEAR', '2025'); -define('SMF_LANG_VERSION', '3.0 Alpha 3'); +define('SMF', 'UPGRADE'); define('SMF_INSTALLING', 1); -define('JQUERY_VERSION', '3.6.3'); -define('POSTGRE_TITLE', 'PostgreSQL'); -define('MYSQL_TITLE', 'MySQL'); -define('SMF_USER_AGENT', 'Mozilla/5.0 (' . php_uname('s') . ' ' . php_uname('m') . ') AppleWebKit/605.1.15 (KHTML, like Gecko) SMF/' . strtr(SMF_VERSION, ' ', '.')); +// Initialize. +require_once __DIR__ . '/index.php'; -if (!defined('TIME_START')) { - define('TIME_START', microtime(true)); -} +SMF\Maintenance\Maintenance::$disable_security = false; -/* - * The minimum required PHP version. - * - * @var string - */ -$GLOBALS['required_php_version'] = '8.0.0'; - -/** - * A list of supported database systems. - * - * @var array - */ -$databases = [ - 'mysql' => [ - 'name' => 'MySQL', - 'version' => '8.0.35', - 'version_check' => function () { - if (Db::$db->title !== MYSQL_TITLE) { - return ''; - } - - return Db::$db->get_version(); - }, - 'alter_support' => true, - ], - 'postgresql' => [ - 'name' => 'PostgreSQL', - 'version' => '12.17', - 'version_check' => function () { - if (Db::$db->title !== POSTGRE_TITLE) { - return ''; - } - - return Db::$db->get_version(); - }, - 'always_has_db' => true, - ], -]; - -/** - * The maximum time a single substep may take, in seconds. - * - * @var int - */ -$timeLimitThreshold = 3; - -/** - * The current path to the upgrade.php file. - * - * @var string - */ -$upgrade_path = dirname(__FILE__); - -/** - * The URL of the current page. - * - * @var string - */ -$upgradeurl = $_SERVER['PHP_SELF']; - -/** - * Flag to disable the required administrator login. - * - * @var bool - */ -$disable_security = false; - -/* - * The amount of seconds allowed between logins. - * If the first user to login is inactive for this amount of seconds, a second login is allowed. - * - * @var int - */ -$upcontext['inactive_timeout'] = 10; - -// All the steps in detail. -// Number,Name,Function,Progress Weight. -$upcontext['steps'] = [ - 0 => [1, 'upgrade_step_login', 'WelcomeLogin', 1], - 1 => [2, 'upgrade_step_options', 'UpgradeOptions', 1], - 2 => [3, 'upgrade_step_backup', 'BackupDatabase', 10], - 3 => [4, 'upgrade_step_database', 'DatabaseChanges', 50], - 4 => [5, 'upgrade_step_convertjson', 'serialize_to_json', 10], - 5 => [6, 'upgrade_step_convertutf', 'ConvertUtf8', 20], - 6 => [7, 'upgrade_step_cleanup', 'Cleanup', 2], - 7 => [8, 'upgrade_step_delete', 'DeleteUpgrade', 1], -]; -// Just to remember which one has files in it. -$upcontext['database_step'] = 3; - -// Secure some resources -@ini_set('mysql.connect_timeout', -1); -@ini_set('default_socket_timeout', 900); -@ini_set('memory_limit', '512M'); - -// Clean the upgrade path if this is from the client. -if (!empty($_SERVER['argv']) && php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) { - for ($i = 1; $i < $_SERVER['argc']; $i++) { - // Provide the help without possible errors if the environment isn't sane. - if (in_array($_SERVER['argv'][$i], ['-h', '--help'])) { - cmdStep0(); - - exit; - } - - if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0) { - $upgrade_path = realpath(str_ends_with($match[1], '/') ? substr($match[1], 0, -1) : $match[1]); - } - - // Cases where we do php other/upgrade.php --path=./ - if ($upgrade_path == './' && isset($_SERVER['PWD'])) { - $upgrade_path = realpath($_SERVER['PWD']); - } - // Cases where we do php upgrade.php --path=../ - elseif ($upgrade_path == '../' && isset($_SERVER['PWD'])) { - $upgrade_path = dirname(realpath($_SERVER['PWD'])); - } - } -} - -// Are we from the client? -if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) { - $command_line = true; - $disable_security = true; -} else { - $command_line = false; -} - -// Set SMF_SETTINGS_FILE to the correct path. -findSettingsFile(); - -// We can't do anything without these files. -foreach ([ - dirname(__FILE__) . '/upgrade-helper.php', - SMF_SETTINGS_FILE, -] as $required_file) { - if (!file_exists($required_file)) { - die(basename($required_file) . ' was not found where it was expected: ' . $required_file . '! Make sure you have uploaded ALL files from the upgrade package to your forum\'s root directory. The upgrader cannot continue.'); - } - - require_once $required_file; -} - -// Fire up the autoloader, SMF\Config, and SMF\Utils. -require_once $sourcedir . '/Autoloader.php'; -Config::load(); -Utils::load(); - -// We don't use "-utf8" anymore... Tweak the entry that may have been loaded by Settings.php -if (isset(Config::$language)) { - Config::$language = str_ireplace('-utf8', '', basename(Config::$language, '.lng')); -} - -// Figure out a valid language request (if any) -// Can't use $_GET until it's been cleaned, so do this manually and VERY restrictively! This even strips off those '-utf8' bits that we don't want. -if (isset($_SERVER['QUERY_STRING']) && preg_match('~\blang=(\w+)~', $_SERVER['QUERY_STRING'], $matches)) { - $upcontext['lang'] = $matches[1]; -} - -// Are we logged in? -if (isset($upgradeData)) { - $upcontext['user'] = json_decode(base64_decode($upgradeData), true); - - // Check for sensible values. - if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400) { - $upcontext['user']['started'] = time(); - } - - if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400) { - $upcontext['user']['updated'] = 0; - } - - $upcontext['started'] = $upcontext['user']['started']; - $upcontext['updated'] = $upcontext['user']['updated']; - - $is_debug = !empty($upcontext['user']['debug']) ? true : false; - - $upcontext['skip_db_substeps'] = !empty($upcontext['user']['skip_db_substeps']); -} - -// Nothing sensible? -if (empty($upcontext['updated'])) { - $upcontext['started'] = time(); - $upcontext['updated'] = 0; - $upcontext['skip_db_substeps'] = false; - $upcontext['user'] = [ - 'id' => 0, - 'name' => 'Guest', - 'pass' => 0, - 'started' => $upcontext['started'], - 'updated' => $upcontext['updated'], - ]; -} - -// Try to load the language file... or at least define a few necessary strings for now. -load_lang_file(); - -// Load up some essential data... -loadEssentialData(); - -// Are we going to be mimic'ing SSI at this point? -if (isset($_GET['ssi'])) { - User::load(); - User::$me->loadPermissions(); - Config::reloadModSettings(); -} - -// Don't do security check if on Yabbse -if (!isset(Config::$modSettings['smfVersion'])) { - $disable_security = true; -} - -// This only exists if we're on SMF ;) -if (isset(Config::$modSettings['smfVersion'])) { - $request = Db::$db->query( - '', - 'SELECT variable, value - FROM {db_prefix}themes - WHERE id_theme = {int:id_theme} - AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})', - [ - 'id_theme' => 1, - 'theme_url' => 'theme_url', - 'theme_dir' => 'theme_dir', - 'images_url' => 'images_url', - 'db_error_skip' => true, - ], - ); - - while ($row = Db::$db->fetch_assoc($request)) { - Config::$modSettings[$row['variable']] = $row['value']; - } - Db::$db->free_result($request); -} - -if (!isset(Config::$modSettings['theme_url'])) { - Config::$modSettings['theme_dir'] = Config::$boarddir . '/Themes/default'; - Config::$modSettings['theme_url'] = 'Themes/default'; - Config::$modSettings['images_url'] = 'Themes/default/images'; -} - -if (!isset($settings['default_theme_url'])) { - $settings['default_theme_url'] = Config::$modSettings['theme_url']; -} - -if (!isset($settings['default_theme_dir'])) { - $settings['default_theme_dir'] = Config::$modSettings['theme_dir']; -} - -// Old DBs won't have this -if (!isset(Config::$modSettings['rand_seed'])) { - Config::generateSeed(); -} - -// This is needed in case someone invokes the upgrader using https when upgrading an http forum -if (Sapi::httpsOn()) { - $settings['default_theme_url'] = strtr($settings['default_theme_url'], ['http://' => 'https://']); -} - -$upcontext['is_large_forum'] = (empty(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] <= '1.1 RC1') && !empty(Config::$modSettings['totalMessages']) && Config::$modSettings['totalMessages'] > 75000; - -// Have we got tracking data - if so use it (It will be clean!) -if (isset($_GET['data'])) { - global $is_debug; - - $upcontext['upgrade_status'] = json_decode(base64_decode($_GET['data']), true); - $upcontext['current_step'] = $upcontext['upgrade_status']['curstep']; - $upcontext['language'] = $upcontext['upgrade_status']['lang']; - $upcontext['rid'] = $upcontext['upgrade_status']['rid']; - $support_js = $upcontext['upgrade_status']['js']; - - // Only set this if the upgrader status says so. - if (empty($is_debug)) { - $is_debug = $upcontext['upgrade_status']['debug']; - } -} -// Set the defaults. -else { - $upcontext['current_step'] = 0; - $upcontext['rid'] = mt_rand(0, 5000); - $upcontext['upgrade_status'] = [ - 'curstep' => 0, - 'lang' => $upcontext['lang'] ?? Lang::getLocaleFromLanguageName(Config::$language), - 'rid' => $upcontext['rid'], - 'pass' => 0, - 'debug' => 0, - 'js' => 0, - ]; - $upcontext['language'] = $upcontext['upgrade_status']['lang']; -} - -// Now that we have the necessary info, make sure we loaded the right language file. -load_lang_file(); - -// Default title... -$upcontext['page_title'] = Lang::$txt['updating_smf_installation']; - -// If this isn't the first stage see whether they are logging in and resuming. -if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step'])) { - checkLogin(); -} - -if ($command_line) { - cmdStep0(); -} - -// Don't error if we're using xml. -if (isset($_GET['xml'])) { - $upcontext['return_error'] = true; -} - -// Loop through all the steps doing each one as required. -$upcontext['overall_percent'] = 0; - -foreach ($upcontext['steps'] as $num => $step) { - if ($num >= $upcontext['current_step']) { - // The current weight of this step in terms of overall progress. - $upcontext['step_weight'] = $step[3]; - // Make sure we reset the skip button. - $upcontext['skip'] = false; - - // We cannot proceed if we're not logged in. - if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass']) { - $upcontext['steps'][0][2](); - break; - } - - // Call the step and if it returns false that means pause! - if (function_exists($step[2]) && $step[2]() === false) { - break; - } - - if (function_exists($step[2])) { - //Start each new step with this unset, so the 'normal' template is called first - unset($_GET['xml'], $upcontext['custom_warning']); - //Clear out warnings at the start of each step - - $_GET['substep'] = 0; - $upcontext['current_step']++; - } - } - $upcontext['overall_percent'] += $step[3]; -} - -upgradeExit(); - -// Exit the upgrade script. -function upgradeExit($fallThrough = false) -{ - global $upcontext, $upgradeurl, $command_line, $is_debug; - - // Save where we are... - if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id'])) { - $upcontext['user']['step'] = $upcontext['current_step']; - $upcontext['user']['substep'] = $_GET['substep']; - $upcontext['user']['updated'] = time(); - $upcontext['user']['skip_db_substeps'] = !empty($upcontext['skip_db_substeps']); - $upcontext['debug'] = $is_debug; - $upgradeData = base64_encode(json_encode($upcontext['user'])); - Config::updateSettingsFile(['upgradeData' => $upgradeData]); - Config::updateDbLastError(0); - } - - // Handle the progress of the step, if any. - if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']])) { - $upcontext['step_progress'] = round($upcontext['step_progress'], 1); - $upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100); - } - $upcontext['overall_percent'] = (int) $upcontext['overall_percent']; - - // We usually dump our templates out. - if (!$fallThrough) { - // This should not happen my dear... HELP ME DEVELOPERS!! - if (!empty($command_line)) { - if (function_exists('debug_print_backtrace')) { - debug_print_backtrace(); - } - - echo "\n" . Lang::getTxt('error_unexpected_template_call', ['sub_template' => $upcontext['sub_template'] ?? '']); - flush(); - - die(); - } - - if (!isset($_GET['xml'])) { - template_upgrade_above(); - } else { - header('content-type: text/xml; charset=UTF-8'); - // Sadly we need to retain the $_GET data thanks to the old upgrade scripts. - $upcontext['get_data'] = []; - - foreach ($_GET as $k => $v) { - if (!str_starts_with($k, 'amp') && !in_array($k, ['xml', 'substep', 'lang', 'data', 'step', 'filecount'])) { - $upcontext['get_data'][$k] = $v; - } - } - template_xml_above(); - } - - // Call the template. - if (isset($upcontext['sub_template'])) { - $upcontext['upgrade_status']['curstep'] = $upcontext['current_step']; - $upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])); - - // Custom stuff to pass back? - if (!empty($upcontext['query_string'])) { - $upcontext['form_url'] .= $upcontext['query_string']; - } - - // Call the appropriate subtemplate - if (is_callable('template_' . $upcontext['sub_template'])) { - call_user_func('template_' . $upcontext['sub_template']); - } else { - die(Lang::getTxt('error_invalid_template', $upcontext)); - } - } - - // Was there an error? - if (!empty($upcontext['forced_error_message'])) { - echo $upcontext['forced_error_message']; - } - - // Show the footer. - if (!isset($_GET['xml'])) { - template_upgrade_below(); - } else { - template_xml_below(); - } - } - - // Show the upgrade time for CLI when we are completely done, if in debug mode. - if (!empty($command_line) && $is_debug) { - $active = time() - $upcontext['started']; - $hours = floor($active / 3600); - $minutes = intval(($active / 60) % 60); - $seconds = intval($active % 60); - - if ($hours > 0) { - echo "\n" . '', Lang::getTxt('upgrade_completed_time_hms', ['h' => $hours, 'm' => $minutes, 's' => $seconds]), '' . "\n"; - } elseif ($minutes > 0) { - echo "\n" . '', Lang::getTxt('upgrade_completed_time_ms', ['m' => $minutes, 's' => $seconds]), '' . "\n"; - } elseif ($seconds > 0) { - echo "\n" . '', Lang::getTxt('upgrade_completed_time_s', ['s' => $seconds]), '' . "\n"; - } - } - - // Bang - gone! - die(); -} - -// Figures out the path to Settings.php. -function findSettingsFile() -{ - global $upgrade_path; - - // Start by assuming the default path. - $settingsFile = $upgrade_path . '/Settings.php'; - - // Check for a custom path in index.php. - if (is_file($upgrade_path . '/index.php')) { - $index_contents = file_get_contents($upgrade_path . '/index.php'); - - // The standard path. - if (str_contains($index_contents, "define('SMF_SETTINGS_FILE', __DIR__ . '/Settings.php');")) { - $settingsFile = $upgrade_path . '/Settings.php'; - } - // A custom path defined in a simple string. - elseif (preg_match('~' . - '\bdefine\s*\(\s*(["\'])SMF_SETTINGS_FILE\1\s*,\s*' . - '(' . - // match the opening quotation mark... - '(["\'])' . - // then any number of other characters or escaped quotation marks... - '(?:.(?!\\3)|\\\(?=\\3))*.?' . - // then the closing quotation mark. - '\\3' . - ')' . - '\s*\)\s*;~u', $index_contents, $matches)) { - $possibleSettingsFile = strtr(substr($matches[2], 1, -1), ['\\' . $matches[3] => $matches[3]]); - - if (is_file($possibleSettingsFile)) { - $settingsFile = $possibleSettingsFile; - } - } - // @todo Test for other possibilities here? - } - - define('SMF_SETTINGS_FILE', $settingsFile); - define('SMF_SETTINGS_BACKUP_FILE', dirname(SMF_SETTINGS_FILE) . '/' . pathinfo(SMF_SETTINGS_FILE, PATHINFO_FILENAME) . '_bak.php'); -} - -// Load the list of language files, and the current language file. -function load_lang_file() -{ - global $upcontext, $upgrade_path, $command_line; - - static $lang_dir = '', $detected_languages = [], $loaded_langfile = ''; - - if (isset(Config::$language)) { - $current_language = Lang::getLocaleFromLanguageName(Config::$language); - } - - if (isset($upcontext['language'])) { - $locale = Lang::getLocaleFromLanguageName($upcontext['language']); - $upcontext['language'] = $locale ?? $upcontext['language']; - } - - $lang_dir = !empty(Config::$languagesdir) ? Config::$languagesdir : fixRelativePath(Config::$boarddir) . '/Languages'; - - // Override the language file? - if (isset($upcontext['language']) && file_exists($lang_dir . '/' . $upcontext['language'] . '/Maintenance.php')) { - $_SESSION['upgrader_lang'] = $upcontext['language']; - } elseif (isset($upcontext['lang']) && file_exists($lang_dir . '/' . $upcontext['lang'] . '/Maintenance.php')) { - $_SESSION['upgrader_lang'] = $upcontext['lang']; - } elseif (isset($current_language) && file_exists($lang_dir . '/' . $current_language . '/Maintenance.php')) { - $_SESSION['upgrader_lang'] = $current_language; - } else { - $_SESSION['upgrader_lang'] = 'en_US'; - } - - // Avoid pointless repetition - if (isset($_SESSION['upgrader_lang']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Maintenance.php') { - return; - } - - // Now try to find the language files - if (empty($detected_languages)) { - // Make sure the languages directory actually exists. - if (file_exists($lang_dir)) { - // Find all the "Maintenance" language files in the directory. - $dir = dir($lang_dir); - - while ($entry = $dir->read()) { - // We can't have periods. - if (str_contains($entry, '.')) { - continue; - } - - if (!is_dir($lang_dir . '/' . $entry) || !file_exists($lang_dir . '/' . $entry . '/' . 'Maintenance.php') || !file_exists($lang_dir . '/' . $entry . '/' . 'General.php')) { - continue; - } - - // Get the line we need. - $fp = @fopen($lang_dir . '/' . $entry . '/' . 'General.php', 'r'); - - // Yay! - if ($fp) { - while (($line = fgets($fp)) !== false) { - if (!str_contains($line, '$txt[\'native_name\']')) { - continue; - } - - preg_match('~\$txt\[\'native_name\'\]\s*=\s*\'([^\']+)\';~', $line, $matchNative); - - // Set the language's name. - if (!empty($matchNative) && !empty($matchNative[1])) { - // Don't mislabel the language if the translator missed this one. - if ($entry !== 'en_US' && $matchNative[1] === 'English (US)') { - break; - } - - $langName = Utils::htmlspecialcharsDecode($matchNative[1]); - break; - } - } - - fclose($fp); - } - - $detected_languages[$entry] = $langName ?? $entry; - } - $dir->close(); - } - // Our guess was wrong, but that's fine. We'll try again after Config::$languagesdir is defined. - elseif (!isset(Config::$languagesdir)) { - // Define a few essential strings for now. - Lang::$txt['error_db_connect_settings'] = 'Cannot connect to the database server.

Please check that the database info variables are correct in Settings.php.'; - Lang::$txt['error_sourcefile_missing'] = 'Unable to find the Sources/{file} file. Please make sure it was uploaded properly, and then try again.'; - - Lang::$txt['warning_lang_old'] = 'The language files for your selected language, {user_language}, have not been updated to the latest version. Upgrade will continue with the forum default, {default_language}.'; - Lang::$txt['warning_lang_missing'] = 'The upgrader could not find the "Maintenance" language file for your selected language, {user_language}. Upgrade will continue with the forum default, {default_language}.'; - - return; - } - - } - - // Didn't find any, show an error message! - if (empty($detected_languages)) { - $from = explode('/', $command_line ? $upgrade_path : $_SERVER['PHP_SELF']); - $to = explode('/', $lang_dir); - $relPath = $to; - - foreach($from as $depth => $dir) { - if ($dir === $to[$depth]) { - array_shift($relPath); - } else { - $remaining = count($from) - $depth; - - if ($remaining > 1) { - $padLength = (count($relPath) + $remaining - 1) * -1; - $relPath = array_pad($relPath, $padLength, '..'); - break; - } - - $relPath[0] = './' . $relPath[0]; - } - } - $relPath = implode(DIRECTORY_SEPARATOR, $relPath); - - // Command line? - if ($command_line) { - echo 'This upgrader was unable to find the upgrader\'s language file or files. They should be found under:', "\n", - $relPath, "\n", - 'In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you have uploaded all the files in the distribution', "\n", - 'If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.', "\n"; - - die; - } - - // Let's not cache this message, eh? - header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); - header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); - header('Cache-Control: no-cache'); - - echo ' - - - SMF Upgrader: Error! - - - -

A critical error has occurred.

-

This upgrader was unable to find the upgrader\'s language file or files. They should be found under:

-
', $relPath, '
-

In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you have uploaded all the files in the distribution.

-

If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.

-

If you continue to get this error message, feel free to look to us for support.

- - '; - - die; - } - - // Make sure it exists. If it doesn't, reset it. - if (!isset($_SESSION['upgrader_lang']) || preg_match('~^[A-Za-z0-9_-]+$~', $_SESSION['upgrader_lang']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Maintenance.php')) { - // Use the first one... - list($_SESSION['upgrader_lang']) = array_keys($detected_languages); - - // If we have English and some other language, use the other language. - if ($_SESSION['upgrader_lang'] == 'en_US' && count($detected_languages) > 1) { - list(, $_SESSION['upgrader_lang']) = array_keys($detected_languages); - } - } - - // Ensure SMF\Lang knows the path to the language directory. - Lang::addDirs($lang_dir); - - // And now load the language files. - Lang::load('General+Maintenance', $_SESSION['upgrader_lang']); - - // Remember what we've done - $loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_lang'] . '/Maintenance.php'; -} - -// Used to direct the user to another location. -function redirectLocation($location, $addForm = true) -{ - global $upgradeurl, $upcontext, $command_line; - - // Command line users can't be redirected. - if ($command_line) { - upgradeExit(true); - } - - // Are we providing the core info? - if ($addForm) { - $upcontext['upgrade_status']['curstep'] = $upcontext['current_step']; - $location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location; - } - - while (@ob_end_clean()) { - header('location: ' . strtr($location, ['&' => '&'])); - } - - // Exit - saving status as we go. - upgradeExit(true); -} - -// Load all essential data and connect to the DB as this is pre SSI.php -function loadEssentialData() -{ - // Report all errors if admin wants them or this is a pre-release version. - if (!empty(Config::$db_show_debug) || strspn(SMF_VERSION, '1234567890.') !== strlen(SMF_VERSION)) { - error_reporting(E_ALL); - } - // Otherwise, report all errors except for deprecation notices. - else { - error_reporting(E_ALL & ~E_DEPRECATED); - } - - define('SMF', 1); - header('X-Frame-Options: SAMEORIGIN'); - header('X-XSS-Protection: 1'); - header('X-Content-Type-Options: nosniff'); - - // Start the session. - if (@ini_get('session.save_handler') == 'user') { - @ini_set('session.save_handler', 'files'); - } - @session_start(); - - require_once Config::$sourcedir . '/Subs-Compat.php'; - - @set_time_limit(600); - - // Initialize everything... - initialize_inputs(); - - // Get the database going! - if (empty(Config::$db_type) || Config::$db_type == 'mysqli') { - Config::$db_type = 'mysql'; - // If overriding Config::$db_type, need to set its settings.php entry too - $changes = []; - $changes['db_type'] = 'mysql'; - Config::updateSettingsFile($changes); - } - - require_once Config::$sourcedir . '/Autoloader.php'; - - if (class_exists('SMF\\Db\\APIs\\' . Db::getClass(Config::$db_type))) { - // Make the connection... - if (empty(Db::$db_connection)) { - Db::load(['non_fatal' => true]); - } else { - // If we've returned here, ping/reconnect to be safe - Db::$db->ping(Db::$db_connection); - } - - // Oh dear god!! - if (Db::$db_connection === null) { - // Get error info... Recast just in case we get false or 0... - $error_message = Db::$db->connect_error(); - - if (empty($error_message)) { - $error_message = ''; - } - $error_number = Db::$db->connect_errno(); - - if (empty($error_number)) { - $error_number = ''; - } - $db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message; - - die(Lang::$txt['error_db_connect_settings'] . '

' . $db_error); - } - - // Load the modSettings data... - $request = Db::$db->query( - '', - 'SELECT variable, value - FROM {db_prefix}settings', - [ - 'db_error_skip' => true, - ], - ); - Config::$modSettings = []; - - while ($row = Db::$db->fetch_assoc($request)) { - Config::$modSettings[$row['variable']] = $row['value']; - } - Db::$db->free_result($request); - } else { - return die(Lang::getTxt('error_sourcefile_missing', ['file' => 'Db/APIs/' . Db::getClass(Config::$db_type) . '.php'])); - } - - // If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars. - if (class_exists('SMF\\QueryString') && php_version_check()) { - QueryString::cleanRequest(); - } - - if (!isset($_GET['substep'])) { - $_GET['substep'] = 0; - } -} - -function initialize_inputs() -{ - global $start_time, $upgrade_path; - - $start_time = time(); - - umask(0); - - ob_start(); - - // Better to upgrade cleanly and fall apart than to screw everything up if things take too long. - ignore_user_abort(true); - - // This is really quite simple; if ?delete is on the URL, delete the upgrader... - if (isset($_GET['delete'])) { - deleteFile(__FILE__); - - // And the extra little files ;). - deleteFile(dirname(__FILE__) . '/upgrade_1-0.sql'); - deleteFile(dirname(__FILE__) . '/upgrade_1-1.sql'); - deleteFile(dirname(__FILE__) . '/upgrade_2-0_' . Db::getClass(Config::$db_type) . '.sql'); - deleteFile(dirname(__FILE__) . '/upgrade_2-1_' . Db::getClass(Config::$db_type) . '.sql'); - deleteFile(dirname(__FILE__) . '/upgrade_3-0_' . Db::getClass(Config::$db_type) . '.sql'); - deleteFile(dirname(__FILE__) . '/upgrade-helper.php'); - - $dh = opendir(dirname(__FILE__)); - - while ($file = readdir($dh)) { - if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1])) { - deleteFile(dirname(__FILE__) . '/' . $file); - } - } - closedir($dh); - - // Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there. - // 1.1 Sources files not in 2.0+ - deleteFile($upgrade_path . '/Sources/ModSettings.php'); - // 1.1 Templates that don't exist any more (e.g. renamed) - deleteFile($upgrade_path . '/Themes/default/Combat.template.php'); - deleteFile($upgrade_path . '/Themes/default/Modlog.template.php'); - // 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder - deleteFile($upgrade_path . '/Themes/default/fader.js'); - deleteFile($upgrade_path . '/Themes/default/script.js'); - deleteFile($upgrade_path . '/Themes/default/spellcheck.js'); - deleteFile($upgrade_path . '/Themes/default/xml_board.js'); - deleteFile($upgrade_path . '/Themes/default/xml_topic.js'); - - // 2.0 Sources files not in 2.1+ - deleteFile($upgrade_path . '/Sources/DumpDatabase.php'); - deleteFile($upgrade_path . '/Sources/LockTopic.php'); - - header('location: http' . (Sapi::httpsOn() ? 's' : '') . '://' . ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png'); - - exit; - } - - // Something is causing this to happen, and it's annoying. Stop it. - $temp = 'upgrade_php?step'; - - while (strlen($temp) > 4) { - if (isset($_GET[$temp])) { - unset($_GET[$temp]); - } - $temp = substr($temp, 1); - } - - // Force a step, defaulting to 0. - $_GET['step'] = (int) @$_GET['step']; - $_GET['substep'] = (int) @$_GET['substep']; -} - -// Step 0 - Let's welcome them in and ask them to login! -function WelcomeLogin() -{ - global $upgradeurl, $upcontext; - global $databases, $upgrade_path; - - $upcontext['sub_template'] = 'welcome_message'; - - // Check for some key files - one template, one language, and a new and an old source file. - $check = @file_exists(Config::$modSettings['theme_dir'] . '/index.template.php') - && @file_exists(Config::$sourcedir . '/QueryString.php') - && @file_exists(Config::$sourcedir . '/Db/APIs/' . Db::getClass(Config::$db_type) . '.php') - && @file_exists(dirname(__FILE__) . '/upgrade_3-0_' . Db::getClass(Config::$db_type) . '.sql'); - - // Need legacy scripts? - if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] < 3.0) { - $check &= @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . Db::getClass(Config::$db_type) . '.sql'); - } - - if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] < 2.1) { - $check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . Db::getClass(Config::$db_type) . '.sql'); - } - - if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] < 2.0) { - $check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql'); - } - - if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] < 1.1) { - $check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql'); - } - - // We don't need "-utf8" files anymore... - $upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language'] ?? $upcontext['lang'] ?? Config::$language); - - if (!$check) { - // Don't tell them what files exactly because it's a spot check - just like teachers don't tell which problems they are spot checking, that's dumb. - return throw_error(Lang::$txt['error_upgrade_files_missing']); - } - - // Do they meet the install requirements? - if (!php_version_check()) { - return throw_error(Lang::$txt['error_php_too_low']); - } - - if (!db_version_check()) { - return throw_error(Lang::getTxt('error_db_too_low', $databases[Config::$db_type])); - } - - // CREATE - $create = Db::$db->create_table('{db_prefix}priv_check', [['name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true]], [['columns' => ['id_test'], 'type' => 'primary']], [], 'overwrite'); - - // ALTER - $alter = Db::$db->add_column('{db_prefix}priv_check', ['name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => '']); - - // DROP - $drop = Db::$db->drop_table('{db_prefix}priv_check'); - - // Sorry... we need CREATE, ALTER and DROP - if (!$create || !$alter || !$drop) { - return throw_error(Lang::getTxt('error_db_privileges', $databases[Config::$db_type])); - } - - // Do a quick version spot check. - $temp = substr(@implode('', @file(Config::$boarddir . '/index.php')), 0, 4096); - preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match); - - if (empty($match[1]) || (trim($match[1]) != SMF_VERSION)) { - return throw_error(Lang::$txt['error_upgrade_old_files']); - } - - // What absolutely needs to be writable? - $writable_files = [ - SMF_SETTINGS_FILE, - SMF_SETTINGS_BACKUP_FILE, - ]; - - // Only check for minified writable files if we have it enabled or not set. - if (!empty(Config::$modSettings['minimize_files']) || !isset(Config::$modSettings['minimize_files'])) { - $writable_files += [ - Config::$modSettings['theme_dir'] . '/css/minified.css', - Config::$modSettings['theme_dir'] . '/scripts/minified.js', - Config::$modSettings['theme_dir'] . '/scripts/minified_deferred.js', - ]; - } - - // Do we need to add this setting? - $need_settings_update = empty(Config::$modSettings['custom_avatar_dir']); - - $custom_av_dir = !empty(Config::$modSettings['custom_avatar_dir']) ? Config::$modSettings['custom_avatar_dir'] : Config::$boarddir . '/custom_avatar'; - $custom_av_url = !empty(Config::$modSettings['custom_avatar_url']) ? Config::$modSettings['custom_avatar_url'] : Config::$boardurl . '/custom_avatar'; - - // This little fellow has to cooperate... - quickFileWritable($custom_av_dir); - - // Are we good now? - if (!is_writable($custom_av_dir)) { - return throw_error(Lang::getTxt('error_dir_not_writable', ['dir' => $custom_av_dir])); - } - - if ($need_settings_update) { - if (!function_exists('cache_put_data')) { - require_once Config::$sourcedir . '/Cache/CacheApi.php'; - } - - Config::updateModSettings(['custom_avatar_dir' => $custom_av_dir]); - Config::updateModSettings(['custom_avatar_url' => $custom_av_url]); - } - - // Check the cache directory. - $cachedir_temp = empty(Config::$cachedir) ? Config::$boarddir . '/cache' : Config::$cachedir; - - if (!file_exists($cachedir_temp)) { - @mkdir($cachedir_temp); - } - - if (!file_exists($cachedir_temp)) { - return throw_error(Lang::$txt['error_cache_not_found']); - } - - quickFileWritable($cachedir_temp . '/db_last_error.php'); - - $lang_dir = !empty(Config::$languagesdir) ? Config::$languagesdir : fixRelativePath(Config::$boarddir) . '/Languages'; - - if (!file_exists($lang_dir . '/' . $upcontext['language'] . '/General.php')) { - return throw_error(Lang::getTxt('error_lang_general_missing', ['lang' => $upcontext['language'], 'url' => $upgradeurl])); - } - - if (!isset($_GET['skiplang'])) { - $temp = substr(@implode('', @file($lang_dir . '/' . $upcontext['language'] . '/General.php')), 0, 4096); - - preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*General(?:[\s]{2}|\*/)~i', $temp, $match); - - if (empty($match[1]) || $match[1] != SMF_LANG_VERSION) { - return throw_error(Lang::getTxt('error_upgrade_old_lang_files', ['lang' => $upcontext['language'], 'url' => $upgradeurl])); - } - } - - // Do we need to update our Settings file with the new language locale? - $current_language = Config::$language; - $new_locale = Lang::getLocaleFromLanguageName($current_language); - - if ($new_locale !== null) { - Config::updateSettingsFile(['language' => $new_locale]); - } - - if (!makeFilesWritable($writable_files)) { - return false; - } - - // Check agreement.txt. (it may not exist, in which case $boarddir must be writable.) - if (isset(Config::$modSettings['agreement']) && (!is_writable($lang_dir) || file_exists($lang_dir . '/en_US/agreement.txt')) && !is_writable($lang_dir . '/en_US/agreement.txt')) { - return throw_error(Lang::$txt['error_agreement_not_writable']); - } - - // Upgrade the agreement. - if (isset(Config::$modSettings['agreement'])) { - $fp = fopen(Config::$boarddir . '/agreement.txt', 'w'); - fwrite($fp, Config::$modSettings['agreement']); - fclose($fp); - } - - // We're going to check that their board dir setting is right in case they've been moving stuff around. - if (strtr(Config::$boarddir, ['/' => '', '\\' => '']) != strtr($upgrade_path, ['/' => '', '\\' => ''])) { - $upcontext['warning'] = ' - ' . Lang::getTxt('upgrade_forumdir_settings', ['boarddir' => Config::$boarddir, 'upgrade_path' => $upgrade_path]) . '
-
    -
  • ' . Lang::getTxt('upgrade_forumdir', [Config::$boarddir]) . '
  • -
  • ' . Lang::getTxt('upgrade_sourcedir', [Config::$sourcedir]) . '
  • -
  • ' . Lang::getTxt('upgrade_cachedir', [$cachedir_temp]) . '
  • -
- ' . Lang::$txt['upgrade_incorrect_settings'] . ''; - } - - // Confirm mbstring is loaded... - if (!extension_loaded('mbstring')) { - return throw_error(Lang::$txt['install_no_mbstring']); - } - - // Confirm fileinfo is loaded... - if (!extension_loaded('fileinfo')) { - return throw_error(Lang::$txt['install_no_fileinfo']); - } - - // Check for https stream support. - $supported_streams = stream_get_wrappers(); - - if (!in_array('https', $supported_streams)) { - $upcontext['custom_warning'] = Lang::$txt['install_no_https']; - } - - // Make sure attachment & avatar folders exist. Big problem if folks move or restructure sites upon upgrade. - checkFolders(); - - // Either we're logged in or we're going to present the login. - if (checkLogin()) { - return true; - } - - $upcontext += SecurityToken::create('login'); - - return false; -} - -// Do a number of attachment & avatar folder checks. -// Display a warning if issues found. Does not force a hard stop. -function checkFolders() -{ - global $upcontext, $command_line; - - $warnings = ''; - - // First, check the avatar directory... - // Note it wasn't specified in yabbse, but there was no smfVersion either. - if (!empty(Config::$modSettings['smfVersion']) && !is_dir(Config::$modSettings['avatar_directory'])) { - $warnings .= Lang::$txt['warning_av_missing']; - } - - // Next, check the custom avatar directory... Note this is optional in 2.0. - if (!empty(Config::$modSettings['custom_avatar_dir']) && !is_dir(Config::$modSettings['custom_avatar_dir'])) { - if (empty($warnings)) { - $warnings = Lang::$txt['warning_custom_av_missing']; - } else { - $warnings .= '

' . Lang::$txt['warning_custom_av_missing']; - } - } - - // Finally, attachment folders. - // A bit more complex, since it may be json or serialized, and it may be an array or just a string... - - // PHP currently has a terrible handling with unserialize in which errors are fatal and not catchable. Lets borrow some code from the RFC that intends to fix this - // https://wiki.php.net/rfc/improve_unserialize_error_handling - try { - set_error_handler(static function ($severity, $message, $file, $line) { - throw new \ErrorException($message, 0, $severity, $file, $line); - }); - $ser_test = @unserialize(Config::$modSettings['attachmentUploadDir']); - } catch (\Throwable $e) { - $ser_test = false; - } finally { - restore_error_handler(); - } - - // Json is simple, it can be caught. - try { - $json_test = @json_decode(Config::$modSettings['attachmentUploadDir'], true); - } catch (\Throwable $e) { - $json_test = null; - } - - $string_test = !empty(Config::$modSettings['attachmentUploadDir']) && is_string(Config::$modSettings['attachmentUploadDir']) && is_dir(Config::$modSettings['attachmentUploadDir']); - - // String? - $attdr_problem_found = false; - - if ($string_test === true) { - // OK... - } - // An array already? - elseif (is_array(Config::$modSettings['attachmentUploadDir'])) { - foreach(Config::$modSettings['attachmentUploadDir'] as $dir) { - if (!empty($dir) && !is_dir($dir)) { - $attdr_problem_found = true; - } - } - } - // Serialized? - elseif ($ser_test !== false) { - if (is_array($ser_test)) { - foreach($ser_test as $dir) { - if (!empty($dir) && !is_dir($dir)) { - $attdr_problem_found = true; - } - } - } else { - if (!empty($ser_test) && !is_dir($ser_test)) { - $attdr_problem_found = true; - } - } - } - // Json? Note the test returns null if encoding was unsuccessful - elseif ($json_test !== null) { - if (is_array($json_test)) { - foreach($json_test as $dir) { - if (!is_dir($dir)) { - $attdr_problem_found = true; - } - } - } else { - if (!is_dir($json_test)) { - $attdr_problem_found = true; - } - } - } - // Unclear, needs a look... - else { - $attdr_problem_found = true; - } - - if ($attdr_problem_found) { - if (empty($warnings)) { - $warnings = Lang::$txt['warning_att_dir_missing']; - } else { - $warnings .= '

' . Lang::$txt['warning_att_dir_missing']; - } - } - - // Might be using CLI - if ($command_line) { - // Change brs to new lines & display - if (!empty($warnings)) { - $warnings = str_replace('
', "\n", $warnings); - echo "\n\n" . $warnings . "\n\n"; - } - } else { - // Might be adding to an existing warning... - if (!empty($warnings)) { - if (empty($upcontext['custom_warning'])) { - $upcontext['custom_warning'] = $warnings; - } else { - $upcontext['custom_warning'] .= '

' . $warnings; - } - } - } -} - -// Step 0.5: Does the login work? -function checkLogin() -{ - global $upcontext, $disable_security; - global $support_js; - - // Are we trying to login? - if (isset($_POST['contbutt']) && (!empty($_POST['user']) || $disable_security)) { - // If we've disabled security pick a suitable name! - if (empty($_POST['user'])) { - $_POST['user'] = 'Administrator'; - } - - // Before 2.0 these column names were different! - $oldDB = false; - - if (empty(Config::$db_type) || Config::$db_type == 'mysql') { - $request = Db::$db->query( - '', - 'SHOW COLUMNS - FROM {db_prefix}members - LIKE {string:member_name}', - [ - 'member_name' => 'memberName', - 'db_error_skip' => true, - ], - ); - - if (Db::$db->num_rows($request) != 0) { - $oldDB = true; - } - Db::$db->free_result($request); - } - - // Get what we believe to be their details. - if (!$disable_security) { - if ($oldDB) { - $request = Db::$db->query( - '', - 'SELECT id_member, memberName AS member_name, passwd, id_group, - additionalGroups AS additional_groups, lngfile - FROM {db_prefix}members - WHERE memberName = {string:member_name}', - [ - 'member_name' => $_POST['user'], - 'db_error_skip' => true, - ], - ); - } else { - $request = Db::$db->query( - '', - 'SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile - FROM {db_prefix}members - WHERE member_name = {string:member_name}', - [ - 'member_name' => $_POST['user'], - 'db_error_skip' => true, - ], - ); - } - - if (Db::$db->num_rows($request) != 0) { - list($id_member, $name, $password, $id_group, $addGroups, $user_language) = Db::$db->fetch_row($request); - - $groups = explode(',', $addGroups); - $groups[] = $id_group; - - foreach ($groups as $k => $v) { - $groups[$k] = (int) $v; - } - - // We don't use "-utf8" anymore... - $user_language = str_ireplace('-utf8', '', Lang::getLocaleFromLanguageName($user_language)); - } else { - $upcontext['username_incorrect'] = true; - } - - Db::$db->free_result($request); - } - $upcontext['username'] = $_POST['user']; - - // Track whether javascript works! - if (isset($_POST['js_works'])) { - if (!empty($_POST['js_works'])) { - $upcontext['upgrade_status']['js'] = 1; - $support_js = 1; - } else { - $support_js = 0; - } - } - - // Note down the version we are coming from. - if (!empty(Config::$modSettings['smfVersion']) && empty($upcontext['user']['version'])) { - $upcontext['user']['version'] = Config::$modSettings['smfVersion']; - } - - // Didn't get anywhere? - if ( - !$disable_security - && empty($upcontext['username_incorrect']) - // 3.0 style - && !Security::hashVerifyPassword( - $_REQUEST['passwrd'], - $password ?? '', - ) - // 2.1 style - && !Security::hashVerifyPassword( - Utils::strtolower(!empty($name) ? $name : '') . $_REQUEST['passwrd'], - $password ?? '', - ) - // 2.0 style - && ( - sha1(strtolower($name ?? '') . $_REQUEST['passwrd']) !== ($password ?? '') - ) - // 1.x style - && ( - hash_hmac('md5', $_REQUEST['passwrd'], strtolower($_POST['user'])) !== ($password ?? '') - ) - ) { - $upcontext['password_failed'] = true; - // Disable the hashing this time. - $upcontext['disable_login_hashing'] = true; - } - - if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security) { - // Set the password. - if (!$disable_security) { - // Do we actually have permission? - if (!in_array(1, $groups)) { - $request = Db::$db->query( - '', - 'SELECT permission - FROM {db_prefix}permissions - WHERE id_group IN ({array_int:groups}) - AND permission = {string:admin_forum}', - [ - 'groups' => $groups, - 'admin_forum' => 'admin_forum', - 'db_error_skip' => true, - ], - ); - - if (Db::$db->num_rows($request) == 0) { - return throw_error(Lang::$txt['error_not_admin']); - } - Db::$db->free_result($request); - } - - $upcontext['user']['id'] = $id_member; - $upcontext['user']['name'] = $name; - } else { - $upcontext['user']['id'] = 1; - $upcontext['user']['name'] = 'Administrator'; - } - - $upcontext['user']['pass'] = random_int(0, 60000); - // This basically is used to match the GET variables to Settings.php. - $upcontext['upgrade_status']['pass'] = $upcontext['user']['pass']; - - $lang_dir = !empty(Config::$languagesdir) ? Config::$languagesdir : fixRelativePath(Config::$boarddir) . '/Languages'; - - // Set the language to that of the user? - if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($lang_dir . '/' . $upcontext['language'] . '/General.php')) { - $user_language = basename($user_language, '.lng'); - $temp = substr(@implode('', @file($lang_dir . '/' . $upcontext['language'] . '/General.php')), 0, 4096); - preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*General(?:[\s]{2}|\*/)~i', $temp, $match); - - if (empty($match[1]) || $match[1] != SMF_LANG_VERSION) { - $upcontext['upgrade_options_warning'] = Lang::getTxt('warning_lang_old', ['user_language' => $user_language, 'default_language' => $upcontext['language']]); - } elseif (!file_exists($lang_dir . '/' . $user_language . '/Maintenance.php')) { - $upcontext['upgrade_options_warning'] = Lang::getTxt('warning_lang_missing', ['user_language' => $user_language, 'default_language' => $upcontext['language']]); - } else { - // Set this as the new language. - $upcontext['language'] = $user_language; - $upcontext['upgrade_status']['lang'] = $upcontext['language']; - - // Include the file. - load_lang_file(); - } - } - - // If we're resuming set the step and substep to be correct. - if (isset($_POST['cont'])) { - $upcontext['current_step'] = $upcontext['user']['step']; - $_GET['substep'] = $upcontext['user']['substep']; - } - - return true; - } - } - - return false; -} - -// Step 1: Do the maintenance and backup. -function UpgradeOptions() -{ - global $command_line, $is_debug; - global $upcontext; - - $upcontext['sub_template'] = 'upgrade_options'; - $upcontext['page_title'] = Lang::$txt['upgrade_options']; - - $upcontext['karma_installed'] = ['good' => false, 'bad' => false]; - $member_columns = Db::$db->list_columns('{db_prefix}members'); - - $upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns); - $upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns); - - $upcontext['migrate_settings_recommended'] = empty(Config::$modSettings['smfVersion']) || version_compare(strtolower(Config::$modSettings['smfVersion']), substr(SMF_VERSION, 0, strpos(SMF_VERSION, '.') + 1 + strspn(SMF_VERSION, '1234567890', strpos(SMF_VERSION, '.') + 1)) . ' foo', '<'); - - unset($member_columns); - - // If we've not submitted then we're done. - if (empty($_POST['upcont'])) { - return false; - } - - // We cannot execute this step in strict mode - strict mode data fixes are not applied yet - setSqlMode(false); - - // Firstly, if they're enabling SM stat collection just do it. - if (!empty($_POST['stats']) && !str_starts_with(Config::$boardurl, 'http://localhost') && empty(Config::$modSettings['allow_sm_stats']) && empty(Config::$modSettings['enable_sm_stats'])) { - $upcontext['allow_sm_stats'] = true; - - // Don't register if we still have a key. - if (empty(Config::$modSettings['sm_stats_key'])) { - // Attempt to register the site etc. - $fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr); - - if (!$fp) { - $fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr); - } - - if ($fp) { - $out = 'GET /smf/stats/register_stats.php?site=' . base64_encode(Config::$boardurl) . ' HTTP/1.1' . "\r\n"; - $out .= 'Host: www.simplemachines.org' . "\r\n"; - $out .= 'Connection: Close' . "\r\n\r\n"; - fwrite($fp, $out); - - $return_data = ''; - - while (!feof($fp)) { - $return_data .= fgets($fp, 128); - } - - fclose($fp); - - // Get the unique site ID. - preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID); - - if (!empty($ID[1])) { - Db::$db->insert( - 'replace', - Config::$db_prefix . 'settings', - ['variable' => 'string', 'value' => 'string'], - [ - ['sm_stats_key', $ID[1]], - ['enable_sm_stats', 1], - ], - ['variable'], - ); - } - } - } else { - Db::$db->insert( - 'replace', - Config::$db_prefix . 'settings', - ['variable' => 'string', 'value' => 'string'], - [ - ['enable_sm_stats', 1], - ], - ['variable'], - ); - } - } - // Don't remove stat collection unless we unchecked the box for real, not from the loop. - elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats'])) { - Db::$db->query( - '', - 'DELETE FROM {db_prefix}settings - WHERE variable = {string:enable_sm_stats}', - [ - 'enable_sm_stats' => 'enable_sm_stats', - 'db_error_skip' => true, - ], - ); - } - - // Deleting old karma stuff? - $_SESSION['delete_karma'] = !empty($_POST['delete_karma']); - - // Emptying the error log? - $_SESSION['empty_error'] = !empty($_POST['empty_error']); - - // Reprocessing attachments? - $_SESSION['reprocess_attachments'] = !empty($_POST['reprocess_attachments']); - - $changes = []; - - // Add proxy settings. - if (!isset(Config::$image_proxy_secret) || Config::$image_proxy_secret == 'smfisawesome') { - $changes['image_proxy_secret'] = bin2hex(random_bytes(10)); - } - - if (!isset(Config::$image_proxy_maxsize)) { - $changes['image_proxy_maxsize'] = 5190; - } - - if (!isset(Config::$image_proxy_enabled)) { - $changes['image_proxy_enabled'] = false; - } - - // If Config::$boardurl reflects https, set force_ssl - if (!function_exists('cache_put_data')) { - require_once Config::$sourcedir . '/Cache/CacheApi.php'; - } - - if (stripos(Config::$boardurl, 'https://') !== false && !isset(Config::$modSettings['force_ssl'])) { - Config::updateModSettings(['force_ssl' => '1']); - } - - $lang_dir = !empty(Config::$languagesdir) ? Config::$languagesdir : fixRelativePath(Config::$boarddir) . '/Languages'; - - // If we're overriding the language follow it through. - if (isset($upcontext['lang']) && file_exists($lang_dir . '/' . $upcontext['lang'] . '/General.php')) { - $changes['language'] = $upcontext['lang']; - } - - if (!empty($_POST['maint'])) { - $changes['maintenance'] = 2; - // Remember what it was... - $upcontext['user']['main'] = Config::$maintenance; - - if (!empty($_POST['maintitle'])) { - $changes['mtitle'] = $_POST['maintitle']; - $changes['mmessage'] = $_POST['mainmessage']; - } else { - $changes['mtitle'] = Lang::$txt['mtitle']; - $changes['mmessage'] = Lang::$txt['mmessage']; - } - } - - if ($command_line) { - echo ' * Updating Settings.php...'; - } - - // Fix some old paths. - if (str_starts_with(Config::$boarddir, '.')) { - $changes['boarddir'] = fixRelativePath(Config::$boarddir); - } - - if (str_starts_with(Config::$sourcedir, '.')) { - $changes['sourcedir'] = fixRelativePath(Config::$sourcedir); - } - - if (empty(Config::$cachedir) || str_starts_with(Config::$cachedir, '.')) { - $changes['cachedir'] = fixRelativePath(Config::$boarddir) . '/cache'; - } - - // Migrate cache settings. - // Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled. - if (!isset(Config::$cache_enable)) { - $changes += [ - 'cache_accelerator' => upgradeCacheSettings(), - 'cache_enable' => !empty(Config::$modSettings['cache_enable']) ? Config::$modSettings['cache_enable'] : 0, - 'cache_memcached' => !empty(Config::$modSettings['cache_memcached']) ? Config::$modSettings['cache_memcached'] : '', - ]; - } - - // 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 (str_contains(Config::$db_server, ':') && Config::$db_type == 'mysql') { - list(Config::$db_server, Config::$db_port) = explode(':', Config::$db_server); - - $changes['db_server'] = Config::$db_server; - - // Only set this if we're not using the default port - if (Config::$db_port != ini_get('mysqli.default_port')) { - $changes['db_port'] = (int) Config::$db_port; - } - } - - // If db_port is set and is the same as the default, set it to 0. - if (!empty(Config::$db_port)) { - if (Config::$db_type == 'mysql' && Config::$db_port == ini_get('mysqli.default_port')) { - $changes['db_port'] = 0; - } elseif (Config::$db_type == 'postgresql' && Config::$db_port == 5432) { - $changes['db_port'] = 0; - } - } - - // Maybe we haven't had this option yet? - if (empty(Config::$packagesdir)) { - $changes['packagesdir'] = fixRelativePath(Config::$boarddir) . '/Packages'; - } - - // Languages have moved! - if (empty(Config::$languagesdir)) { - $changes['languagesdir'] = fixRelativePath(Config::$boarddir) . '/Languages'; - } - - // Make sure we fix the language as well. - if (stristr(Config::$language, '-utf8')) { - $changes['language'] = str_ireplace('-utf8', '', Config::$language); - } - - // @todo Maybe change the cookie name if going to 1.1, too? - - // Ensure this doesn't get lost in translation. - $changes['upgradeData'] = base64_encode(json_encode($upcontext['user'])); - - // Update Settings.php with the new settings, and rebuild if they selected that option. - $res = Config::updateSettingsFile($changes, false, !empty($_POST['migrateSettings'])); - - if ($command_line && $res) { - echo ' Successful.' . "\n"; - } elseif ($command_line && !$res) { - echo ' FAILURE.' . "\n"; - - die; - } - - // Are we doing debug? - if (isset($_POST['debug'])) { - $upcontext['upgrade_status']['debug'] = true; - $is_debug = true; - } - - // If we're not backing up then jump one. - if (empty($_POST['backup'])) { - $upcontext['current_step']++; - } - - // If we've got here then let's proceed to the next step! - return true; -} - -// Backup the database - why not... -function BackupDatabase() -{ - global $upcontext, $command_line, $support_js, $file_steps; - - $upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database'; - $upcontext['page_title'] = Lang::$txt['backup_database']; - - // Done it already - js wise? - if (!empty($_POST['backup_done'])) { - return true; - } - - // We cannot execute this step in strict mode - strict mode data fixes are not applied yet - setSqlMode(false); - - // Get all the table names. - $filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', Config::$db_prefix, $match) != 0 ? $match[2] : Config::$db_prefix) . '%'; - $db = preg_match('~^`(.+?)`\.(.+?)$~', Config::$db_prefix, $match) != 0 ? strtr($match[1], ['`' => '']) : false; - $tables = Db::$db->list_tables($db, $filter); - - $table_names = []; - - foreach ($tables as $table) { - if (!str_starts_with($table, 'backup_')) { - $table_names[] = $table; - } - } - - $upcontext['table_count'] = count($table_names); - $upcontext['cur_table_num'] = $_GET['substep']; - $upcontext['cur_table_name'] = str_replace(Config::$db_prefix, '', $table_names[$_GET['substep']] ?? $table_names[0]); - $upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100); - // For non-java auto submit... - $file_steps = $upcontext['table_count']; - - // What ones have we already done? - foreach ($table_names as $id => $table) { - if ($id < $_GET['substep']) { - $upcontext['previous_tables'][] = $table; - } - } - - if ($command_line) { - echo 'Backing Up Tables.'; - } - - // If we don't support javascript we backup here. - if (!$support_js || isset($_GET['xml'])) { - // Backup each table! - for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++) { - $upcontext['cur_table_name'] = str_replace(Config::$db_prefix, '', ($table_names[$substep + 1] ?? $table_names[$substep])); - $upcontext['cur_table_num'] = $substep + 1; - - $upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100); - - // Do we need to pause? - nextSubstep($substep); - - backupTable($table_names[$substep]); - - // If this is XML to keep it nice for the user do one table at a time anyway! - if (isset($_GET['xml'])) { - return upgradeExit(); - } - } - - if ($command_line) { - echo "\n" . ' Successful.\'' . "\n"; - flush(); - } - $upcontext['step_progress'] = 100; - - $_GET['substep'] = 0; - - // Make sure we move on! - return true; - } - - // Either way next place to post will be database changes! - $_GET['substep'] = 0; - - return false; -} - -// Backup one table... -function backupTable($table) -{ - global $command_line; - - if ($command_line) { - echo "\n" . ' +++ Backing up \"' . str_replace(Config::$db_prefix, '', $table) . '"...'; - flush(); - } - - Db::$db->backup_table($table, 'backup_' . $table); - - if ($command_line) { - echo ' done.'; - } -} - -// Step 2: Everything. -function DatabaseChanges() -{ - global $upcontext, $support_js; - - // Have we just completed this? - if (!empty($_POST['database_done'])) { - return true; - } - - $upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes'; - $upcontext['page_title'] = Lang::$txt['database_changes']; - - $upcontext['delete_karma'] = !empty($_SESSION['delete_karma']); - $upcontext['empty_error'] = !empty($_SESSION['empty_error']); - $upcontext['reprocess_attachments'] = !empty($_SESSION['reprocess_attachments']); - - if (Config::$db_type == 'mysql') { - convertToInnoDb(); - } - - // All possible files. - // Name, < version, insert_on_complete - // Last entry in array indicates whether to use sql_mode of STRICT or not. - $files = [ - ['upgrade_1-0.sql', '1.1', '1.1 RC0', false], - ['upgrade_1-1.sql', '2.0', '2.0 a', false], - ['upgrade_2-0_' . Db::getClass(Config::$db_type) . '.sql', '2.1', '2.1 dev0', false], - ['upgrade_2-1_' . Db::getClass(Config::$db_type) . '.sql', '3.0', '3.0 dev0', true], - ['upgrade_3-0_' . Db::getClass(Config::$db_type) . '.sql', '3.1', SMF_VERSION, true], - ]; - - // How many files are there in total? - if (isset($_GET['filecount'])) { - $upcontext['file_count'] = (int) $_GET['filecount']; - } else { - $upcontext['file_count'] = 0; - - foreach ($files as $file) { - if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] < $file[1]) { - $upcontext['file_count']++; - } - } - } - - // Do each file! - $did_not_do = count($files) - $upcontext['file_count']; - $upcontext['step_progress'] = 0; - $upcontext['cur_file_num'] = 0; - - foreach ($files as $file) { - if ($did_not_do) { - $did_not_do--; - } else { - $upcontext['cur_file_num']++; - $upcontext['cur_file_name'] = $file[0]; - - // Do we actually need to do this still? - if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] < $file[1]) { - // Use STRICT mode on more recent steps - setSqlMode($file[3]); - - // Reload modSettings to capture any adds/updates made along the way - $request = Db::$db->query( - '', - 'SELECT variable, value - FROM {db_prefix}settings', - [ - 'db_error_skip' => true, - ], - ); - - Config::$modSettings = []; - - while ($row = Db::$db->fetch_assoc($request)) { - Config::$modSettings[$row['variable']] = $row['value']; - } - - Db::$db->free_result($request); - - // Some theme settings are in Config::$modSettings - // Note we still might be doing yabbse (no smf ver) - if (isset(Config::$modSettings['smfVersion'])) { - $request = Db::$db->query( - '', - 'SELECT variable, value - FROM {db_prefix}themes - WHERE id_theme = {int:id_theme} - AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})', - [ - 'id_theme' => 1, - 'theme_url' => 'theme_url', - 'theme_dir' => 'theme_dir', - 'images_url' => 'images_url', - 'db_error_skip' => true, - ], - ); - - while ($row = Db::$db->fetch_assoc($request)) { - Config::$modSettings[$row['variable']] = $row['value']; - } - - Db::$db->free_result($request); - } - - if (!isset(Config::$modSettings['theme_url'])) { - Config::$modSettings['theme_dir'] = Config::$boarddir . '/Themes/default'; - Config::$modSettings['theme_url'] = 'Themes/default'; - Config::$modSettings['images_url'] = 'Themes/default/images'; - } - - // Now process the file... - $nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]); - - if ($nextFile) { - // Only update the version of this if complete. - Db::$db->insert( - 'replace', - Config::$db_prefix . 'settings', - ['variable' => 'string', 'value' => 'string'], - [ - ['smfVersion', $file[2]], - ], - ['variable'], - ); - - Config::$modSettings['smfVersion'] = $file[2]; - } - - // If this is XML we only do this stuff once. - if (isset($_GET['xml'])) { - // Flag to move on to the next. - $upcontext['completed_step'] = true; - - // Did we complete the whole file? - if ($nextFile) { - $upcontext['current_debug_item_num'] = -1; - } - - return upgradeExit(); - } - - if ($support_js) { - break; - } - } - // Set the progress bar to be right as if we had - even if we hadn't... - $upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100; - } - } - - $_GET['substep'] = 0; - - // So the template knows we're done. - if (!$support_js) { - $upcontext['changes_complete'] = true; - - return true; - } - - return $did_not_do === 0; -} - -// Different versions of the files use different sql_modes -function setSqlMode($strict = true) -{ - if (Config::$db_type != 'mysql') { - return; - } - - if ($strict) { - $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'; - } else { - $mode = ''; - } - - mysqli_query(Db::$db_connection, 'SET SESSION sql_mode = \'' . $mode . '\''); -} - -/** - * Converts all MySQL tables to the InnoDB engine and dynamic rows. - */ -function convertToInnoDb() -{ - if (Config::$db_type != 'mysql') { - return; - } - - $tables = Db::$db->list_tables(false, Db::$db->prefix . '%'); - - foreach ($tables as $table) { - $structure = Db::$db->table_structure($table); - - if ($structure['engine'] !== 'InnoDB') { - Db::$db->query( - '', - 'ALTER TABLE {identifier:table} - ENGINE {literal:InnoDB} - ROW_FORMAT=DYNAMIC', - [ - 'table' => $table, - ], - ); - } elseif ($structure['row_format'] !== 'Dynamic') { - Db::$db->query( - '', - 'ALTER TABLE {identifier:table} - ROW_FORMAT=DYNAMIC', - [ - 'table' => $table, - ], - ); - } - } - - // Ensure all future tables use dynamic row format. - Db::$db->query( - '', - 'SET GLOBAL innodb_default_row_format=DYNAMIC', - [], - ); -} - -// Delete the damn thing! -function DeleteUpgrade() -{ - global $command_line, $upcontext; - global $settings; - - // Now it's nice to have some of the basic SMF source files. - if (!isset($_GET['ssi']) && !$command_line) { - redirectLocation('&ssi=1'); - } - - $upcontext['sub_template'] = 'upgrade_complete'; - $upcontext['page_title'] = Lang::$txt['upgrade_complete']; - - $endl = $command_line ? "\n" : '
' . "\n"; - - $changes = [ - 'language' => (str_ends_with(Config::$language, '.lng') ? substr(Config::$language, 0, -4) : Config::$language), - 'db_error_send' => true, - 'upgradeData' => null, - ]; - - clearstatcache(); - $current_settings = Config::getCurrentSettings(filemtime(SMF_SETTINGS_FILE)); - - // Fix case of Tasks directory. - if ( - isset($current_settings['tasksdir']) - && is_dir($current_settings['tasksdir']) - && basename($current_settings['tasksdir']) !== 'Tasks' - && is_writable($current_settings['tasksdir']) - && is_writable($current_settings['sourcedir']) - ) { - // Do 'tasks' and 'Tasks' both exist? - if ( - !empty(fileinode(realpath($current_settings['sourcedir'] . '/tasks'))) - && !empty(fileinode(realpath($current_settings['sourcedir'] . '/Tasks'))) - && fileinode(realpath($current_settings['tasksdir'])) !== fileinode(realpath($current_settings['sourcedir'] . '/Tasks')) - ) { - // Move everything in 'Tasks' to 'tasks'. - foreach (glob(realpath($current_settings['sourcedir'] . '/Tasks') . DIRECTORY_SEPARATOR . '*') as $path) { - rename($path, realpath($current_settings['tasksdir']) . DIRECTORY_SEPARATOR . basename($path)); - } - - // Now delete 'Tasks'. - rmdir(realpath($current_settings['sourcedir'] . '/Tasks')); - } - - // Rename 'tasks' to 'Tasks'. - // Do this in two steps to make sure it works on case insensitive file systems. - rename($current_settings['tasksdir'], $current_settings['sourcedir'] . DIRECTORY_SEPARATOR . 'Tasks_temp'); - rename($current_settings['sourcedir'] . DIRECTORY_SEPARATOR . 'Tasks_temp', $current_settings['sourcedir'] . DIRECTORY_SEPARATOR . 'Tasks'); - } - - // Are we in maintenance mode? - if (isset($upcontext['user']['main'])) { - if ($command_line) { - echo ' * '; - } - $upcontext['removed_maintenance'] = true; - $changes['maintenance'] = $upcontext['user']['main']; - } - // Otherwise if somehow we are in 2 let's go to 1. - elseif (!empty(Config::$maintenance) && Config::$maintenance == 2) { - $changes['maintenance'] = 1; - } - - // Wipe this out... - $upcontext['user'] = []; - - Config::updateSettingsFile($changes); - - // Clean any old cache files away. - upgrade_clean_cache(); - - // Can we delete the file? - $upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__); - - // Now is the perfect time to fetch the SM files. - if ($command_line) { - cli_scheduled_fetchSMfiles(); - } else { - (new TaskRunner())->runScheduledTasks(['fetchSMfiles']); // Now go get those files! - - // This is needed in case someone invokes the upgrader using https when upgrading an http forum - if (Sapi::httpsOn()) { - $settings['default_theme_url'] = strtr($settings['default_theme_url'], ['http://' => 'https://']); - } - } - - // Queue any post-upgrade background tasks that we should run. - addBackgroundTasks(); - - // Log what we've done. - if (!isset(User::$me)) { - User::load(); - } - - if (empty(User::$me->id) && !empty($upcontext['user']['id'])) { - User::setMe($upcontext['user']['id']); - } - - User::$me->ip = $command_line || empty($_SERVER['REMOTE_ADDR']) ? '127.0.0.1' : $_SERVER['REMOTE_ADDR']; - - // Log the action manually, so CLI still works. - Db::$db->insert( - '', - '{db_prefix}log_actions', - [ - 'log_time' => 'int', - 'id_log' => 'int', - 'id_member' => 'int', - 'ip' => 'inet', - 'action' => 'string', - 'id_board' => 'int', - 'id_topic' => 'int', - 'id_msg' => 'int', - 'extra' => 'string-65534', - ], - [ - [ - time(), - 3, - User::$me->id, - User::$me->ip, - 'upgrade', - 0, - 0, - 0, - json_encode(['version' => SMF_FULL_VERSION, 'member' => User::$me->id]), - ], - ], - ['id_action'], - ); - User::setMe(0); - - if ($command_line) { - echo $endl; - echo 'Upgrade Complete!', $endl; - echo 'Please delete this file as soon as possible for security reasons.', $endl; - - exit; - } - - // Make sure it says we're done. - $upcontext['overall_percent'] = 100; - - if (isset($upcontext['step_progress'])) { - unset($upcontext['step_progress']); - } - - $_GET['substep'] = 0; - - return false; -} - -// Queues background tasks that we want to run soon after upgrading. -function addBackgroundTasks() -{ - Db::$db->insert( - 'insert', - '{db_prefix}background_tasks', - [ - 'task_class' => 'string', - 'task_data' => 'string', - 'claimed_time' => 'int', - ], - [ - [ - 'SMF\\Tasks\\UpdateSpoofDetectorNames', - json_encode(['last_member_id' => 0]), - 0, - ], - ], - ['id_task'], - ); -} - -// Just like the built in one, but setup for CLI to not use themes. -function cli_scheduled_fetchSMfiles() -{ - if (empty(Config::$modSettings['time_format'])) { - Config::$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p'; - } - - // What files do we want to get - $request = Db::$db->query( - '', - 'SELECT id_file, filename, path, parameters - FROM {db_prefix}admin_info_files', - [ - ], - ); - - $js_files = []; - - while ($row = Db::$db->fetch_assoc($request)) { - $js_files[$row['id_file']] = [ - 'filename' => $row['filename'], - 'path' => $row['path'], - 'parameters' => sprintf($row['parameters'], Config::$language, urlencode(Config::$modSettings['time_format']), urlencode(SMF_FULL_VERSION)), - ]; - } - Db::$db->free_result($request); - - foreach ($js_files as $ID_FILE => $file) { - // Create the url - $server = empty($file['path']) || !str_starts_with($file['path'], 'http://') ? 'https://www.simplemachines.org' : ''; - $url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : ''); - - // Get the file - $file_data = WebFetchApi::fetch($url); - - // If we got an error - give up - the site might be down. - if ($file_data === false) { - return throw_error(sprintf('Could not retrieve the file %1$s.', $url)); - } - - // Save the file to the database. - Db::$db->query( - 'substring', - 'UPDATE {db_prefix}admin_info_files - SET data = SUBSTRING({string:file_data}, 1, 65534) - WHERE id_file = {int:id_file}', - [ - 'id_file' => $ID_FILE, - 'file_data' => $file_data, - ], - ); - } - - return true; -} - -function convertSettingsToTheme() -{ - $values = [ - 'show_latest_member' => @$GLOBALS['showlatestmember'], - 'show_bbc' => $GLOBALS['showyabbcbutt'] ?? @$GLOBALS['showbbcbutt'], - 'show_modify' => @$GLOBALS['showmodify'], - 'show_user_images' => @$GLOBALS['showuserpic'], - 'show_blurb' => @$GLOBALS['showusertext'], - 'show_gender' => @$GLOBALS['showgenderimage'], - 'show_newsfader' => @$GLOBALS['shownewsfader'], - 'display_recent_bar' => @$GLOBALS['Show_RecentBar'], - 'show_member_bar' => @$GLOBALS['Show_MemberBar'], - 'linktree_link' => @$GLOBALS['curposlinks'], - 'show_profile_buttons' => @$GLOBALS['profilebutton'], - 'show_mark_read' => @$GLOBALS['showmarkread'], - 'show_board_desc' => @$GLOBALS['ShowBDescrip'], - 'newsfader_time' => @$GLOBALS['fadertime'], - 'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0, - 'enable_news' => @$GLOBALS['enable_news'], - 'linktree_inline' => @Config::$modSettings['enableInlineLinks'], - 'return_to_post' => @Config::$modSettings['returnToPost'], - ]; - - $themeData = []; - - foreach ($values as $variable => $value) { - if (!isset($value) || $value === null) { - $value = 0; - } - - $themeData[] = [0, 1, $variable, $value]; - } - - if (!empty($themeData)) { - Db::$db->insert( - 'ignore', - Config::$db_prefix . 'themes', - ['id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'], - $themeData, - ['id_member', 'id_theme', 'variable'], - ); - } -} - -// This function only works with MySQL but that's fine as it is only used for v1.0. -function convertSettingstoOptions() -{ - // Format: new_setting -> old_setting_name. - $values = [ - 'calendar_start_day' => 'cal_startmonday', - 'view_newest_first' => 'viewNewestFirst', - 'view_newest_pm_first' => 'viewNewestFirst', - ]; - - foreach ($values as $variable => $value) { - if (empty(Config::$modSettings[$value[0]])) { - continue; - } - - Db::$db->query( - '', - 'INSERT IGNORE INTO {db_prefix}themes - (id_member, id_theme, variable, value) - SELECT id_member, 1, {string:variable}, {string:value} - FROM {db_prefix}members', - [ - 'variable' => $variable, - 'value' => Config::$modSettings[$value[0]], - 'db_error_skip' => true, - ], - ); - - Db::$db->query( - '', - 'INSERT IGNORE INTO {db_prefix}themes - (id_member, id_theme, variable, value) - VALUES (-1, 1, {string:variable}, {string:value})', - [ - 'variable' => $variable, - 'value' => Config::$modSettings[$value[0]], - 'db_error_skip' => true, - ], - ); - } -} - -function php_version_check() -{ - return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>='); -} - -function db_version_check() -{ - global $databases; - - $curver = $databases[Config::$db_type]['version_check'](); - $curver = preg_replace('~\-.+?$~', '', $curver); - - return version_compare($databases[Config::$db_type]['version'], $curver, '<='); -} - -function fixRelativePath($path) -{ - global $install_path; - - // Fix the . at the start, clear any duplicate slashes, and fix any trailing slash... - return addslashes(preg_replace(['~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'], [$install_path . '$1', '/', '\\', ''], $path)); -} - -function parse_sql($filename) -{ - global $db_collation, $command_line, $file_steps, $step_progress, $custom_warning; - global $upcontext, $support_js, $is_debug; - -/* - Failure allowed on: - - INSERT INTO but not INSERT IGNORE INTO. - - UPDATE IGNORE but not UPDATE. - - ALTER TABLE and ALTER IGNORE TABLE. - - DROP TABLE. - Yes, I realize that this is a bit confusing... maybe it should be done differently? - - If a comment... - - begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.) - - begins with ---# it is a debugging statement, no break - only shown at all in debug. - - is only ---#, it is "done." and then a break - only shown in debug. - - begins with ---{ it is a code block terminating at ---}. - - Every block of between "--- ..."s is a step. Every "---#" section represents a substep. - - Replaces the following variables: - - {$boarddir} - - {$boardurl} - - {$db_prefix} - - {$db_name} - - {$db_collation} -*/ - - // Our custom error handler - does nothing but does stop public errors from XML! - // Note that php error suppression - @ - used heavily in the upgrader, calls the error handler - // but error_reporting() will return 0 as it does so (pre php8). - // Note error handling in php8+ no longer fails silently on many errors, but error_reporting() - // will return 4437 (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE) - // as it does so. - set_error_handler( - function ($errno, $errstr, $errfile, $errline) use ($support_js) { - if ($support_js) { - return true; - } - - if ((error_reporting() != 0) && (error_reporting() != (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE))) { - echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline; - } - }, - ); - - // If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8mb4 - // Note it is expected to be in the format: ENGINE=InnoDB{$db_collation}; - if (Config::$db_type == 'mysql') { - $db_collation = ' DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci'; - } else { - $db_collation = ''; - } - - $endl = $command_line ? "\n" : '
' . "\n"; - - $lines = file($filename); - - $current_type = 'sql'; - $current_data = ''; - $substep = 0; - $last_step = ''; - - // Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php - $lines = preg_replace('/\) ENGINE=(InnoDB|MyISAM);/', ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;', $lines); - - // Count the total number of steps within this file - for progress. - $file_steps = substr_count(implode('', $lines), '---#'); - $upcontext['total_items'] = substr_count(implode('', $lines), '--- '); - $upcontext['debug_items'] = $file_steps; - $upcontext['current_item_num'] = 0; - $upcontext['current_item_name'] = ''; - $upcontext['current_debug_item_num'] = 0; - $upcontext['current_debug_item_name'] = ''; - // This array keeps a record of what we've done in case java is dead... - $upcontext['actioned_items'] = []; - - $done_something = false; - - foreach ($lines as $line_number => $line) { - $do_current = $substep >= $_GET['substep']; - - // Get rid of any comments in the beginning of the line... - if (str_starts_with(trim($line), '/*')) { - $line = preg_replace('~/\*.+?\*/~', '', $line); - } - - // Always flush. Flush, flush, flush. Flush, flush, flush, flush! FLUSH! - if ($is_debug && !$support_js && $command_line) { - flush(); - } - - if (trim($line) === '') { - continue; - } - - if (trim(substr($line, 0, 3)) === '---') { - $type = substr($line, 3, 1); - - // An error?? - if (trim($current_data) != '' && $type !== '}') { - $upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl; - - if ($command_line) { - echo $upcontext['error_message']; - } - } - - if ($type == ' ') { - if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line) { - echo ' Successful.', $endl; - flush(); - } - - $last_step = htmlspecialchars(rtrim(substr($line, 4))); - $upcontext['current_item_num']++; - $upcontext['current_item_name'] = $last_step; - - if ($do_current) { - $upcontext['actioned_items'][] = $last_step; - - if ($command_line) { - echo ' * '; - } - - // Starting a new main step in our DB changes, so it's time to reset this. - $upcontext['skip_db_substeps'] = false; - } - } elseif ($type == '#') { - $upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps; - - $upcontext['current_debug_item_num']++; - - if (trim($line) != '---#') { - $upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4))); - } - - // Have we already done something? - if (isset($_GET['xml']) && $done_something) { - restore_error_handler(); - - return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false; - } - - if ($do_current) { - if (trim($line) == '---#' && $command_line) { - echo ' done.', $endl; - } elseif ($command_line) { - echo ' +++ ', rtrim(substr($line, 4)); - } elseif (trim($line) != '---#') { - if ($is_debug) { - $upcontext['actioned_items'][] = $upcontext['current_debug_item_name']; - } - } - } - - if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep']) { - if ($command_line) { - echo ' * '; - } else { - $upcontext['actioned_items'][] = $last_step; - } - } - - // Small step - only if we're actually doing stuff. - if ($do_current) { - nextSubstep(++$substep); - } else { - $substep++; - } - } elseif ($type == '{') { - $current_type = 'code'; - } elseif ($type == '}') { - $current_type = 'sql'; - - if (!$do_current || !empty($upcontext['skip_db_substeps'])) { - $current_data = ''; - - // Avoid confusion when skipping something we normally would have done - if ($do_current) { - $done_something = true; - } - - continue; - } - - // @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false - if (eval('use SMF\Config; use SMF\Utils; use SMF\Lang; use SMF\Db\DatabaseApi as Db; use SMF\Security; use SMF\Uuid; global $db_prefix, $modSettings, $smcFunc, $txt, $upcontext, $db_name; ' . $current_data) === false) { - $upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl; - - if ($command_line) { - echo $upcontext['error_message']; - } - } - - // Done with code! - $current_data = ''; - $done_something = true; - } - - continue; - } - - $current_data .= $line; - - if (str_ends_with(rtrim($current_data), ';') && $current_type === 'sql') { - if ((!$support_js || isset($_GET['xml']))) { - if (!$do_current || !empty($upcontext['skip_db_substeps'])) { - $current_data = ''; - - if ($do_current) { - $done_something = true; - } - - continue; - } - - // {$sboarddir} is deprecated, but blah blah backward compatibility blah... - $current_data = strtr(substr(rtrim($current_data), 0, -1), ['{$db_name}' => Config::$db_name, '{$db_prefix}' => Config::$db_prefix, '{$boarddir}' => Db::$db->escape_string(Config::$boarddir), '{$sboarddir}' => Db::$db->escape_string(Config::$boarddir), '{$boardurl}' => Config::$boardurl, '{$db_collation}' => $db_collation]); - - upgrade_query($current_data); - - // @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc). - /* - $result = Db::$db->query('', $current_data, false, false); - // Went wrong? - if (!$result) - { - // Bit of a bodge - do we want the error? - if (!empty($upcontext['return_error'])) - { - $upcontext['error_message'] = Db::$db->error(Db::$db_connection); - return false; - } - }*/ - $done_something = true; - } - $current_data = ''; - } - // If this is xml based and we're just getting the item name then that's grand. - elseif ($support_js && isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current) { - restore_error_handler(); - - return false; - } - - // Clean up by cleaning any step info. - $step_progress = []; - $custom_warning = ''; - } - - // Put back the error handler. - restore_error_handler(); - - if ($command_line) { - echo ' Successful.' . "\n"; - flush(); - } - - $_GET['substep'] = 0; - - return true; -} - -function upgrade_query($string, $unbuffered = false) -{ - global $command_line, $upcontext, $upgradeurl; - - // Get the query result - working around some SMF specific security - just this once! - Config::$modSettings['disableQueryCheck'] = true; - Db::$unbuffered = $unbuffered; - $ignore_insert_error = false; - - $result = Db::$db->query('', $string, ['security_override' => true, 'db_error_skip' => true]); - Db::$unbuffered = false; - - // Failure?! - if ($result !== false) { - return $result; - } - - $db_error_message = Db::$db->error(Db::$db_connection); - - // If MySQL we do something more clever. - if (Config::$db_type == 'mysql') { - $mysqli_errno = mysqli_errno(Db::$db_connection); - $error_query = in_array(substr(trim($string), 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('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1])) { - mysqli_query(Db::$db_connection, 'REPAIR TABLE `' . $match[1] . '`'); - $result = mysqli_query(Db::$db_connection, $string); - - if ($result !== false) { - return $result; - } - } - } elseif ($mysqli_errno == 2013) { - Db::$db_connection = mysqli_connect(Config::$db_server, Config::$db_user, Config::$db_passwd); - mysqli_select_db(Db::$db_connection, Config::$db_name); - - if (Db::$db_connection) { - $result = mysqli_query(Db::$db_connection, $string); - - 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 && str_starts_with(trim($string), '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($string), 0, 7), ['SELECT ', 'SHOW CO'])) { - return false; - } - } - // If a table already exists don't go potty. - else { - if (in_array(substr(trim($string), 0, 8), ['CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U'])) { - if (str_contains($db_error_message, 'exist')) { - return true; - } - } elseif (str_contains(trim($string), 'INSERT ')) { - if (str_contains($db_error_message, 'duplicate') || $ignore_insert_error) { - return true; - } - } - } - - // Get the query string so we pass everything. - $query_string = ''; - - foreach ($_GET as $k => $v) { - $query_string .= ';' . $k . '=' . $v; - } - - if (strlen($query_string) != 0) { - $query_string = '?' . substr($query_string, 1); - } - - if ($command_line) { - echo 'Unsuccessful! Database error message:', "\n", $db_error_message, "\n"; - - die; - } - - // Bit of a bodge - do we want the error? - if (!empty($upcontext['return_error'])) { - $upcontext['error_message'] = $db_error_message; - $upcontext['error_string'] = $string; - - return false; - } - - // Otherwise we have to display this somewhere appropriate if possible. - $upcontext['forced_error_message'] = ' - ' . Lang::$txt['upgrade_unsuccessful'] . '
- -
- ' . Lang::$txt['upgrade_thisquery'] . ' -
' . nl2br(htmlspecialchars(trim($string))) . ';
- - ' . Lang::$txt['upgrade_causerror'] . ' -
' . nl2br(htmlspecialchars($db_error_message)) . '
-
- -
- -
- '; - - upgradeExit(); -} - -// This performs a table alter, but does it unbuffered so the script can time out professionally. -function protected_alter($change, $substep, $is_test = false) -{ - // Firstly, check whether the current index/column exists. - $found = false; - - if ($change['type'] === 'column') { - $columns = Db::$db->list_columns('{db_prefix}' . $change['table'], true); - - foreach ($columns as $column) { - // Found it? - if ($column['name'] === $change['name']) { - $found |= true; - - // Do some checks on the data if we have it set. - if (isset($change['col_type'])) { - $found &= $change['col_type'] === $column['type']; - } - - if (isset($change['null_allowed'])) { - $found &= $column['null'] == $change['null_allowed']; - } - - if (isset($change['default'])) { - $found &= $change['default'] === $column['default']; - } - } - } - } elseif ($change['type'] === 'index') { - $request = upgrade_query(' - SHOW INDEX - FROM ' . Config::$db_prefix . $change['table']); - - if ($request !== false) { - $cur_index = []; - - while ($row = Db::$db->fetch_assoc($request)) { - if ($row['Key_name'] === $change['name']) { - $cur_index[(int) $row['Seq_in_index']] = $row['Column_name']; - } - } - - ksort($cur_index, SORT_NUMERIC); - $found = array_values($cur_index) === $change['target_columns']; - - Db::$db->free_result($request); - } - } - - // If we're trying to add and it's added, we're done. - if ($found && in_array($change['method'], ['add', 'change'])) { - return true; - } - - // Otherwise if we're removing and it wasn't found we're also done. - if (!$found && in_array($change['method'], ['remove', 'change_remove'])) { - return true; - } - - // Otherwise is it just a test? - if ($is_test) { - return false; - } - - // Not found it yet? Bummer! How about we see if we're currently doing it? - $running = false; - $found = false; - - while (1 == 1) { - $request = upgrade_query(' - SHOW FULL PROCESSLIST'); - - while ($row = Db::$db->fetch_assoc($request)) { - if (str_contains($row['Info'], 'ALTER TABLE ' . Config::$db_prefix . $change['table']) && str_contains($row['Info'], $change['text'])) { - $found = true; - } - } - - // Can't find it? Then we need to run it fools! - if (!$found && !$running) { - Db::$db->free_result($request); - - $success = upgrade_query(' - ALTER TABLE ' . Config::$db_prefix . $change['table'] . ' - ' . $change['text'], true) !== false; - - if (!$success) { - return false; - } - - // Return - $running = true; - } - // What if we've not found it, but we'd ran it already? Must of completed. - elseif (!$found) { - Db::$db->free_result($request); - - return true; - } - - // Pause execution for a sec or three. - sleep(3); - - // Can never be too well protected. - nextSubstep($substep); - } - - // Protect it. - nextSubstep($substep); -} - -/** - * Alter a text column definition preserving its character set. - * - * @param array $change - * @param int $substep - */ -function textfield_alter($change, $substep) -{ - $request = Db::$db->query( - '', - 'SHOW FULL COLUMNS - FROM {db_prefix}' . $change['table'] . ' - LIKE {string:column}', - [ - 'column' => $change['column'], - 'db_error_skip' => true, - ], - ); - - if (Db::$db->num_rows($request) === 0) { - die('Unable to find column ' . $change['column'] . ' inside table ' . Config::$db_prefix . $change['table']); - } - $table_row = Db::$db->fetch_assoc($request); - Db::$db->free_result($request); - - // If something of the current column definition is different, fix it. - $column_fix = $table_row['Type'] !== $change['type'] || (strtolower($table_row['Null']) === 'yes') !== $change['null_allowed'] || ($table_row['Default'] === null) !== !isset($change['default']) || (isset($change['default']) && $change['default'] !== $table_row['Default']); - - // Columns that previously allowed null, need to be converted first. - $null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed']; - - // Get the character set that goes with the collation of the column. - if ($column_fix && !empty($table_row['Collation'])) { - $request = Db::$db->query( - '', - 'SHOW COLLATION - LIKE {string:collation}', - [ - 'collation' => $table_row['Collation'], - 'db_error_skip' => true, - ], - ); - - // No results? Just forget it all together. - if (Db::$db->num_rows($request) === 0) { - unset($table_row['Collation']); - } else { - $collation_info = Db::$db->fetch_assoc($request); - } - Db::$db->free_result($request); - } - - if ($column_fix) { - // Make sure there are no NULL's left. - if ($null_fix) { - Db::$db->query( - '', - 'UPDATE {db_prefix}' . $change['table'] . ' - SET ' . $change['column'] . ' = {string:default} - WHERE ' . $change['column'] . ' IS NULL', - [ - 'default' => $change['default'] ?? '', - 'db_error_skip' => true, - ], - ); - } - - // Do the actual alteration. - Db::$db->query( - '', - 'ALTER TABLE {db_prefix}' . $change['table'] . ' - CHANGE COLUMN ' . $change['column'] . ' ' . $change['column'] . ' ' . $change['type'] . (isset($collation_info['Charset']) ? ' CHARACTER SET ' . $collation_info['Charset'] . ' COLLATE ' . $collation_info['Collation'] : '') . ($change['null_allowed'] ? '' : ' NOT NULL') . (isset($change['default']) ? ' default {string:default}' : ''), - [ - 'default' => $change['default'] ?? '', - 'db_error_skip' => true, - ], - ); - } - nextSubstep($substep); -} - -// The next substep. -function nextSubstep($substep) -{ - global $start_time, $timeLimitThreshold, $command_line, $custom_warning; - global $step_progress, $is_debug, $upcontext; - - if ($_GET['substep'] < $substep) { - $_GET['substep'] = $substep; - } - - if ($command_line) { - if (time() - $start_time > 1 && empty($is_debug)) { - echo '.'; - $start_time = time(); - } - - return; - } - - @set_time_limit(300); - - if (function_exists('apache_reset_timeout')) { - @apache_reset_timeout(); - } - - if (time() - $start_time <= $timeLimitThreshold) { - return; - } - - // Do we have some custom step progress stuff? - if (!empty($step_progress)) { - $upcontext['substep_progress'] = 0; - $upcontext['substep_progress_name'] = $step_progress['name']; - - if ($step_progress['current'] > $step_progress['total']) { - $upcontext['substep_progress'] = 99.9; - } else { - $upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100; - } - - // Make it nicely rounded. - $upcontext['substep_progress'] = round($upcontext['substep_progress'], 1); - } - - // If this is XML we just exit right away! - if (isset($_GET['xml'])) { - return upgradeExit(); - } - - // We're going to pause after this! - $upcontext['pause'] = true; - - $upcontext['query_string'] = ''; - - foreach ($_GET as $k => $v) { - if ($k != 'data' && $k != 'substep' && $k != 'step') { - $upcontext['query_string'] .= ';' . $k . '=' . $v; - } - } - - // Custom warning? - if (!empty($custom_warning)) { - $upcontext['custom_warning'] = $custom_warning; - } - - upgradeExit(); -} - -function cmdStep0() -{ - global $start_time, $databases, $upcontext; - global $is_debug; - $start_time = time(); - - while (ob_get_level() > 0) { - ob_end_clean(); - } - ob_implicit_flush(1); - - if (!isset($_SERVER['argv'])) { - $_SERVER['argv'] = []; - } - $_GET['maint'] = 1; - - foreach ($_SERVER['argv'] as $i => $arg) { - if (preg_match('~^--language=(.+)$~', $arg, $match) != 0) { - $upcontext['lang'] = $match[1]; - } elseif (preg_match('~^--path=(.+)$~', $arg) != 0) { - continue; - } elseif ($arg == '--no-maintenance') { - $_GET['maint'] = 0; - } elseif ($arg == '--debug') { - $is_debug = true; - } elseif ($arg == '--backup') { - $_POST['backup'] = 1; - } elseif ($arg == '--rebuild-settings') { - $_POST['migrateSettings'] = 1; - } elseif ($arg == '--allow-stats') { - $_POST['stats'] = 1; - } elseif ($arg == '--template' && (file_exists(Config::$boarddir . '/template.php') || file_exists(Config::$boarddir . '/template.html') && !file_exists(Config::$modSettings['theme_dir'] . '/converted'))) { - $_GET['conv'] = 1; - } elseif ($i != 0) { - echo 'SMF Command-line Upgrader -Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]... - - --path=/path/to/SMF Specify custom SMF root directory. - --language=LANG Reset the forum\'s language to LANG. - --no-maintenance Don\'t put the forum into maintenance mode. - --debug Output debugging information. - --backup Create backups of tables with "backup_" prefix. - --allow-stats Allow Simple Machines stat collection - --rebuild-settings Rebuild the Settings.php file'; - echo "\n"; - - exit; - } - } - - if (!php_version_check()) { - print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true); - } - - if (!db_version_check()) { - print_error('Error: ' . $databases[Config::$db_type]['name'] . ' ' . $databases[Config::$db_type]['version'] . ' does not match minimum requirements.', true); - } - - // CREATE - $create = Db::$db->create_table('{db_prefix}priv_check', [['name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true]], [['columns' => ['id_test'], 'primary' => true]], [], 'overwrite'); - - // ALTER - $alter = Db::$db->add_column('{db_prefix}priv_check', ['name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => '']); - - // DROP - $drop = Db::$db->drop_table('{db_prefix}priv_check'); - - // Sorry... we need CREATE, ALTER and DROP - if (!$create || !$alter || !$drop) { - print_error('The ' . $databases[Config::$db_type]['name'] . " user you have set in Settings.php does not have proper privileges.\n\nPlease ask your host to give this user the ALTER, CREATE, and DROP privileges.", true); - } - - $check = @file_exists(Config::$modSettings['theme_dir'] . '/index.template.php') - && @file_exists(Config::$sourcedir . '/QueryString.php') - && @file_exists(Config::$sourcedir . '/Actions/Admin/Boards.php'); - - if (!$check && !isset(Config::$modSettings['smfVersion'])) { - print_error('Error: Some files are missing or out-of-date.', true); - } - - // Do a quick version spot check. - $temp = substr(@implode('', @file(Config::$boarddir . '/index.php')), 0, 4096); - preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match); - - if (empty($match[1]) || (trim($match[1]) != SMF_VERSION)) { - print_error('Error: Some files have not yet been updated properly.'); - } - - // Make sure Settings.php is writable. - quickFileWritable(SMF_SETTINGS_FILE); - - if (!is_writable(SMF_SETTINGS_FILE)) { - print_error('Error: Unable to obtain write access to "' . basename(SMF_SETTINGS_FILE) . '".', true); - } - - // Make sure Settings_bak.php is writable. - quickFileWritable(SMF_SETTINGS_BACKUP_FILE); - - if (!is_writable(SMF_SETTINGS_BACKUP_FILE)) { - print_error('Error: Unable to obtain write access to "' . basename(SMF_SETTINGS_BACKUP_FILE) . '".'); - } - - $lang_dir = !empty(Config::$languagesdir) ? Config::$languagesdir : fixRelativePath(Config::$boarddir) . '/Languages'; - - if (isset(Config::$modSettings['agreement']) && (!is_writable($lang_dir) || file_exists($lang_dir . '/en_US/agreement.txt')) && !is_writable($lang_dir . '/en_US/agreement.txt')) { - print_error('Error: Unable to obtain write access to "agreement.txt".'); - } elseif (isset(Config::$modSettings['agreement'])) { - $fp = fopen($lang_dir . '/en_US/agreement.txt', 'w'); - fwrite($fp, Config::$modSettings['agreement']); - fclose($fp); - } - - // Make sure Themes is writable. - quickFileWritable(Config::$modSettings['theme_dir']); - - if (!is_writable(Config::$modSettings['theme_dir']) && !isset(Config::$modSettings['smfVersion'])) { - print_error('Error: Unable to obtain write access to "Themes".'); - } - - // Make sure cache directory exists and is writable! - $cachedir_temp = empty(Config::$cachedir) ? Config::$boarddir . '/cache' : Config::$cachedir; - - if (!file_exists($cachedir_temp)) { - @mkdir($cachedir_temp); - } - - // Make sure the cache temp dir is writable. - quickFileWritable($cachedir_temp); - - if (!is_writable($cachedir_temp)) { - print_error('Error: Unable to obtain write access to "cache".', true); - } - - // Make sure db_last_error.php is writable. - quickFileWritable($cachedir_temp . '/db_last_error.php'); - - if (!is_writable($cachedir_temp . '/db_last_error.php')) { - print_error('Error: Unable to obtain write access to "db_last_error.php".'); - } - - if (!file_exists($lang_dir . '/' . $upcontext['language'] . '/General.php')) { - print_error('Error: Unable to find language files!', true); - } else { - $temp = file_get_contents($lang_dir . '/' . $upcontext['language'] . '/General.php', false, null, 0, 4096); - preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*General(?:[\s]{2}|\*/)~i', $temp, $match); - - if (empty($match[1]) || $match[1] != SMF_LANG_VERSION) { - print_error('Error: Language files out of date.', true); - } - - if (!file_exists($lang_dir . '/' . $upcontext['language'] . '/Maintenance.php')) { - print_error('Error: Maintenance language is missing for selected language.', true); - } - - // Otherwise include it! - require_once $lang_dir . '/' . $upcontext['language'] . '/Maintenance.php'; - } - - // Do we need to add this setting? - $need_settings_update = empty(Config::$modSettings['custom_avatar_dir']); - - $custom_av_dir = !empty(Config::$modSettings['custom_avatar_dir']) ? Config::$modSettings['custom_avatar_dir'] : Config::$boarddir . '/custom_avatar'; - $custom_av_url = !empty(Config::$modSettings['custom_avatar_url']) ? Config::$modSettings['custom_avatar_url'] : Config::$boardurl . '/custom_avatar'; - - // This little fellow has to cooperate... - quickFileWritable($custom_av_dir); - - // Are we good now? - if (!is_writable($custom_av_dir)) { - print_error(Lang::getTxt('error_dir_not_writable', ['dir' => $custom_av_dir])); - } elseif ($need_settings_update) { - if (!function_exists('cache_put_data')) { - require_once Config::$sourcedir . '/Cache/CacheApi.php'; - } - - Config::updateModSettings(['custom_avatar_dir' => $custom_av_dir]); - Config::updateModSettings(['custom_avatar_url' => $custom_av_url]); - } - - // Make sure attachment & avatar folders exist. Big problem if folks move or restructure sites upon upgrade. - checkFolders(); - - // Make sure we skip the HTML for login. - $_POST['upcont'] = true; - $upcontext['current_step'] = 1; -} - -/** - * Handles converting your database to UTF-8 (specifically, utf8mb4). - */ -function ConvertUtf8(): bool -{ - global $upcontext; - global $command_line, $support_js; - - // Only applicable to MySQL and its forks. - if (!empty($_POST['utf8_done']) || Config::$db_type !== 'mysql') { - Config::updateSettingsFile(['db_character_set' => '', 'db_mb4' => null]); - - if ($command_line) { - return Cleanup(); - } - - return true; - } - - $upcontext['page_title'] = Lang::$txt['converting_utf8']; - $upcontext['sub_template'] = isset($_GET['xml']) ? 'convert_xml' : 'convert_utf8'; - $upcontext['dropping_index'] = $upcontext['dropping_index'] ?? false; - - // Get all the characters sets that are supported by this MySQL server. - $request = Db::$db->query('', 'SHOW CHARACTER SET'); - $supported_charsets = array_map(fn ($row) => $row['Charset'], Db::$db->fetch_all($request)); - Db::$db->free_result($request); - - // Which character set have they been using for interacting with the browser? - if (isset(Config::$modSettings['global_character_set'])) { - // Things are easy if this exists. - $lang_charset = Config::$modSettings['global_character_set']; - } elseif (version_compare(strtolower(str_replace(' ', '.', Config::$modSettings['smfVersion'])), '3.0.dev.1', '>=')) { - $lang_charset = 'utf8'; - } else { - // Figure it out the hard way. - // These are the $txt['lang_character_set'] values from all the 2.0.19 - // language packs that weren't *-utf8 ones. Note that some of them used - // UTF-8 in both versions of their language packs, so UTF-8 still shows - // up in a number of entries below. - $lang_charsets = [ - 'afrikaans' => 'ISO-8859-1', - 'albanian' => 'ISO-8859-1', - 'arabic' => 'windows-1256', - 'armenian_east' => 'armscii-8', - 'armenian_west' => 'armscii-8', - 'azerbaijani_latin' => 'ISO-8859-9', - 'bangla' => 'UTF-8', - 'basque' => 'ISO-8859-1', - 'belarusian' => 'ISO-8859-5', - 'bosnian' => 'ISO-8859-1', - 'bulgarian' => 'windows-1251', - 'cambodian' => 'UTF-8', - 'catalan' => 'ISO-8859-1', - 'chinese_simplified' => 'gbk', - 'chinese_traditional' => 'big5', - 'croatian' => 'ISO-8859-2', - 'czech' => 'ISO-8859-2', - 'czech_informal' => 'ISO-8859-2', - 'danish' => 'ISO-8859-1', - 'dutch' => 'ISO-8859-1', - 'english' => 'ISO-8859-1', - 'english_british' => 'ISO-8859-1', - 'english_pirate' => 'UTF-8', - 'esperanto' => 'ISO-8859-3', - 'estonian' => 'ISO-8859-15', - 'filipino_tagalog' => 'UTF-8', - 'filipino_visayan' => 'UTF-8', - 'finnish' => 'ISO-8859-1', - 'french' => 'ISO-8859-1', - 'galician' => 'ISO-8859-1', - 'georgian' => 'UTF-8', - 'german' => 'ISO-8859-1', - 'german_informal' => 'ISO-8859-1', - 'greek' => 'windows-1253', - 'hebrew' => 'windows-1255', - 'hindi' => 'ISO-8859-1', - 'hungarian' => 'ISO-8859-2', - 'icelandic' => 'ISO-8859-1', - 'indonesian' => 'ISO-8859-1', - 'irish' => 'UTF-8', - 'italian' => 'ISO-8859-1', - 'japanese' => 'UTF-8', - 'khmer' => 'UTF-8', - 'korean' => 'UTF-8', - 'kurdish_kurmanji' => 'ISO-8859-9', - 'kurdish_sorani' => 'windows-1256', - 'lao' => 'tis-620', - 'latvian' => 'ISO-8859-13', - 'macedonian' => 'UTF-8', - 'malay' => 'ISO-8859-1', - 'malayalam' => 'UTF-8', - 'mongolian' => 'UTF-8', - 'nepali' => 'UTF-8', - 'norwegian' => 'ISO-8859-1', - 'persian' => 'UTF-8', - 'polish' => 'ISO-8859-2', - 'portuguese_brazilian' => 'ISO-8859-1', - 'portuguese_pt' => 'ISO-8859-1', - 'romanian' => 'ISO-8859-2', - 'russian' => 'windows-1251', - 'sakha' => 'UTF-8', - 'serbian_cyrillic' => 'ISO-8859-5', - 'serbian_latin' => 'ISO-8859-2', - 'sinhala' => 'UTF-8', - 'slovak' => 'ISO-8859-2', - 'slovenian' => 'ISO-8859-2', - 'spanish' => 'ISO-8859-1', - 'spanish_es' => 'ISO-8859-1', - 'spanish_latin' => 'ISO-8859-1', - 'swedish' => 'ISO-8859-1', - 'telugu' => 'UTF-8', - 'thai' => 'tis-620', - 'turkish' => 'ISO-8859-9', - 'turkmen' => 'ISO-8859-9', - 'ukrainian' => 'windows-1251', - 'urdu' => 'UTF-8', - 'uzbek_cyrillic' => 'ISO-8859-5', - 'uzbek_latin' => 'ISO-8859-5', - 'vietnamese' => 'UTF-8', - 'welsh' => 'ISO-8859-1', - 'yoruba' => 'UTF-8', - ]; - - // Map in the new locales. We do it like this because we want to try - // our best to capture the correct charset no matter what the status of - // the language upgrade is. - foreach ($lang_charsets as $key => $value) { - $locale = Lang::getLocaleFromLanguageName($key); - - if ($locale !== null) { - $lang_charsets[$locale] = $value; - } - } - - $lang_charset = $lang_charsets[Config::$language]; - } - - // Maps character sets used in old, non-Unicode SMF language files to the - // corresponding MySQL aliases for those character sets. This list only - // includes exact matches. - $charset_maps = [ - // Armenian - 'armscii-8' => 'armscii8', - // Chinese-traditional. - 'big5' => 'big5', - // Chinese-simplified. - 'gbk' => 'gbk', - // West European. - 'ISO-8859-1' => 'latin1', - // Romanian. - 'ISO-8859-2' => 'latin2', - // Turkish. - 'ISO-8859-9' => 'latin5', - // Latvian - 'ISO-8859-13' => 'latin7', - // Thai. - 'tis-620' => 'tis620', - // Persian, Chinese, etc. - 'UTF-8' => 'utf8mb3', - // Russian. - 'windows-1251' => 'cp1251', - // Arabic. - 'windows-1256' => 'cp1256', - ]; - - // Remove any mapped character sets that are unsupported by this MySQL server. - $charset_maps = array_intersect($charset_maps, $supported_charsets); - - // Manual character translation for a couple of rare character sets that old - // SMF language files might have used. - $translation_tables = [ - 'windows-1253' => [ - '0x80' => '0xE282AC', - '0x81' => '\'\'', - '0x82' => '0xE2809A', - '0x83' => '0xC692', - '0x84' => '0xE2809E', - '0x85' => '0xE280A6', - '0x86' => '0xE280A0', - '0x87' => '0xE280A1', - '0x88' => '\'\'', - '0x89' => '0xE280B0', - '0x8A' => '\'\'', - '0x8B' => '0xE280B9', - '0x8C' => '\'\'', - '0x8D' => '\'\'', - '0x8E' => '\'\'', - '0x8F' => '\'\'', - '0x90' => '\'\'', - '0x91' => '0xE28098', - '0x92' => '0xE28099', - '0x93' => '0xE2809C', - '0x94' => '0xE2809D', - '0x95' => '0xE280A2', - '0x96' => '0xE28093', - '0x97' => '0xE28094', - '0x98' => '\'\'', - '0x99' => '0xE284A2', - '0x9A' => '\'\'', - '0x9B' => '0xE280BA', - '0x9C' => '\'\'', - '0x9D' => '\'\'', - '0x9E' => '\'\'', - '0x9F' => '\'\'', - '0xA0' => '0xC2A0', - '0xA1' => '0xCE85', - '0xA2' => '0xCE86', - '0xA3' => '0xC2A3', - '0xA4' => '0xC2A4', - '0xA5' => '0xC2A5', - '0xA6' => '0xC2A6', - '0xA7' => '0xC2A7', - '0xA8' => '0xC2A8', - '0xA9' => '0xC2A9', - '0xAA' => '\'\'', - '0xAB' => '0xC2AB', - '0xAC' => '0xC2AC', - '0xAD' => '0xC2AD', - '0xAE' => '0xC2AE', - '0xAF' => '0xE28095', - '0xB0' => '0xC2B0', - '0xB1' => '0xC2B1', - '0xB2' => '0xC2B2', - '0xB3' => '0xC2B3', - '0xB4' => '0xCE84', - '0xB5' => '0xC2B5', - '0xB6' => '0xC2B6', - '0xB7' => '0xC2B7', - '0xB8' => '0xCE88', - '0xB9' => '0xCE89', - '0xBA' => '0xCE8A', - '0xBB' => '0xC2BB', - '0xBC' => '0xCE8C', - '0xBD' => '0xC2BD', - '0xBE' => '0xCE8E', - '0xBF' => '0xCE8F', - '0xC0' => '0xCE90', - '0xC1' => '0xCE91', - '0xC2' => '0xCE92', - '0xC3' => '0xCE93', - '0xC4' => '0xCE94', - '0xC5' => '0xCE95', - '0xC6' => '0xCE96', - '0xC7' => '0xCE97', - '0xC8' => '0xCE98', - '0xC9' => '0xCE99', - '0xCA' => '0xCE9A', - '0xCB' => '0xCE9B', - '0xCC' => '0xCE9C', - '0xCD' => '0xCE9D', - '0xCE' => '0xCE9E', - '0xCF' => '0xCE9F', - '0xD0' => '0xCEA0', - '0xD1' => '0xCEA1', - '0xD2' => '0xEFBFBD', - '0xD3' => '0xCEA3', - '0xD4' => '0xCEA4', - '0xD5' => '0xCEA5', - '0xD6' => '0xCEA6', - '0xD7' => '0xCEA7', - '0xD8' => '0xCEA8', - '0xD9' => '0xCEA9', - '0xDA' => '0xCEAA', - '0xDB' => '0xCEAB', - '0xDC' => '0xCEAC', - '0xDD' => '0xCEAD', - '0xDE' => '0xCEAE', - '0xDF' => '0xCEAF', - '0xE0' => '0xCEB0', - '0xE1' => '0xCEB1', - '0xE2' => '0xCEB2', - '0xE3' => '0xCEB3', - '0xE4' => '0xCEB4', - '0xE5' => '0xCEB5', - '0xE6' => '0xCEB6', - '0xE7' => '0xCEB7', - '0xE8' => '0xCEB8', - '0xE9' => '0xCEB9', - '0xEA' => '0xCEBA', - '0xEB' => '0xCEBB', - '0xEC' => '0xCEBC', - '0xED' => '0xCEBD', - '0xEE' => '0xCEBE', - '0xEF' => '0xCEBF', - '0xF0' => '0xCF80', - '0xF1' => '0xCF81', - '0xF2' => '0xCF82', - '0xF3' => '0xCF83', - '0xF4' => '0xCF84', - '0xF5' => '0xCF85', - '0xF6' => '0xCF86', - '0xF7' => '0xCF87', - '0xF8' => '0xCF88', - '0xF9' => '0xCF89', - '0xFA' => '0xCF8A', - '0xFB' => '0xCF8B', - '0xFC' => '0xCF8C', - '0xFD' => '0xCF8D', - '0xFE' => '0xCF8E', - ], - 'windows-1255' => [ - '0x80' => '0xE282AC', - '0x81' => '\'\'', - '0x82' => '0xE2809A', - '0x83' => '0xC692', - '0x84' => '0xE2809E', - '0x85' => '0xE280A6', - '0x86' => '0xE280A0', - '0x87' => '0xE280A1', - '0x88' => '0xCB86', - '0x89' => '0xE280B0', - '0x8A' => '\'\'', - '0x8B' => '0xE280B9', - '0x8C' => '\'\'', - '0x8D' => '\'\'', - '0x8E' => '\'\'', - '0x8F' => '\'\'', - '0x90' => '\'\'', - '0x91' => '0xE28098', - '0x92' => '0xE28099', - '0x93' => '0xE2809C', - '0x94' => '0xE2809D', - '0x95' => '0xE280A2', - '0x96' => '0xE28093', - '0x97' => '0xE28094', - '0x98' => '0xCB9C', - '0x99' => '0xE284A2', - '0x9A' => '\'\'', - '0x9B' => '0xE280BA', - '0x9C' => '\'\'', - '0x9D' => '\'\'', - '0x9E' => '\'\'', - '0x9F' => '\'\'', - '0xA0' => '0xC2A0', - '0xA1' => '0xC2A1', - '0xA2' => '0xC2A2', - '0xA3' => '0xC2A3', - '0xA4' => '0xE282AA', - '0xA5' => '0xC2A5', - '0xA6' => '0xC2A6', - '0xA7' => '0xC2A7', - '0xA8' => '0xC2A8', - '0xA9' => '0xC2A9', - '0xAA' => '0xC397', - '0xAB' => '0xC2AB', - '0xAC' => '0xC2AC', - '0xAD' => '0xC2AD', - '0xAE' => '0xC2AE', - '0xAF' => '0xC2AF', - '0xB0' => '0xC2B0', - '0xB1' => '0xC2B1', - '0xB2' => '0xC2B2', - '0xB3' => '0xC2B3', - '0xB4' => '0xC2B4', - '0xB5' => '0xC2B5', - '0xB6' => '0xC2B6', - '0xB7' => '0xC2B7', - '0xB8' => '0xC2B8', - '0xB9' => '0xC2B9', - '0xBA' => '0xC3B7', - '0xBB' => '0xC2BB', - '0xBC' => '0xC2BC', - '0xBD' => '0xC2BD', - '0xBE' => '0xC2BE', - '0xBF' => '0xC2BF', - '0xC0' => '0xD6B0', - '0xC1' => '0xD6B1', - '0xC2' => '0xD6B2', - '0xC3' => '0xD6B3', - '0xC4' => '0xD6B4', - '0xC5' => '0xD6B5', - '0xC6' => '0xD6B6', - '0xC7' => '0xD6B7', - '0xC8' => '0xD6B8', - '0xC9' => '0xD6B9', - '0xCA' => '0xEFBFBD', - '0xCB' => '0xD6BB', - '0xCC' => '0xD6BC', - '0xCD' => '0xD6BD', - '0xCE' => '0xD6BE', - '0xCF' => '0xD6BF', - '0xD0' => '0xD780', - '0xD1' => '0xD781', - '0xD2' => '0xD782', - '0xD3' => '0xD783', - '0xD4' => '0xD7B0', - '0xD5' => '0xD7B1', - '0xD6' => '0xD7B2', - '0xD7' => '0xD7B3', - '0xD8' => '0xD7B4', - '0xD9' => '\'\'', - '0xDA' => '\'\'', - '0xDB' => '\'\'', - '0xDC' => '\'\'', - '0xDD' => '\'\'', - '0xDE' => '\'\'', - '0xDF' => '\'\'', - '0xE0' => '0xD790', - '0xE1' => '0xD791', - '0xE2' => '0xD792', - '0xE3' => '0xD793', - '0xE4' => '0xD794', - '0xE5' => '0xD795', - '0xE6' => '0xD796', - '0xE7' => '0xD797', - '0xE8' => '0xD798', - '0xE9' => '0xD799', - '0xEA' => '0xD79A', - '0xEB' => '0xD79B', - '0xEC' => '0xD79C', - '0xED' => '0xD79D', - '0xEE' => '0xD79E', - '0xEF' => '0xD79F', - '0xF0' => '0xD7A0', - '0xF1' => '0xD7A1', - '0xF2' => '0xD7A2', - '0xF3' => '0xD7A3', - '0xF4' => '0xD7A4', - '0xF5' => '0xD7A5', - '0xF6' => '0xD7A6', - '0xF7' => '0xD7A7', - '0xF8' => '0xD7A8', - '0xF9' => '0xD7A9', - '0xFA' => '0xD7AA', - '0xFB' => '\'\'', - '0xFC' => '\'\'', - '0xFD' => '0xE2808E', - '0xFE' => '0xE2808F', - ], - ]; - - // Create a MySQL function to decode entities. - Db::$db->disableQueryCheck = true; - Db::$db->query( - '', - 'CREATE FUNCTION IF NOT EXISTS {identifier:db_name}.smf_entity_decode(txt TEXT CHARSET utf8mb4) RETURNS TEXT CHARSET utf8mb4 - NO SQL - DETERMINISTIC - BEGIN - - DECLARE tmp TEXT CHARSET utf8mb4 DEFAULT txt; - DECLARE entity TEXT CHARSET utf8mb4; - DECLARE pos1 INT DEFAULT 1; - DECLARE pos2 INT; - DECLARE codepoint INT; - - IF txt IS NULL THEN - RETURN NULL; - END IF; - LOOP - SET pos1 = LOCATE("&#", tmp, pos1); - IF pos1 = 0 THEN - RETURN tmp; - END IF; - SET pos2 = LOCATE(";", tmp, pos1 + 2); - IF pos2 > pos1 THEN - SET entity = SUBSTRING(tmp, pos1, pos2 - pos1 + 1); - IF entity REGEXP "^&#[[:digit:]]+;$" THEN - SET codepoint = CAST(SUBSTRING(entity, 3, pos2 - pos1 - 2) AS UNSIGNED); - SET tmp = CONCAT(LEFT(tmp, pos1 - 1), CHAR(codepoint USING utf32), SUBSTRING(tmp, pos2 + 1)); - END IF; - IF entity REGEXP "^&#x[[:xdigit:]]+;$" THEN - SET codepoint = CAST(CONV(SUBSTRING(entity, 4, pos2 - pos1 - 3), 16, 10) AS UNSIGNED); - SET tmp = CONCAT(LEFT(tmp, pos1 - 1), CHAR(codepoint USING utf32), SUBSTRING(tmp, pos2 + 1)); - END IF; - END IF; - SET pos1 = pos1 + 1; - END LOOP; - END', - [ - 'db_name' => Db::$db->name, - ] - ); - Db::$db->disableQueryCheck = false; - - // Get all the SMF tables. - $tables = Db::$db->list_tables(false, Db::$db->prefix . '%'); - - // Set some initial values for the templates. - $upcontext['table_count'] = count($tables); - $upcontext['cur_table_num'] = (int) $_GET['substep']; - $upcontext['cur_table_name'] = str_replace(Db::$db->prefix, '', $tables[$upcontext['cur_table_num']]); - $upcontext['step_progress'] = (int) ($upcontext['cur_table_num'] / $upcontext['table_count'] * 100); - - foreach ($tables as $table_num => $table) { - if ($table_num < $upcontext['cur_table_num']) { - $upcontext['previous_tables'][] = $table; - } - } - - // Make sure we're ready & have painted the template before proceeding - if ($support_js && !isset($_GET['xml'])) { - $_GET['substep'] = 0; - - return false; - } - - foreach ($tables as $table_num => $table) { - if ($table_num + 1 < $upcontext['cur_table_num']) { - continue; - } - - $upcontext['cur_table_num'] = $table_num + 1; - $upcontext['cur_table_name'] = str_replace(Db::$db->prefix, '', $table); - $upcontext['step_progress'] = (int) (($table_num + 1) / $upcontext['table_count'] * 100); - - // Do we need to pause? - nextSubstep($table_num); - - // Just to make sure it doesn't time out. - Sapi::resetTimeout(); - - $table_charset = Db::$db->detect_charset($table); - $structure = Db::$db->table_structure($table); - - // If there's a fulltext index, we need to drop it first... - foreach ($structure['indexes'] as $i => $index) { - if ($index['type'] === 'fulltext') { - Db::$db->remove_index($table, $index['name']); - - if ( - $table = Db::$db->prefix . 'messages' - && (Config::$modSettings['search_index'] ?? null) === 'fulltext' - ) { - Config::updateModSettings(['search_index' => '']); - $upcontext['dropping_index'] = true; - } - } - } - - // Is the table already using some version of Unicode? - $table_is_unicode = str_starts_with($table_charset, 'utf') || $table_charset === 'ucs2'; - - // We might need to do each column individually. - $convert_columns_individually = !( - // Probably don't need to if the table uses the expected charset. - $table_charset === ($charset_maps[$lang_charset] ?? null) - // Probably don't need to if they're just different versions of Unicode. - || ( - $table_is_unicode - && ( - !isset($charset_maps[$lang_charset]) - || str_starts_with($charset_maps[$lang_charset], 'utf') - || $charset_maps[$lang_charset] === 'ucs2' - ) - ) - ); - - $string_columns = []; - - foreach ($structure['columns'] as $c => $column) { - if (!in_array($column['type'], ['varchar', 'char', 'tinytext', 'text', 'mediumtext', 'longtext', 'enum', 'set'])) { - continue; - } - - $string_columns[] = $column['name']; - - $structure['columns'][$c]['charset'] = Db::$db->detect_charset($table, $column['name']); - - // We need to do each column individually if any of them use a - // different character set than the table as a whole. - if ($structure['columns'][$c]['charset'] !== $table_charset) { - $convert_columns_individually = true; - } - } - - if ($command_line && ($table_charset !== 'utf8mb4' || $convert_columns_individually)) { - echo 'Converting table ' . $structure['name'] . ' to utf8mb4...'; - } - - // Do we need to do each column individually? - if ($convert_columns_individually) { - foreach ($structure['columns'] as $c => $column) { - if (!isset($column['charset'])) { - continue; - } - - if ($column['charset'] !== ($charset_maps[$lang_charset] ?? null)) { - // First, convert the column to binary. - Db::$db->change_column( - $table, - $column['name'], - [ - 'type' => strtr($column['type'], ['text' => 'blob', 'char' => 'binary']), - ], - ); - - // Which encoding should we be converting from? - if (!isset($charset_maps[$lang_charset])) { - // $lang_charset doesn't map to a supported database charset, which - // means that the string was stored using the wrong charset, but - // still would have been interpreted as $lang_charset once retrieved. - $from_charset = $lang_charset; - } else { - // This column simply isn't using the table's default character set. - $from_charset = $column['charset']; - } - - // If $from_charset is already some variant of UTF-8, we don't need to - // deal with the byte-level conversion step. - if (str_starts_with(strtolower($from_charset), 'utf8')) { - continue; - } - - if (!in_array($from_charset, $supported_charsets)) { - // Build a huge REPLACE statement. - $replace = '{identifier:column}'; - - if (isset($translation_tables[$from_charset])) { - foreach ($translation_tables[$from_charset] as $from => $to) { - $replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')'; - } - } else { - try { - for ($i = 0; $i <= 0xFF; $i++) { - $from = '0x' . strtoupper(dechex($i)); - $to = '0x' . strtoupper(bin2hex(mb_convert_encoding(chr($i), 'UTF-8', $from_charset))); - - if ($from !== $to) { - $replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')'; - } - } - } catch (\Throwable $e) { - // mb_convert_encoding will throw a ValueError if - // either encoding is unrecognized. - unset($replace); - continue; - } - } - - // Convert the characters to UTF-8, using raw bytes. - Db::$db->query( - '', - 'UPDATE {identifier:table} - SET {identifier:column} = ' . $replace, - [ - 'table' => $table, - 'column' => $column['name'], - ], - ); - } - } - } - - // Change the table's character set to utf8mb4. - Db::$db->query( - '', - 'ALTER TABLE {identifier:table_name} - CONVERT TO CHARACTER SET utf8mb4', - [ - 'table_name' => $table, - ] - ); - - // Convert each column from binary back to text. - foreach ($structure['columns'] as $c => $column) { - Db::$db->change_column( - $table, - $column['name'], - [ - 'type' =>$column['type'], - ], - ); - } - } else { - // Change the table's character set to utf8mb4. - Db::$db->query( - '', - 'ALTER TABLE {identifier:table_name} - CONVERT TO CHARACTER SET utf8mb4', - [ - 'table_name' => $table, - ] - ); - } - - // Convert entities to characters. - // @todo Do this in batches for the sake of large forums. - if (!empty($string_columns)) { - $col_ent_decode = []; - - foreach ($string_columns as $col) { - $col_ent_decode[] = $col . ' = smf_entity_decode(' . $col . ')'; - } - - Db::$db->query( - '', - 'UPDATE {identifier:table_name} - SET {raw:col_ent_decode}', - [ - 'table_name' => $table, - 'col_ent_decode' => implode(', ', $col_ent_decode), - ] - ); - } - - if ($command_line && ($table_charset !== 'utf8mb4' || $convert_columns_individually)) { - echo " done.\n"; - } - } - - // Set the default character set for the database as a whole to utf8mb4. - Db::$db->query( - '', - 'ALTER DATABASE {identifier:db_name} - CHARACTER SET utf8mb4', - [ - 'db_name' => Db::$db->name, - ] - ); - - Db::$db->query( - '', - 'DROP FUNCTION IF EXISTS {identifier:db_name}.smf_entity_decode', - [ - 'db_name' => Db::$db->name, - ] - ); - - // Record whatever the previous language character set was, unless it was already UTF-8. - if (!str_starts_with(strtolower($lang_charset), 'utf8')) { - Config::updateModSettings(['previousCharacterSet' => $lang_charset]); - } - - // Remove obsolete settings. - Config::updateModSettings(['global_character_set' => null]); - Config::updateSettingsFile(['db_character_set' => '', 'db_mb4' => null]); - - if ($upcontext['dropping_index'] && $command_line) { - echo "\n" . '', Lang::$txt['upgrade_fulltext_error'], ''; - flush(); - } - - // Make sure we move on! - if ($command_line) { - return Cleanup(); - } - - $_GET['substep'] = 0; - - return false; -} - -/** - * Wrapper for unserialize that attempts to repair corrupted serialized data strings - * - * @param string $string Serialized data that may or may not have been corrupted - * @return string|bool The unserialized data, or false if the repair failed - */ -function upgrade_unserialize($string) -{ - if (!is_string($string)) { - $data = false; - } - // Might be JSON already. - elseif (str_starts_with($string, '{')) { - $data = @json_decode($string, true); - - if (is_null($data)) { - $data = false; - } - } elseif (in_array(substr($string, 0, 2), ['b:', 'i:', 'd:', 's:', 'a:', 'N;'])) { - $data = @Utils::safeUnserialize($string); - - // The serialized data is broken. - if ($data === false) { - // This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8) - $new_string = preg_replace_callback( - '~\bs:(\d+):"(.*?)";(?=$|[bidsaO]:|[{}}]|N;)~s', - function ($matches) { - return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";'; - }, - $string, - ); - - // @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc. - - // Did it work? - $data = @Utils::safeUnserialize($string); - } - } - // Just a plain string, then. - else { - $data = false; - } - - return $data; -} - -function serialize_to_json() -{ - global $command_line, $upcontext, $support_js; - - $upcontext['sub_template'] = isset($_GET['xml']) ? 'serialize_json_xml' : 'serialize_json'; - - // First thing's first - did we already do this? - if (!empty(Config::$modSettings['json_done'])) { - if ($command_line) { - return ConvertUtf8(); - } - - return true; - } - - // Needed when writing settings - if (!function_exists('cache_put_data')) { - require_once Config::$sourcedir . '/Cache/CacheApi.php'; - } - - // Done it already - js wise? - if (!empty($_POST['json_done'])) { - Config::updateModSettings(['json_done' => true]); - - return true; - } - - // List of tables affected by this function - // name => array('key', col1[,col2|true[,col3]]) - // If 3rd item in array is true, it indicates that col1 could be empty... - $tables = [ - 'background_tasks' => ['id_task', 'task_data'], - 'log_actions' => ['id_action', 'extra'], - 'log_online' => ['session', 'url'], - 'log_packages' => ['id_install', 'db_changes', 'failed_steps', 'credits'], - 'log_spider_hits' => ['id_hit', 'url'], - 'log_subscribed' => ['id_sublog', 'pending_details'], - 'pm_rules' => ['id_rule', 'criteria', 'actions'], - 'qanda' => ['id_question', 'answers'], - 'subscriptions' => ['id_subscribe', 'cost'], - 'user_alerts' => ['id_alert', 'extra', true], - 'user_drafts' => ['id_draft', 'to_list', true], - // These last two are a bit different - we'll handle those separately - 'settings' => [], - 'themes' => [], - ]; - - // Set up some context stuff... - // Because we're not using numeric indices, we need this to figure out the current table name... - $keys = array_keys($tables); - - $upcontext['page_title'] = Lang::$txt['converting_json']; - $upcontext['table_count'] = count($keys); - $upcontext['cur_table_num'] = $_GET['substep']; - $upcontext['cur_table_name'] = $keys[$_GET['substep']] ?? $keys[0]; - $upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100); - - foreach ($keys as $id => $table) { - if ($id < $_GET['substep']) { - $upcontext['previous_tables'][] = $table; - } - } - - if ($command_line) { - echo 'Converting data from serialize() to json_encode().'; - } - - // Fix the data in each table - for ($substep = $_GET['substep']; $substep < $upcontext['table_count']; $substep++) { - $upcontext['cur_table_name'] = $keys[$substep + 1] ?? $keys[$substep]; - $upcontext['cur_table_num'] = $substep + 1; - - $upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100); - - // Do we need to pause? - nextSubstep($substep); - - // Initialize a few things... - $where = ''; - $vars = []; - $table = $keys[$substep]; - $info = $tables[$table]; - - // Now the fun - build our queries and all that fun stuff - if ($table == 'settings') { - // Now a few settings... - $serialized_settings = [ - 'attachment_basedirectories', - 'attachmentUploadDir', - 'cal_today_birthday', - 'cal_today_event', - 'cal_today_holiday', - 'displayFields', - 'last_attachments_directory', - 'memberlist_cache', - 'search_custom_index_config', - 'spider_name_cache', - ]; - - // Loop through and fix these... - $new_settings = []; - - if ($command_line) { - echo "\n" . 'Fixing some settings...'; - } - - foreach ($serialized_settings as $var) { - if (isset(Config::$modSettings[$var])) { - // Attempt to unserialize the setting - $temp = upgrade_unserialize(Config::$modSettings[$var]); - - if (!$temp && $command_line) { - echo "\n - Failed to unserialize the '" . $var . "' setting. Skipping."; - } elseif ($temp !== false) { - $new_settings[$var] = json_encode($temp); - } - } - } - - // Update everything at once - Config::updateModSettings($new_settings, true); - - if ($command_line) { - echo ' done.'; - } - } elseif ($table == 'themes') { - // Finally, fix the admin prefs. Unfortunately this is stored per theme, but hopefully they only have one theme installed at this point... - $query = Db::$db->query( - '', - 'SELECT id_member, id_theme, value FROM {db_prefix}themes - WHERE variable = {string:admin_prefs}', - [ - 'admin_prefs' => 'admin_preferences', - ], - ); - - if (Db::$db->num_rows($query) != 0) { - while ($row = Db::$db->fetch_assoc($query)) { - $temp = upgrade_unserialize($row['value']); - - if ($command_line) { - if ($temp === false) { - echo "\n" . 'Unserialize of admin_preferences for user ' . $row['id_member'] . ' failed. Skipping.'; - } else { - echo "\n" . 'Fixing admin preferences...'; - } - } - - if ($temp !== false) { - $row['value'] = json_encode($temp); - - // Even though we have all values from the table, UPDATE is still faster than REPLACE - Db::$db->query( - '', - 'UPDATE {db_prefix}themes - SET value = {string:prefs} - WHERE id_theme = {int:theme} - AND id_member = {int:member} - AND variable = {string:admin_prefs}', - [ - 'prefs' => $row['value'], - 'theme' => $row['id_theme'], - 'member' => $row['id_member'], - 'admin_prefs' => 'admin_preferences', - ], - ); - - if ($command_line) { - echo ' done.'; - } - } - } - - Db::$db->free_result($query); - } - } else { - // First item is always the key... - $key = $info[0]; - unset($info[0]); - - // Now we know what columns we have and such... - if (count($info) == 2 && $info[2] === true) { - $col_select = $info[1]; - $where = ' WHERE ' . $info[1] . ' != {empty}'; - } else { - $col_select = implode(', ', $info); - } - - $query = Db::$db->query( - '', - 'SELECT ' . $key . ', ' . $col_select . ' - FROM {db_prefix}' . $table . $where, - [], - ); - - if (Db::$db->num_rows($query) != 0) { - if ($command_line) { - echo "\n" . ' +++ Fixing the "' . $table . '" table...'; - flush(); - } - - while ($row = Db::$db->fetch_assoc($query)) { - $update = ''; - - // We already know what our key is... - foreach ($info as $col) { - if ($col !== true && $row[$col] != '') { - $temp = upgrade_unserialize($row[$col]); - - // Oh well... - if ($temp === false) { - $temp = []; - - if ($command_line) { - echo "\nFailed to unserialize " . $row[$col] . ". Setting to empty value.\n"; - } - } - - $row[$col] = json_encode($temp); - - // Build our SET string and variables array - $update .= (empty($update) ? '' : ', ') . $col . ' = {string:' . $col . '}'; - $vars[$col] = $row[$col]; - } - } - - $vars[$key] = $row[$key]; - - // In a few cases, we might have empty data, so don't try to update in those situations... - if (!empty($update)) { - Db::$db->query( - '', - 'UPDATE {db_prefix}' . $table . ' - SET ' . $update . ' - WHERE ' . $key . ' = {' . ($key == 'session' ? 'string' : 'int') . ':' . $key . '}', - $vars, - ); - } - } - - if ($command_line) { - echo ' done.'; - } - - // Free up some memory... - Db::$db->free_result($query); - } - } - - // If this is XML to keep it nice for the user do one table at a time anyway! - if (isset($_GET['xml'])) { - return upgradeExit(); - } - } - - if ($command_line) { - echo "\n" . 'Successful.' . "\n"; - flush(); - } - $upcontext['step_progress'] = 100; - - // Last but not least, insert a dummy setting so we don't have to do this again in the future... - Config::updateModSettings(['json_done' => true]); - - $_GET['substep'] = 0; - - // Make sure we move on! - if ($command_line) { - return ConvertUtf8(); - } - - return true; -} - -function Cleanup() -{ - global $command_line, $upcontext, $support_js; - - $upcontext['sub_template'] = isset($_GET['xml']) ? 'cleanup_xml' : 'cleanup'; - $upcontext['page_title'] = Lang::$txt['upgrade_step_cleanup']; - - // Done it already - js wise? - if (!empty($_POST['cleanup_done'])) { - return true; - } - - $cleanupSteps = [ - 0 => 'CleanupLanguages', - 1 => 'CleanupAgreements', - ]; - - $upcontext['steps_count'] = count($cleanupSteps); - $upcontext['cur_substep_num'] = ((int) $_GET['substep']) ?? 0; - $upcontext['cur_substep'] = $cleanupSteps[$upcontext['cur_substep_num']] ?? $cleanupSteps[0]; - $upcontext['cur_substep_name'] = Lang::$txt['upgrade_step_cleanup_' . $upcontext['cur_substep']] ?? Lang::$txt['upgrade_step_cleanup']; - $upcontext['step_progress'] = (int) (($upcontext['cur_substep_num'] / $upcontext['steps_count']) * 100); - - foreach ($cleanupSteps as $id => $substep) { - if ($id < $_GET['substep']) { - $upcontext['previous_substeps'][] = $substep; - } - } - - if ($command_line) { - echo 'Cleaning up.'; - } - - // Dubstep. - for ($substep = $upcontext['cur_substep_num']; $substep < $upcontext['steps_count']; $substep++) { - $upcontext['step_progress'] = (int) (($substep / $upcontext['steps_count']) * 100); - $upcontext['cur_substep_name'] = Lang::$txt['upgrade_step_cleanup_' . $cleanupSteps[$substep]] ?? Lang::$txt['upgrade_step_cleanup']; - $upcontext['cur_substep_num'] = $substep + 1; - - if ($command_line) { - echo "\n" . ' +++ Clean up "' . $upcontext['cur_substep_name'] . '"...'; - } - - // Timeouts - nextSubstep($substep); - - if ($command_line) { - echo ' done.'; - } - - // Just to make sure it doesn't time out. - if (function_exists('apache_reset_timeout')) { - @apache_reset_timeout(); - } - - // Do the cleanup stuff. - $cleanupSteps[$substep](); - - // If this is XML to keep it nice for the user do one cleanup at a time anyway! - if (isset($_GET['xml'])) { - return upgradeExit(); - } - } - - if ($command_line) { - echo "\n" . 'Successful.' . "\n"; - flush(); - } - - $upcontext['step_progress'] = 100; - $_GET['substep'] = 0; - $_POST['cleanup_done'] = true; - - return true; -} - -function CleanupLanguages() -{ - global $upcontext, $upgrade_path, $command_line; - - $old_languages_dir = isset(Config::$modSettings['theme_dir']) ? Config::$modSettings['theme_dir'] . '/languages' : $upgrade_path . '/Themes/default/languages'; - - // Can't do this if the old Themes/default/languages directory is not writable. - if (!quickFileWritable($old_languages_dir)) { - return; - } - - $dir = dir($old_languages_dir); - - while ($entry = $dir->read()) { - if (in_array($entry, ['.', '..', 'index.php'])) { - continue; - } - - // Skip ThemeStrings - if (str_starts_with($entry, 'ThemeStrings.')) { - continue; - } - - // Rename Settings to ThemeStrings. - if (str_starts_with($entry, 'Settings.') && str_ends_with($entry, '.php') && !str_contains($entry, '-utf8')) { - quickFileWritable($old_languages_dir . '/' . $entry); - rename($old_languages_dir . '/' . $entry, $old_languages_dir . '/' . str_replace('Settings.', 'ThemeStrings.', $entry)); - } else { - deleteFile($old_languages_dir . '/' . $entry); - } - } - $dir->close(); -} - -function CleanupAgreements() -{ - global $upcontext, $upgrade_path, $command_line; - - // Can't do this if the old Themes/default/languages directory is not writable. - if(!quickFileWritable(Config::$boarddir)) { - return; - } - - $dir = dir(Config::$boarddir); - - while ($entry = $dir->read()) { - if (in_array($entry, ['.', '..', 'index.php'])) { - continue; - } - - // Skip anything not agreements. - if (!str_starts_with($entry, 'agreements.') || !str_ends_with($entry, '.txt')) { - continue; - } - - rename(Config::$boarddir . '/' . $entry, Config::$languagesdir . '/' . $entry); - } - $dir->close(); -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Templates are below this point -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -// This is what is displayed if there's any chmod to be done. If not it returns nothing... -function template_chmod() -{ - global $upcontext, $settings; - - // Don't call me twice! - if (!empty($upcontext['chmod_called'])) { - return; - } - - $upcontext['chmod_called'] = true; - - // Nothing? - if (empty($upcontext['chmod']['files']) && empty($upcontext['chmod']['ftp_error'])) { - return; - } - - // Was it a problem with Windows? - if (!empty($upcontext['chmod']['ftp_error']) && $upcontext['chmod']['ftp_error'] == 'total_mess') { - echo ' -
-

', Lang::$txt['upgrade_writable_files'], '

-
    -
  • ' . implode('
  • -
  • ', $upcontext['chmod']['files']) . '
  • -
-
'; - - return false; - } - - echo ' -
-

', Lang::$txt['upgrade_ftp_login'], '

-

', Lang::$txt['upgrade_ftp_perms'], '

- '; - - if (!empty($upcontext['chmod']['ftp_error'])) { - echo ' -
-

', Lang::$txt['upgrade_ftp_error'], '

- ', $upcontext['chmod']['ftp_error'], ' -

'; - } - - if (empty($upcontext['chmod_in_form'])) { - echo ' -
'; - } - - echo ' -
-
- -
-
-
- - -
- -
', Lang::$txt['ftp_server_info'], '
-
-
- -
-
- -
', Lang::$txt['ftp_username_info'], '
-
-
- -
-
- -
', Lang::$txt['ftp_password_info'], '
-
-
- -
-
- -
', !empty($upcontext['chmod']['path']) ? Lang::$txt['ftp_path_found_info'] : Lang::$txt['ftp_path_info'], '
-
-
- -
- -
'; - - if (empty($upcontext['chmod_in_form'])) { - echo ' -
'; - } - - echo ' -
'; -} - -function template_upgrade_above() -{ - global $settings, $upcontext, $upgradeurl; - - echo ' - - - - - ', Lang::$txt['upgrade_upgrade_utility'], ' - - - ', Lang::$txt['lang_rtl'] == '1' ? '' : '', ' - - - - - -
- -
-
-
-
-

', Lang::$txt['upgrade_progress'], '

-
    '; - - foreach ((array) $upcontext['steps'] as $num => $step) { - echo ' - - ', Lang::$txt['upgrade_step'], ' ', $step[0], ': ', Lang::$txt[$step[1]], ' - '; - } - - echo ' -
-
- -
-
-

', Lang::$txt['upgrade_overall_progress'], '

-
- ', $upcontext['overall_percent'], '% -
'; - - if (isset($upcontext['step_progress'])) { - echo ' -
-

', Lang::$txt['upgrade_step_progress'], '

-
- ', $upcontext['step_progress'], '% -
'; - } - - echo ' -
-

', isset($upcontext['substep_progress_name']) ? trim(strtr($upcontext['substep_progress_name'], ['.' => ''])) : '', '

-
- ', $upcontext['substep_progress'] ?? 0, '% -
'; - - // How long have we been running this? - $elapsed = time() - $upcontext['started']; - $mins = (int) ($elapsed / 60); - $seconds = $elapsed - $mins * 60; - echo ' -
- ', Lang::$txt['upgrade_time_elapsed'], ': - ', $mins, ' ', Lang::$txt['upgrade_time_mins'], ', ', $seconds, ' ', Lang::$txt['upgrade_time_secs'], '. -
'; - echo ' -
-
-

', $upcontext['page_title'], '

-
'; -} - -function template_upgrade_below() -{ - global $upcontext; - - if (!empty($upcontext['pause'])) { - echo ' - ', Lang::$txt['upgrade_incomplete'], '.
- -

', Lang::$txt['upgrade_not_quite_done'], '

-

- ', Lang::$txt['upgrade_paused_overload'], ' -

'; - } - - if (!empty($upcontext['custom_warning'])) { - echo ' -
-

', Lang::$txt['upgrade_note'], '

- ', $upcontext['custom_warning'], ' -
'; - } - - echo ' -
'; - - if (!empty($upcontext['continue'])) { - echo ' - '; - } - - if (!empty($upcontext['skip'])) { - echo ' - '; - } - - echo ' -
- -
-
-
-
-
-
- '; - - // Are we on a pause? - if (!empty($upcontext['pause'])) { - echo ' - '; - } - - echo ' - -'; -} - -function template_xml_above() -{ - global $upcontext; - - echo '<', '?xml version="1.0" encoding="UTF-8"?', '> - '; - - if (!empty($upcontext['get_data'])) { - foreach ((array) $upcontext['get_data'] as $k => $v) { - echo ' - ', $v, ''; - } - } -} - -function template_xml_below() -{ - echo ' - '; -} - -function template_error_message() -{ - global $upcontext; - - echo ' -
- ', $upcontext['error_msg'], ' -
- ', Lang::$txt['upgrade_respondtime_clickhere'], ' -
'; -} - -function template_welcome_message() -{ - global $upcontext, $disable_security, $settings; - - echo ' - - -

', Lang::getTxt('upgrade_ready_proceed', ['SMF_VERSION' => SMF_VERSION]), '

-
- - - '; - - $upcontext['chmod_in_form'] = true; - template_chmod(); - - // For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade! - if ($upcontext['is_large_forum']) { - echo ' -
-

', Lang::$txt['upgrade_warning'], '

- ', Lang::$txt['upgrade_warning_lots_data'], ' -
'; - } - - // A warning message? - if (!empty($upcontext['warning'])) { - echo ' -
-

', Lang::$txt['upgrade_warning'], '

- ', $upcontext['warning'], ' -
'; - } - - // Paths are incorrect? - echo ' -
-

', Lang::$txt['upgrade_critical_error'], '

- ', Lang::getTxt('upgrade_error_script_js', ['url' => 'https://download.simplemachines.org/?tools']), ' -
'; - - // Is there someone already doing this? - if (!empty($upcontext['user']['id']) && (time() - $upcontext['started'] < 72600 || time() - $upcontext['updated'] < 3600)) { - $ago = time() - $upcontext['started']; - $ago_hours = floor($ago / 3600); - $ago_minutes = (int) (((int) ($ago / 60)) % 60); - $ago_seconds = intval($ago % 60); - $agoTxt = $ago < 60 ? 'upgrade_time_s' : ($ago < 3600 ? 'upgrade_time_ms' : 'upgrade_time_hms'); - - $updated = time() - $upcontext['updated']; - $updated_hours = floor($updated / 3600); - $updated_minutes = intval(((int) ($updated / 60)) % 60); - $updated_seconds = intval($updated % 60); - $updatedTxt = $updated < 60 ? 'upgrade_time_updated_s' : ($updated < 3600 ? 'upgrade_time_updated_hm' : 'upgrade_time_updated_hms'); - - echo ' -
-

', Lang::$txt['upgrade_warning'], '

-

', Lang::getTxt('upgrade_time_user', $upcontext['user']), '

-

', Lang::getTxt($agoTxt, ['s' => $ago_seconds, 'm' => $ago_minutes, 'h' => $ago_hours]), '

-

', Lang::getTxt($updatedTxt, ['s' => $updated_seconds, 'm' => $updated_minutes, 'h' => $updated_hours]), '

'; - - if ($updated < 600) { - echo ' -

', Lang::$txt['upgrade_run_script'], ' ', $upcontext['user']['name'], ' ', Lang::$txt['upgrade_run_script2'], '

'; - } - - if ($updated > $upcontext['inactive_timeout']) { - echo ' -

', Lang::$txt['upgrade_run'], '

'; - } elseif ($upcontext['inactive_timeout'] > 120) { - echo ' -

', Lang::getTxt('upgrade_script_timeout_minutes', ['name' => $upcontext['user']['name'], 'timeout' => round($upcontext['inactive_timeout'] / 60, 1)]), '

'; - } else { - echo ' -

', Lang::getTxt('upgrade_script_timeout_seconds', ['name' => $upcontext['user']['name'], 'timeout' => $upcontext['inactive_timeout']]), '

'; - } - - echo ' -
'; - } - - echo ' - ', Lang::$txt['upgrade_admin_login'], ' ', $disable_security ? Lang::$txt['upgrade_admin_disabled'] : '', ' -

', Lang::$txt['upgrade_sec_login'], '

-
-
- -
-
- '; - - if (!empty($upcontext['username_incorrect'])) { - echo ' -
', Lang::$txt['upgrade_wrong_username'], '
'; - } - - echo ' -
-
- -
-
- '; - - if (!empty($upcontext['password_failed'])) { - echo ' -
', Lang::$txt['upgrade_wrong_password'], '
'; - } - - echo ' -
'; - - // Can they continue? - if (!empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] >= $upcontext['inactive_timeout'] && $upcontext['user']['step'] > 1) { - echo ' -
- -
'; - } - - echo ' -
- - ', Lang::$txt['upgrade_bypass'], ' - - - '; - - // Say we want the continue button! - $upcontext['continue'] = !empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] < $upcontext['inactive_timeout'] ? 2 : 1; - - // This defines whether javascript is going to work elsewhere :D - echo ' - '; -} - -function template_upgrade_options() -{ - global $upcontext; - - echo ' -

', Lang::$txt['upgrade_areyouready'], '

- '; - - // Warning message? - if (!empty($upcontext['upgrade_options_warning'])) { - echo ' -
-

', Lang::$txt['upgrade_warning'], '

- ', $upcontext['upgrade_options_warning'], ' -
'; - } - - echo ' -
    -
  • - - - (', Lang::$txt['upgrade_recommended'], ') -
  • -
  • - - - (', Lang::$txt['upgrade_customize'], ') - -
  • -
  • - - -
  • -
  • - - -
  • '; - - if (!empty($upcontext['karma_installed']['good']) || !empty($upcontext['karma_installed']['bad'])) { - echo ' -
  • - - -
  • '; - } - - // If attachment step has been run previously, offer an option to do it again. - // Helpful if folks had improper attachment folders specified previously. - if (!empty(Config::$modSettings['attachments_21_done'])) { - echo ' -
  • - - -
  • '; - } - - echo ' -
  • - - -
  • -
  • - - -
  • -
- '; - - // We need a normal continue button here! - $upcontext['continue'] = 1; -} - -// Template for the database backup tool/ -function template_backup_database() -{ - global $upcontext, $support_js, $is_debug; - - echo ' -

', Lang::$txt['upgrade_wait'], '

'; - - echo ' - - - ', Lang::getTxt('upgrade_completedtables_outof', $upcontext), ' -
- -
'; - - // Don't any tables so far? - if (!empty($upcontext['previous_tables'])) { - foreach ((array) $upcontext['previous_tables'] as $table) { - echo ' -
', Lang::$txt['upgrade_completed_table'], ' "', $table, '".'; - } - } - - echo ' -

- ', Lang::$txt['upgrade_current_table'], ' "', $upcontext['cur_table_name'], '" -

-

', Lang::$txt['upgrade_backup_complete'], '

'; - - // Continue please! - $upcontext['continue'] = $support_js ? 2 : 1; - - // If javascript allows we want to do this using XML. - if ($support_js) { - echo ' - '; - } -} - -function template_backup_xml() -{ - global $upcontext; - - echo ' - ', $upcontext['cur_table_name'], '
'; -} - -// Here is the actual "make the changes" template! -function template_database_changes() -{ - global $upcontext, $support_js, $is_debug, $timeLimitThreshold; - - if (empty($is_debug) && !empty($upcontext['upgrade_status']['debug'])) { - $is_debug = true; - } - - echo ' -

', Lang::$txt['upgrade_db_changes'], '

-

', Lang::$txt['upgrade_db_patient'], '

'; - - echo ' - - '; - - // No javascript looks rubbish! - if (!$support_js) { - foreach ((array) $upcontext['actioned_items'] as $num => $item) { - if ($num != 0) { - echo ' Successful!'; - } - echo '
' . $item; - } - - // Only tell deubbers how much time they wasted waiting for the upgrade because they don't have javascript. - if (!empty($upcontext['changes_complete'])) { - if ($is_debug) { - $active = time() - $upcontext['started']; - $hours = floor($active / 3600); - $minutes = intval(($active / 60) % 60); - $seconds = intval($active % 60); - - echo '', Lang::getTxt('upgrade_success_time_db', ['s' => $seconds, 'm' => $minutes, 'h' => $hours]), '
'; - } else { - echo '', Lang::$txt['upgrade_success'], '
'; - } - - echo ' -

', Lang::$txt['upgrade_db_complete'], '

'; - } - } else { - // Tell them how many files we have in total. - if ($upcontext['file_count'] > 1) { - echo ' - ', Lang::$txt['upgrade_script'], ' ', $upcontext['cur_file_num'], ' of ', $upcontext['file_count'], '.'; - } - - echo ' -

- ', Lang::$txt['upgrade_executing'], ' "', $upcontext['current_item_name'], '" (', $upcontext['current_item_num'], ' ', Lang::$txt['upgrade_of'], ' ', $upcontext['total_items'], '', $upcontext['file_count'] > 1 ? ' - of this script' : '', ') -

-

', Lang::$txt['upgrade_db_complete2'], '

'; - - if ($is_debug) { - // Let our debuggers know how much time was spent, but not wasted since JS handled refreshing the page! - if ($upcontext['current_debug_item_num'] == $upcontext['debug_items']) { - $active = time() - $upcontext['started']; - $hours = floor($active / 3600); - $minutes = intval(($active / 60) % 60); - $seconds = intval($active % 60); - - echo ' -

', Lang::getTxt('upgrade_success_time_db', ['s' => $seconds, 'm' => $minutes, 'm' => $hours]), '

'; - } else { - echo ' -

'; - } - - echo ' -
- -
'; - } - } - - // Place for the XML error message. - echo ' -
-

', Lang::$txt['upgrade_error'], '

-
', $upcontext['error_message'] ?? Lang::$txt['upgrade_unknown_error'], '
-
'; - - // We want to continue at some point! - $upcontext['continue'] = $support_js ? 2 : 1; - - // If javascript allows we want to do this using XML. - if ($support_js) { - echo ' - '; - } - -} - -function template_database_xml() -{ - global $is_debug, $upcontext; - - echo ' - ', $upcontext['cur_file_name'], ' - ', $upcontext['current_item_name'], ' - ', $upcontext['current_debug_item_name'], ''; - - if (!empty($upcontext['error_message'])) { - echo ' - ', $upcontext['error_message'], ''; - } - - if (!empty($upcontext['error_string'])) { - echo ' - ', $upcontext['error_string'], ''; - } - - if ($is_debug) { - echo ' - ', time(), ''; - } -} - -// Template for the UTF-8 conversion step. Basically a copy of the backup stuff with slight modifications.... -function template_convert_utf8() -{ - global $upcontext, $support_js, $is_debug; - - echo ' -

', Lang::$txt['upgrade_wait2'], '

- - - ', Lang::$txt['upgrade_completed'], ' ', $upcontext['cur_table_num'], ' ', Lang::$txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', Lang::$txt['upgrade_tables'], ' -
- -
'; - - // Done any tables so far? - if (!empty($upcontext['previous_tables'])) { - foreach ((array) $upcontext['previous_tables'] as $table) { - echo ' -
', Lang::$txt['upgrade_completed_table'], ' "', $table, '".'; - } - } - - echo ' -

- ', Lang::$txt['upgrade_current_table'], ' "', $upcontext['cur_table_name'], '" -

'; - - // If we dropped their index, let's let them know - if ($upcontext['dropping_index']) { - echo ' -

', Lang::$txt['upgrade_conversion_proceed'], '

'; - - // Continue please! - $upcontext['continue'] = $support_js ? 2 : 1; - - // If javascript allows we want to do this using XML. - if ($support_js) { - echo ' - '; - } -} - -function template_convert_xml() -{ - global $upcontext; - - echo ' - ', $upcontext['cur_table_name'], '
'; -} - -// Template for the database backup tool/ -function template_serialize_json() -{ - global $upcontext, $support_js, $is_debug; - - echo ' -

', Lang::$txt['upgrade_convert_datajson'], '

- - - ', Lang::$txt['upgrade_completed'], ' ', $upcontext['cur_table_num'], ' ', Lang::$txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', Lang::$txt['upgrade_tables'], ' -
- -
'; - - // Don't any tables so far? - if (!empty($upcontext['previous_tables'])) { - foreach ((array) $upcontext['previous_tables'] as $table) { - echo ' -
', Lang::$txt['upgrade_completed_table'], ' "', $table, '".'; - } - } - - echo ' -

- ', Lang::$txt['upgrade_current_table'], ' "', $upcontext['cur_table_name'], '" -

-

', Lang::$txt['upgrade_json_completed'], '

'; - - // Try to make sure substep was reset. - if ($upcontext['cur_table_num'] == $upcontext['table_count']) { - echo ' - '; - } - - // Continue please! - $upcontext['continue'] = $support_js ? 2 : 1; - - // If javascript allows we want to do this using XML. - if ($support_js) { - echo ' - '; - } -} - -function template_serialize_json_xml() -{ - global $upcontext; - - echo ' - ', $upcontext['cur_table_name'], '
'; -} - -function template_cleanup() -{ - global $upcontext, $support_js, $is_debug; - - echo ' -

', Lang::$txt['upgrade_step_cleanup'], '

- - - ', Lang::$txt['upgrade_completed'], ' ', $upcontext['cur_substep_num'], ' ', Lang::$txt['upgrade_outof'], ' ', $upcontext['steps_count'], ' ', Lang::$txt['upgrade_steps'], ' -
- -
'; - - // Dont any tables so far? - if (!empty($upcontext['previous_substeps'])) { - foreach ((array) $upcontext['previous_substeps'] as $substep) { - echo ' -
', Lang::$txt['completed_cleanup_step'], ' "', $substep, '".'; - } - } - - echo ' -

- ', Lang::$txt['upgrade_current_step'], ' "', $upcontext['cur_substep_name'], '" -

-

', Lang::$txt['upgrade_cleanup_completed'], '

'; - - // Continue please! - $upcontext['continue'] = $support_js ? 2 : 1; - - // If javascript allows we want to do this using XML. - if ($support_js) { - echo ' - '; - } -} - -function template_cleanup_xml() -{ - global $upcontext; - - echo ' - ', $upcontext['cur_substep_name'], ''; -} - - -function template_upgrade_complete() -{ - global $upcontext, $upgradeurl, $settings, $is_debug; - - echo ' -

', Lang::getTxt('upgrade_done', ['boardurl' => Config::$boardurl]), '

- '; - - if (!empty($upcontext['can_delete_script'])) { - echo ' - - ', Lang::$txt['upgrade_delete_server'], ' - -
'; - } - - // Show Upgrade time in debug mode when we completed the upgrade process totally - if ($is_debug) { - $active = time() - $upcontext['started']; - $hours = floor($active / 3600); - $minutes = intval((int) ($active / 60) % 60); - $seconds = intval((int) $active % 60); - - if ($hours > 0) { - $upgrade_completed_time = 'upgrade_completed_time_hms'; - } elseif ($minutes > 0) { - $upgrade_completed_time = 'upgrade_completed_time_ms'; - } else { - $upgrade_completed_time = 'upgrade_completed_time_s'; - } - - echo Lang::getTxt($upgrade_completed_time, ['s' => $seconds, 'm' => $minutes, 'h' => $hours]); - } - - echo ' -

- ', Lang::getTxt('upgrade_problems', ['url' => 'https://www.simplemachines.org']), ' -
- ', Lang::$txt['upgrade_luck'], '
- Simple Machines -

'; -} - -/** - * Convert MySQL (var)char ip col to binary - * - * newCol needs to be a varbinary(16) null able field - * - * @param string $targetTable The table to perform the operation on - * @param string $oldCol The old column to gather data from - * @param string $newCol The new column to put data in - * @param int $limit The amount of entries to handle at once. - * @param int $setSize The amount of entries after which to update the database. - */ -function MySQLConvertOldIp($targetTable, $oldCol, $newCol, $limit = 50000, $setSize = 100): void -{ - global $step_progress; - - $current_substep = !isset($_GET['substep']) ? 0 : (int) $_GET['substep']; - - // Skip this if we don't have the column - $request = Db::$db->query( - '', - 'SHOW FIELDS - FROM {db_prefix}{raw:table} - WHERE Field = {string:name}', - [ - 'table' => $targetTable, - 'name' => $oldCol, - ], - ); - - if (Db::$db->num_rows($request) !== 1) { - Db::$db->free_result($request); - - return; - } - Db::$db->free_result($request); - - // Setup progress bar - if (!isset($_GET['total_fixes']) || !isset($_GET['a'])) { - $request = Db::$db->query( - '', - 'SELECT COUNT(DISTINCT {raw:old_col}) - FROM {db_prefix}{raw:table_name}', - [ - 'old_col' => $oldCol, - 'table_name' => $targetTable, - ], - ); - list($step_progress['total']) = Db::$db->fetch_row($request); - $_GET['total_fixes'] = $step_progress['total']; - Db::$db->free_result($request); - - $_GET['a'] = 0; - } - - $step_progress['name'] = 'Converting ips'; - $step_progress['current'] = $_GET['a']; - $step_progress['total'] = $_GET['total_fixes']; - - // Main process loop - $is_done = false; - - while (!$is_done) { - // Keep looping at the current step. - nextSubstep($current_substep); - - // mysql default max length is 1mb https://dev.mysql.com/doc/refman/5.1/en/packet-too-large.html - $arIp = []; - - $request = Db::$db->query( - '', - 'SELECT DISTINCT {raw:old_col} - FROM {db_prefix}{raw:table_name} - WHERE {raw:new_col} = {string:empty} - LIMIT {int:limit}', - [ - 'old_col' => $oldCol, - 'new_col' => $newCol, - 'table_name' => $targetTable, - 'empty' => '', - 'limit' => $limit, - ], - ); - - while ($row = Db::$db->fetch_assoc($request)) { - $arIp[] = $row[$oldCol]; - } - - Db::$db->free_result($request); - - if (empty($arIp)) { - $is_done = true; - } - - $updates = []; - $new_ips = []; - $cases = []; - $count = count($arIp); - - for ($i = 0; $i < $count; $i++) { - $new_ip = trim($arIp[$i]); - - $new_ip = filter_var($new_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6); - - if ($new_ip === false) { - $new_ip = ''; - } - - $updates['ip' . $i] = $arIp[$i]; - $new_ips['newip' . $i] = $new_ip; - $cases[$arIp[$i]] = 'WHEN ' . $oldCol . ' = {string:ip' . $i . '} THEN {inet:newip' . $i . '}'; - - // 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( - '', - 'UPDATE {db_prefix}' . $targetTable . ' - SET ' . $newCol . ' = CASE ' . - implode(' - ', $cases) . ' - ELSE NULL - END - WHERE ' . $oldCol . ' IN ({array_string:whereSet})', - array_merge($updates, $new_ips), - ); - - $updates = []; - $new_ips = []; - $cases = []; - } - } - - $_GET['a'] += $limit; - $step_progress['current'] = $_GET['a']; - } - - $step_progress = []; - unset($_GET['a'], $_GET['total_fixes']); -} - -/** - * Get the column info. This is basically the same as smf_db_list_columns but we get 1 column, force detail and other checks. - * - * @param string $targetTable The table to perform the operation on - * @param string $column The column we are looking for. - * - * @return array Info on the table. - */ -function upgradeGetColumnInfo($targetTable, $column): array -{ - $columns = Db::$db->list_columns($targetTable, true); - - if (isset($columns[$column])) { - return $columns[$column]; - } - - return []; -} +(new SMF\Maintenance\Maintenance())->execute(SMF\Maintenance\Maintenance::UPGRADE); From 56f0939807bc652bf5ee34f18ee98c7afa6c251f Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Mon, 19 May 2025 00:01:22 -0600 Subject: [PATCH 11/90] Updates composer.lock to point to new version of SMF BuildTools Signed-off-by: Jon Stovell --- composer.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.lock b/composer.lock index c1fbb1e4ef7..1d757cde6db 100644 --- a/composer.lock +++ b/composer.lock @@ -669,12 +669,12 @@ "source": { "type": "git", "url": "https://github.com/SimpleMachines/BuildTools.git", - "reference": "1a9a3373f953034b3d430613abccae55697751d8" + "reference": "4ecd9b09871a71e110fa3561e1bd8ab588f48461" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SimpleMachines/BuildTools/zipball/1a9a3373f953034b3d430613abccae55697751d8", - "reference": "1a9a3373f953034b3d430613abccae55697751d8", + "url": "https://api.github.com/repos/SimpleMachines/BuildTools/zipball/4ecd9b09871a71e110fa3561e1bd8ab588f48461", + "reference": "4ecd9b09871a71e110fa3561e1bd8ab588f48461", "shasum": "" }, "require": { From ab286988373ccf12f15a1f977308b343cf8c1046 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Mon, 19 May 2025 00:01:55 -0600 Subject: [PATCH 12/90] Tweaks .editorconfig to use spaces for indentation in composer.lock Signed-off-by: Jon Stovell --- .editorconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 181f2c2b005..9f68ce77ceb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 \ No newline at end of file +indent_size = 2 From fdc21c1b92622979158594042bfa2fa57207d26b Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Mon, 19 May 2025 00:05:50 -0600 Subject: [PATCH 13/90] Fixes content of Sources/Maintenance/*/index.php files Signed-off-by: Jon Stovell --- Sources/Maintenance/Cleanup/index.php | 2 +- Sources/Maintenance/Cleanup/v2_1/index.php | 2 +- Sources/Maintenance/Cleanup/v3_0/index.php | 2 +- Sources/Maintenance/Migration/index.php | 2 +- Sources/Maintenance/Migration/v2_1/index.php | 2 +- Sources/Maintenance/Migration/v3_0/index.php | 2 +- Sources/Maintenance/Tools/index.php | 2 +- Sources/Maintenance/index.php | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Maintenance/Cleanup/index.php b/Sources/Maintenance/Cleanup/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/Cleanup/index.php +++ b/Sources/Maintenance/Cleanup/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } diff --git a/Sources/Maintenance/Cleanup/v2_1/index.php b/Sources/Maintenance/Cleanup/v2_1/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/Cleanup/v2_1/index.php +++ b/Sources/Maintenance/Cleanup/v2_1/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } diff --git a/Sources/Maintenance/Cleanup/v3_0/index.php b/Sources/Maintenance/Cleanup/v3_0/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/Cleanup/v3_0/index.php +++ b/Sources/Maintenance/Cleanup/v3_0/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } diff --git a/Sources/Maintenance/Migration/index.php b/Sources/Maintenance/Migration/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/Migration/index.php +++ b/Sources/Maintenance/Migration/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } diff --git a/Sources/Maintenance/Migration/v2_1/index.php b/Sources/Maintenance/Migration/v2_1/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/Migration/v2_1/index.php +++ b/Sources/Maintenance/Migration/v2_1/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } diff --git a/Sources/Maintenance/Migration/v3_0/index.php b/Sources/Maintenance/Migration/v3_0/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/Migration/v3_0/index.php +++ b/Sources/Maintenance/Migration/v3_0/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } diff --git a/Sources/Maintenance/Tools/index.php b/Sources/Maintenance/Tools/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/Tools/index.php +++ b/Sources/Maintenance/Tools/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } diff --git a/Sources/Maintenance/index.php b/Sources/Maintenance/index.php index ee0549de33f..cc9dd085708 100644 --- a/Sources/Maintenance/index.php +++ b/Sources/Maintenance/index.php @@ -4,5 +4,5 @@ if (file_exists(dirname(__DIR__) . '/index.php')) { include dirname(__DIR__) . '/index.php'; } else { -exit; + exit; } From 3c4049b650e99b76c97d67666155c4e10e957c24 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Mon, 19 May 2025 11:25:33 -0600 Subject: [PATCH 14/90] Allows explicit handling of row format in MySQL::create_table() Signed-off-by: Jon Stovell --- Sources/Db/APIs/MySQL.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index 767cf9724a7..cc1cbb1b7f7 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -1826,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( '', From 555a7e5cb6a44723970d1283b8757ee249117c1a Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Mon, 19 May 2025 11:26:06 -0600 Subject: [PATCH 15/90] Includes row format and collation in MySQL::table_sql() output Signed-off-by: Jon Stovell --- Sources/Db/APIs/MySQL.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index cc1cbb1b7f7..38f83773d40 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -1247,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; } From d48f71d65b87683ad453c43945f8ff761fdb0e77 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 20 May 2025 11:57:27 -0600 Subject: [PATCH 16/90] Updates version numbers Signed-off-by: Jon Stovell --- .github/phpcs/SectionComments.php | 2 +- .php-cs-fixer.dist.php | 2 +- Sources/Db/Schema/Column.php | 2 +- Sources/Db/Schema/DbIndex.php | 2 +- Sources/Db/Schema/Table.php | 2 +- Sources/Db/Schema/v2_1/AdminInfoFiles.php | 2 +- Sources/Db/Schema/v2_1/ApprovalQueue.php | 2 +- Sources/Db/Schema/v2_1/Attachments.php | 2 +- Sources/Db/Schema/v2_1/BackgroundTasks.php | 2 +- Sources/Db/Schema/v2_1/BanGroups.php | 2 +- Sources/Db/Schema/v2_1/BanItems.php | 2 +- Sources/Db/Schema/v2_1/BoardPermissions.php | 2 +- Sources/Db/Schema/v2_1/BoardPermissionsView.php | 2 +- Sources/Db/Schema/v2_1/Boards.php | 2 +- Sources/Db/Schema/v2_1/Calendar.php | 2 +- Sources/Db/Schema/v2_1/CalendarHolidays.php | 2 +- Sources/Db/Schema/v2_1/Categories.php | 2 +- Sources/Db/Schema/v2_1/CustomFields.php | 2 +- Sources/Db/Schema/v2_1/GroupModerators.php | 2 +- Sources/Db/Schema/v2_1/LogActions.php | 2 +- Sources/Db/Schema/v2_1/LogActivity.php | 2 +- Sources/Db/Schema/v2_1/LogBanned.php | 2 +- Sources/Db/Schema/v2_1/LogBoards.php | 2 +- Sources/Db/Schema/v2_1/LogComments.php | 2 +- Sources/Db/Schema/v2_1/LogDigest.php | 2 +- Sources/Db/Schema/v2_1/LogErrors.php | 2 +- Sources/Db/Schema/v2_1/LogFloodcontrol.php | 2 +- Sources/Db/Schema/v2_1/LogGroupRequests.php | 2 +- Sources/Db/Schema/v2_1/LogMarkRead.php | 2 +- Sources/Db/Schema/v2_1/LogMemberNotices.php | 2 +- Sources/Db/Schema/v2_1/LogNotify.php | 2 +- Sources/Db/Schema/v2_1/LogOnline.php | 2 +- Sources/Db/Schema/v2_1/LogPackages.php | 2 +- Sources/Db/Schema/v2_1/LogPolls.php | 2 +- Sources/Db/Schema/v2_1/LogReported.php | 2 +- Sources/Db/Schema/v2_1/LogReportedComments.php | 2 +- Sources/Db/Schema/v2_1/LogScheduledTasks.php | 2 +- Sources/Db/Schema/v2_1/LogSearchMessages.php | 2 +- Sources/Db/Schema/v2_1/LogSearchResults.php | 2 +- Sources/Db/Schema/v2_1/LogSearchSubjects.php | 2 +- Sources/Db/Schema/v2_1/LogSearchTopics.php | 2 +- Sources/Db/Schema/v2_1/LogSpiderHits.php | 2 +- Sources/Db/Schema/v2_1/LogSpiderStats.php | 2 +- Sources/Db/Schema/v2_1/LogSubscribed.php | 2 +- Sources/Db/Schema/v2_1/LogTopics.php | 2 +- Sources/Db/Schema/v2_1/MailQueue.php | 2 +- Sources/Db/Schema/v2_1/MemberLogins.php | 2 +- Sources/Db/Schema/v2_1/Membergroups.php | 2 +- Sources/Db/Schema/v2_1/Members.php | 2 +- Sources/Db/Schema/v2_1/Mentions.php | 2 +- Sources/Db/Schema/v2_1/MessageIcons.php | 2 +- Sources/Db/Schema/v2_1/Messages.php | 2 +- Sources/Db/Schema/v2_1/ModeratorGroups.php | 2 +- Sources/Db/Schema/v2_1/Moderators.php | 2 +- Sources/Db/Schema/v2_1/PackageServers.php | 2 +- Sources/Db/Schema/v2_1/PermissionProfiles.php | 2 +- Sources/Db/Schema/v2_1/Permissions.php | 2 +- Sources/Db/Schema/v2_1/PersonalMessages.php | 2 +- Sources/Db/Schema/v2_1/PmLabeledMessages.php | 2 +- Sources/Db/Schema/v2_1/PmLabels.php | 2 +- Sources/Db/Schema/v2_1/PmRecipients.php | 2 +- Sources/Db/Schema/v2_1/PmRules.php | 2 +- Sources/Db/Schema/v2_1/PollChoices.php | 2 +- Sources/Db/Schema/v2_1/Polls.php | 2 +- Sources/Db/Schema/v2_1/Qanda.php | 2 +- Sources/Db/Schema/v2_1/ScheduledTasks.php | 2 +- Sources/Db/Schema/v2_1/Sessions.php | 2 +- Sources/Db/Schema/v2_1/SmileyFiles.php | 2 +- Sources/Db/Schema/v2_1/Smileys.php | 2 +- Sources/Db/Schema/v2_1/Spiders.php | 2 +- Sources/Db/Schema/v2_1/Subscriptions.php | 2 +- Sources/Db/Schema/v2_1/Themes.php | 2 +- Sources/Db/Schema/v2_1/Topics.php | 2 +- Sources/Db/Schema/v2_1/UserAlerts.php | 2 +- Sources/Db/Schema/v2_1/UserAlertsPrefs.php | 2 +- Sources/Db/Schema/v2_1/UserDrafts.php | 2 +- Sources/Db/Schema/v2_1/UserLikes.php | 2 +- Sources/Db/Schema/v3_0/AdminInfoFiles.php | 2 +- Sources/Db/Schema/v3_0/ApprovalQueue.php | 2 +- Sources/Db/Schema/v3_0/Attachments.php | 2 +- Sources/Db/Schema/v3_0/BackgroundTasks.php | 2 +- Sources/Db/Schema/v3_0/BanGroups.php | 2 +- Sources/Db/Schema/v3_0/BanItems.php | 2 +- Sources/Db/Schema/v3_0/BoardPermissions.php | 2 +- Sources/Db/Schema/v3_0/BoardPermissionsView.php | 2 +- Sources/Db/Schema/v3_0/Boards.php | 2 +- Sources/Db/Schema/v3_0/Calendar.php | 2 +- Sources/Db/Schema/v3_0/Categories.php | 2 +- Sources/Db/Schema/v3_0/CustomFields.php | 2 +- Sources/Db/Schema/v3_0/GroupModerators.php | 2 +- Sources/Db/Schema/v3_0/LogActions.php | 2 +- Sources/Db/Schema/v3_0/LogActivity.php | 2 +- Sources/Db/Schema/v3_0/LogBanned.php | 2 +- Sources/Db/Schema/v3_0/LogBoards.php | 2 +- Sources/Db/Schema/v3_0/LogComments.php | 2 +- Sources/Db/Schema/v3_0/LogDigest.php | 2 +- Sources/Db/Schema/v3_0/LogErrors.php | 2 +- Sources/Db/Schema/v3_0/LogFloodcontrol.php | 2 +- Sources/Db/Schema/v3_0/LogGroupRequests.php | 2 +- Sources/Db/Schema/v3_0/LogMarkRead.php | 2 +- Sources/Db/Schema/v3_0/LogMemberNotices.php | 2 +- Sources/Db/Schema/v3_0/LogNotify.php | 2 +- Sources/Db/Schema/v3_0/LogOnline.php | 2 +- Sources/Db/Schema/v3_0/LogPackages.php | 2 +- Sources/Db/Schema/v3_0/LogPolls.php | 2 +- Sources/Db/Schema/v3_0/LogReported.php | 2 +- Sources/Db/Schema/v3_0/LogReportedComments.php | 2 +- Sources/Db/Schema/v3_0/LogScheduledTasks.php | 2 +- Sources/Db/Schema/v3_0/LogSearchMessages.php | 2 +- Sources/Db/Schema/v3_0/LogSearchResults.php | 2 +- Sources/Db/Schema/v3_0/LogSearchSubjects.php | 2 +- Sources/Db/Schema/v3_0/LogSearchTopics.php | 2 +- Sources/Db/Schema/v3_0/LogSpiderHits.php | 2 +- Sources/Db/Schema/v3_0/LogSpiderStats.php | 2 +- Sources/Db/Schema/v3_0/LogSubscribed.php | 2 +- Sources/Db/Schema/v3_0/LogTopics.php | 2 +- Sources/Db/Schema/v3_0/MailQueue.php | 2 +- Sources/Db/Schema/v3_0/MemberLogins.php | 2 +- Sources/Db/Schema/v3_0/Membergroups.php | 2 +- Sources/Db/Schema/v3_0/Members.php | 2 +- Sources/Db/Schema/v3_0/Mentions.php | 2 +- Sources/Db/Schema/v3_0/MessageIcons.php | 2 +- Sources/Db/Schema/v3_0/Messages.php | 2 +- Sources/Db/Schema/v3_0/ModeratorGroups.php | 2 +- Sources/Db/Schema/v3_0/Moderators.php | 2 +- Sources/Db/Schema/v3_0/PackageServers.php | 2 +- Sources/Db/Schema/v3_0/PermissionProfiles.php | 2 +- Sources/Db/Schema/v3_0/Permissions.php | 2 +- Sources/Db/Schema/v3_0/PersonalMessages.php | 2 +- Sources/Db/Schema/v3_0/PmLabeledMessages.php | 2 +- Sources/Db/Schema/v3_0/PmLabels.php | 2 +- Sources/Db/Schema/v3_0/PmRecipients.php | 2 +- Sources/Db/Schema/v3_0/PmRules.php | 2 +- Sources/Db/Schema/v3_0/PollChoices.php | 2 +- Sources/Db/Schema/v3_0/Polls.php | 2 +- Sources/Db/Schema/v3_0/Qanda.php | 2 +- Sources/Db/Schema/v3_0/ScheduledTasks.php | 2 +- Sources/Db/Schema/v3_0/Sessions.php | 2 +- Sources/Db/Schema/v3_0/Settings.php | 2 +- Sources/Db/Schema/v3_0/SmileyFiles.php | 2 +- Sources/Db/Schema/v3_0/Smileys.php | 2 +- Sources/Db/Schema/v3_0/Spiders.php | 2 +- Sources/Db/Schema/v3_0/Subscriptions.php | 2 +- Sources/Db/Schema/v3_0/Themes.php | 2 +- Sources/Db/Schema/v3_0/Topics.php | 2 +- Sources/Db/Schema/v3_0/UserAlerts.php | 2 +- Sources/Db/Schema/v3_0/UserAlertsPrefs.php | 2 +- Sources/Db/Schema/v3_0/UserDrafts.php | 2 +- Sources/Db/Schema/v3_0/UserLikes.php | 2 +- Sources/Debug/DebugUtils.php | 2 +- Sources/Maintenance/Migration/v3_0/PackageVersion.php | 2 +- Themes/default/scripts/jquery.sceditor.smf.js | 2 +- Themes/default/scripts/smf_jquery_plugins.js | 2 +- 153 files changed, 153 insertions(+), 153 deletions(-) diff --git a/.github/phpcs/SectionComments.php b/.github/phpcs/SectionComments.php index f199023e413..1a40751f4f9 100644 --- a/.github/phpcs/SectionComments.php +++ b/.github/phpcs/SectionComments.php @@ -8,7 +8,7 @@ * @copyright 2025 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 71b7d60c15d..9ed4ffb8816 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -8,7 +8,7 @@ * @copyright 2025 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ $finder = (new PhpCsFixer\Finder()) ->in(__DIR__) diff --git a/Sources/Db/Schema/Column.php b/Sources/Db/Schema/Column.php index 6652acb148c..0fd5b583018 100644 --- a/Sources/Db/Schema/Column.php +++ b/Sources/Db/Schema/Column.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/DbIndex.php b/Sources/Db/Schema/DbIndex.php index 16b133ff40f..527f58445f8 100644 --- a/Sources/Db/Schema/DbIndex.php +++ b/Sources/Db/Schema/DbIndex.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index 6426f80a3f2..931ae3b7341 100644 --- a/Sources/Db/Schema/Table.php +++ b/Sources/Db/Schema/Table.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/AdminInfoFiles.php b/Sources/Db/Schema/v2_1/AdminInfoFiles.php index 439c8edf690..077065e9a14 100644 --- a/Sources/Db/Schema/v2_1/AdminInfoFiles.php +++ b/Sources/Db/Schema/v2_1/AdminInfoFiles.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/ApprovalQueue.php b/Sources/Db/Schema/v2_1/ApprovalQueue.php index 5c79741294e..e9a707cb042 100644 --- a/Sources/Db/Schema/v2_1/ApprovalQueue.php +++ b/Sources/Db/Schema/v2_1/ApprovalQueue.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Attachments.php b/Sources/Db/Schema/v2_1/Attachments.php index e060e36a72e..9e82b01e13b 100644 --- a/Sources/Db/Schema/v2_1/Attachments.php +++ b/Sources/Db/Schema/v2_1/Attachments.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/BackgroundTasks.php b/Sources/Db/Schema/v2_1/BackgroundTasks.php index 42e732eb17c..88b378b691c 100644 --- a/Sources/Db/Schema/v2_1/BackgroundTasks.php +++ b/Sources/Db/Schema/v2_1/BackgroundTasks.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/BanGroups.php b/Sources/Db/Schema/v2_1/BanGroups.php index 512897e6f5c..6efdad94005 100644 --- a/Sources/Db/Schema/v2_1/BanGroups.php +++ b/Sources/Db/Schema/v2_1/BanGroups.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/BanItems.php b/Sources/Db/Schema/v2_1/BanItems.php index d6450924327..df2e379fce9 100644 --- a/Sources/Db/Schema/v2_1/BanItems.php +++ b/Sources/Db/Schema/v2_1/BanItems.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/BoardPermissions.php b/Sources/Db/Schema/v2_1/BoardPermissions.php index 1d933157165..8debc34dcbe 100644 --- a/Sources/Db/Schema/v2_1/BoardPermissions.php +++ b/Sources/Db/Schema/v2_1/BoardPermissions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/BoardPermissionsView.php b/Sources/Db/Schema/v2_1/BoardPermissionsView.php index eeea940d3b8..ed537c947e0 100644 --- a/Sources/Db/Schema/v2_1/BoardPermissionsView.php +++ b/Sources/Db/Schema/v2_1/BoardPermissionsView.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Boards.php b/Sources/Db/Schema/v2_1/Boards.php index 4e64c3fed10..1a208541f91 100644 --- a/Sources/Db/Schema/v2_1/Boards.php +++ b/Sources/Db/Schema/v2_1/Boards.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Calendar.php b/Sources/Db/Schema/v2_1/Calendar.php index 7fbf37c8bfb..2cf9e1a2a37 100644 --- a/Sources/Db/Schema/v2_1/Calendar.php +++ b/Sources/Db/Schema/v2_1/Calendar.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/CalendarHolidays.php b/Sources/Db/Schema/v2_1/CalendarHolidays.php index 8b26a1658ab..0a6e9d501fc 100644 --- a/Sources/Db/Schema/v2_1/CalendarHolidays.php +++ b/Sources/Db/Schema/v2_1/CalendarHolidays.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Categories.php b/Sources/Db/Schema/v2_1/Categories.php index 80b40077cd9..8bf396d58af 100644 --- a/Sources/Db/Schema/v2_1/Categories.php +++ b/Sources/Db/Schema/v2_1/Categories.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/CustomFields.php b/Sources/Db/Schema/v2_1/CustomFields.php index ce9408547ac..be1b91007cc 100644 --- a/Sources/Db/Schema/v2_1/CustomFields.php +++ b/Sources/Db/Schema/v2_1/CustomFields.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/GroupModerators.php b/Sources/Db/Schema/v2_1/GroupModerators.php index 2b15bc0bbac..e1062d575f8 100644 --- a/Sources/Db/Schema/v2_1/GroupModerators.php +++ b/Sources/Db/Schema/v2_1/GroupModerators.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogActions.php b/Sources/Db/Schema/v2_1/LogActions.php index f6b1c898057..9489c8f2024 100644 --- a/Sources/Db/Schema/v2_1/LogActions.php +++ b/Sources/Db/Schema/v2_1/LogActions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogActivity.php b/Sources/Db/Schema/v2_1/LogActivity.php index 8edbca5c3e2..ecbbfd8ff91 100644 --- a/Sources/Db/Schema/v2_1/LogActivity.php +++ b/Sources/Db/Schema/v2_1/LogActivity.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogBanned.php b/Sources/Db/Schema/v2_1/LogBanned.php index 1cf339991fb..c1aa24c9109 100644 --- a/Sources/Db/Schema/v2_1/LogBanned.php +++ b/Sources/Db/Schema/v2_1/LogBanned.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogBoards.php b/Sources/Db/Schema/v2_1/LogBoards.php index 1dd0e48e403..1e91c60a49a 100644 --- a/Sources/Db/Schema/v2_1/LogBoards.php +++ b/Sources/Db/Schema/v2_1/LogBoards.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogComments.php b/Sources/Db/Schema/v2_1/LogComments.php index 78bb3629703..9b71b8a561c 100644 --- a/Sources/Db/Schema/v2_1/LogComments.php +++ b/Sources/Db/Schema/v2_1/LogComments.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogDigest.php b/Sources/Db/Schema/v2_1/LogDigest.php index f437c1a636d..fed9fa90a27 100644 --- a/Sources/Db/Schema/v2_1/LogDigest.php +++ b/Sources/Db/Schema/v2_1/LogDigest.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogErrors.php b/Sources/Db/Schema/v2_1/LogErrors.php index b5451d4d547..d627d59ce66 100644 --- a/Sources/Db/Schema/v2_1/LogErrors.php +++ b/Sources/Db/Schema/v2_1/LogErrors.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogFloodcontrol.php b/Sources/Db/Schema/v2_1/LogFloodcontrol.php index 046b0bc061d..e98d1685c0b 100644 --- a/Sources/Db/Schema/v2_1/LogFloodcontrol.php +++ b/Sources/Db/Schema/v2_1/LogFloodcontrol.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogGroupRequests.php b/Sources/Db/Schema/v2_1/LogGroupRequests.php index b9a986eb3b2..0d0bab37d0c 100644 --- a/Sources/Db/Schema/v2_1/LogGroupRequests.php +++ b/Sources/Db/Schema/v2_1/LogGroupRequests.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogMarkRead.php b/Sources/Db/Schema/v2_1/LogMarkRead.php index ca60766a537..64f8dcd71ed 100644 --- a/Sources/Db/Schema/v2_1/LogMarkRead.php +++ b/Sources/Db/Schema/v2_1/LogMarkRead.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogMemberNotices.php b/Sources/Db/Schema/v2_1/LogMemberNotices.php index 7c4a7cd9d41..95663d3c3b0 100644 --- a/Sources/Db/Schema/v2_1/LogMemberNotices.php +++ b/Sources/Db/Schema/v2_1/LogMemberNotices.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogNotify.php b/Sources/Db/Schema/v2_1/LogNotify.php index 38e8ef08ec2..dd88c7302d6 100644 --- a/Sources/Db/Schema/v2_1/LogNotify.php +++ b/Sources/Db/Schema/v2_1/LogNotify.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogOnline.php b/Sources/Db/Schema/v2_1/LogOnline.php index e05d228087b..392b92c6dc9 100644 --- a/Sources/Db/Schema/v2_1/LogOnline.php +++ b/Sources/Db/Schema/v2_1/LogOnline.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogPackages.php b/Sources/Db/Schema/v2_1/LogPackages.php index 780b4410069..6fae24ecc78 100644 --- a/Sources/Db/Schema/v2_1/LogPackages.php +++ b/Sources/Db/Schema/v2_1/LogPackages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogPolls.php b/Sources/Db/Schema/v2_1/LogPolls.php index d0bc70cba2a..3e10a7b7d13 100644 --- a/Sources/Db/Schema/v2_1/LogPolls.php +++ b/Sources/Db/Schema/v2_1/LogPolls.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogReported.php b/Sources/Db/Schema/v2_1/LogReported.php index 813db0e197a..46b60ff5610 100644 --- a/Sources/Db/Schema/v2_1/LogReported.php +++ b/Sources/Db/Schema/v2_1/LogReported.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogReportedComments.php b/Sources/Db/Schema/v2_1/LogReportedComments.php index dfa7a7045d3..5c398234e37 100644 --- a/Sources/Db/Schema/v2_1/LogReportedComments.php +++ b/Sources/Db/Schema/v2_1/LogReportedComments.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogScheduledTasks.php b/Sources/Db/Schema/v2_1/LogScheduledTasks.php index 6e53995a3fb..41881ac23e4 100644 --- a/Sources/Db/Schema/v2_1/LogScheduledTasks.php +++ b/Sources/Db/Schema/v2_1/LogScheduledTasks.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogSearchMessages.php b/Sources/Db/Schema/v2_1/LogSearchMessages.php index 8ad2df893a9..46d700580f0 100644 --- a/Sources/Db/Schema/v2_1/LogSearchMessages.php +++ b/Sources/Db/Schema/v2_1/LogSearchMessages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogSearchResults.php b/Sources/Db/Schema/v2_1/LogSearchResults.php index c05e9c2fa22..3b566e3a44b 100644 --- a/Sources/Db/Schema/v2_1/LogSearchResults.php +++ b/Sources/Db/Schema/v2_1/LogSearchResults.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogSearchSubjects.php b/Sources/Db/Schema/v2_1/LogSearchSubjects.php index 101bc519556..10950311b7a 100644 --- a/Sources/Db/Schema/v2_1/LogSearchSubjects.php +++ b/Sources/Db/Schema/v2_1/LogSearchSubjects.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogSearchTopics.php b/Sources/Db/Schema/v2_1/LogSearchTopics.php index f065f44380f..b4dc1c008e7 100644 --- a/Sources/Db/Schema/v2_1/LogSearchTopics.php +++ b/Sources/Db/Schema/v2_1/LogSearchTopics.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogSpiderHits.php b/Sources/Db/Schema/v2_1/LogSpiderHits.php index a6a2a9e724a..0a72b9104d8 100644 --- a/Sources/Db/Schema/v2_1/LogSpiderHits.php +++ b/Sources/Db/Schema/v2_1/LogSpiderHits.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogSpiderStats.php b/Sources/Db/Schema/v2_1/LogSpiderStats.php index 8de1101b218..b0537c9f032 100644 --- a/Sources/Db/Schema/v2_1/LogSpiderStats.php +++ b/Sources/Db/Schema/v2_1/LogSpiderStats.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogSubscribed.php b/Sources/Db/Schema/v2_1/LogSubscribed.php index a82014d6b8f..3eeaedfec35 100644 --- a/Sources/Db/Schema/v2_1/LogSubscribed.php +++ b/Sources/Db/Schema/v2_1/LogSubscribed.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/LogTopics.php b/Sources/Db/Schema/v2_1/LogTopics.php index 2feae003c88..77a099e0dea 100644 --- a/Sources/Db/Schema/v2_1/LogTopics.php +++ b/Sources/Db/Schema/v2_1/LogTopics.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/MailQueue.php b/Sources/Db/Schema/v2_1/MailQueue.php index 9901afc5e04..1960862a94e 100644 --- a/Sources/Db/Schema/v2_1/MailQueue.php +++ b/Sources/Db/Schema/v2_1/MailQueue.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/MemberLogins.php b/Sources/Db/Schema/v2_1/MemberLogins.php index 44ec76d2774..41aa9cfb47c 100644 --- a/Sources/Db/Schema/v2_1/MemberLogins.php +++ b/Sources/Db/Schema/v2_1/MemberLogins.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Membergroups.php b/Sources/Db/Schema/v2_1/Membergroups.php index e4986f6a152..21134fe67c1 100644 --- a/Sources/Db/Schema/v2_1/Membergroups.php +++ b/Sources/Db/Schema/v2_1/Membergroups.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Members.php b/Sources/Db/Schema/v2_1/Members.php index 7fe48a16743..31934863600 100644 --- a/Sources/Db/Schema/v2_1/Members.php +++ b/Sources/Db/Schema/v2_1/Members.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Mentions.php b/Sources/Db/Schema/v2_1/Mentions.php index 75fc63c8399..17028a49390 100644 --- a/Sources/Db/Schema/v2_1/Mentions.php +++ b/Sources/Db/Schema/v2_1/Mentions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/MessageIcons.php b/Sources/Db/Schema/v2_1/MessageIcons.php index 6bf80dd1f33..c9c48ba2eac 100644 --- a/Sources/Db/Schema/v2_1/MessageIcons.php +++ b/Sources/Db/Schema/v2_1/MessageIcons.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Messages.php b/Sources/Db/Schema/v2_1/Messages.php index d0af51b2dee..61f9db59a42 100644 --- a/Sources/Db/Schema/v2_1/Messages.php +++ b/Sources/Db/Schema/v2_1/Messages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/ModeratorGroups.php b/Sources/Db/Schema/v2_1/ModeratorGroups.php index 1e5d52a75c2..2e417fb4b30 100644 --- a/Sources/Db/Schema/v2_1/ModeratorGroups.php +++ b/Sources/Db/Schema/v2_1/ModeratorGroups.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Moderators.php b/Sources/Db/Schema/v2_1/Moderators.php index 3105b22a11c..7c0057f88b8 100644 --- a/Sources/Db/Schema/v2_1/Moderators.php +++ b/Sources/Db/Schema/v2_1/Moderators.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PackageServers.php b/Sources/Db/Schema/v2_1/PackageServers.php index b9765d88b49..e19ac1cc4fa 100644 --- a/Sources/Db/Schema/v2_1/PackageServers.php +++ b/Sources/Db/Schema/v2_1/PackageServers.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PermissionProfiles.php b/Sources/Db/Schema/v2_1/PermissionProfiles.php index a2f48c9d0c0..8a6e9c144c2 100644 --- a/Sources/Db/Schema/v2_1/PermissionProfiles.php +++ b/Sources/Db/Schema/v2_1/PermissionProfiles.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Permissions.php b/Sources/Db/Schema/v2_1/Permissions.php index b3ded0f5b31..00a2e7d0e9e 100644 --- a/Sources/Db/Schema/v2_1/Permissions.php +++ b/Sources/Db/Schema/v2_1/Permissions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PersonalMessages.php b/Sources/Db/Schema/v2_1/PersonalMessages.php index fe5065b3142..594253d02d1 100644 --- a/Sources/Db/Schema/v2_1/PersonalMessages.php +++ b/Sources/Db/Schema/v2_1/PersonalMessages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PmLabeledMessages.php b/Sources/Db/Schema/v2_1/PmLabeledMessages.php index 0487e905ab0..c774df0bc0d 100644 --- a/Sources/Db/Schema/v2_1/PmLabeledMessages.php +++ b/Sources/Db/Schema/v2_1/PmLabeledMessages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PmLabels.php b/Sources/Db/Schema/v2_1/PmLabels.php index 82e3ff145e5..982d9792146 100644 --- a/Sources/Db/Schema/v2_1/PmLabels.php +++ b/Sources/Db/Schema/v2_1/PmLabels.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PmRecipients.php b/Sources/Db/Schema/v2_1/PmRecipients.php index d37ca05eaa3..ff0c34b1e01 100644 --- a/Sources/Db/Schema/v2_1/PmRecipients.php +++ b/Sources/Db/Schema/v2_1/PmRecipients.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PmRules.php b/Sources/Db/Schema/v2_1/PmRules.php index 5465d75d695..4c6ca1b0c24 100644 --- a/Sources/Db/Schema/v2_1/PmRules.php +++ b/Sources/Db/Schema/v2_1/PmRules.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/PollChoices.php b/Sources/Db/Schema/v2_1/PollChoices.php index 1aca5089ae7..ebeaa4fcff0 100644 --- a/Sources/Db/Schema/v2_1/PollChoices.php +++ b/Sources/Db/Schema/v2_1/PollChoices.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Polls.php b/Sources/Db/Schema/v2_1/Polls.php index e5ac4cfbfb5..62a71cc2e19 100644 --- a/Sources/Db/Schema/v2_1/Polls.php +++ b/Sources/Db/Schema/v2_1/Polls.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Qanda.php b/Sources/Db/Schema/v2_1/Qanda.php index 98fbe36cdf7..ce833830fa6 100644 --- a/Sources/Db/Schema/v2_1/Qanda.php +++ b/Sources/Db/Schema/v2_1/Qanda.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/ScheduledTasks.php b/Sources/Db/Schema/v2_1/ScheduledTasks.php index c992783044e..85a588762a0 100644 --- a/Sources/Db/Schema/v2_1/ScheduledTasks.php +++ b/Sources/Db/Schema/v2_1/ScheduledTasks.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Sessions.php b/Sources/Db/Schema/v2_1/Sessions.php index aacb16ba20f..d9305282d8c 100644 --- a/Sources/Db/Schema/v2_1/Sessions.php +++ b/Sources/Db/Schema/v2_1/Sessions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/SmileyFiles.php b/Sources/Db/Schema/v2_1/SmileyFiles.php index a700b29014d..4e47446b858 100644 --- a/Sources/Db/Schema/v2_1/SmileyFiles.php +++ b/Sources/Db/Schema/v2_1/SmileyFiles.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Smileys.php b/Sources/Db/Schema/v2_1/Smileys.php index 173ffd40096..a94ce2d4494 100644 --- a/Sources/Db/Schema/v2_1/Smileys.php +++ b/Sources/Db/Schema/v2_1/Smileys.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Spiders.php b/Sources/Db/Schema/v2_1/Spiders.php index 8f466c397ed..57c44904563 100644 --- a/Sources/Db/Schema/v2_1/Spiders.php +++ b/Sources/Db/Schema/v2_1/Spiders.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Subscriptions.php b/Sources/Db/Schema/v2_1/Subscriptions.php index c0527fc5aa3..e6eebace7b7 100644 --- a/Sources/Db/Schema/v2_1/Subscriptions.php +++ b/Sources/Db/Schema/v2_1/Subscriptions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Themes.php b/Sources/Db/Schema/v2_1/Themes.php index a9410f4b3f6..1e4531b8a08 100644 --- a/Sources/Db/Schema/v2_1/Themes.php +++ b/Sources/Db/Schema/v2_1/Themes.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/Topics.php b/Sources/Db/Schema/v2_1/Topics.php index e63ade2240e..1c39648acc7 100644 --- a/Sources/Db/Schema/v2_1/Topics.php +++ b/Sources/Db/Schema/v2_1/Topics.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/UserAlerts.php b/Sources/Db/Schema/v2_1/UserAlerts.php index 2e863b44052..169e96a38be 100644 --- a/Sources/Db/Schema/v2_1/UserAlerts.php +++ b/Sources/Db/Schema/v2_1/UserAlerts.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/UserAlertsPrefs.php b/Sources/Db/Schema/v2_1/UserAlertsPrefs.php index ed2ee31a595..f90147aaca0 100644 --- a/Sources/Db/Schema/v2_1/UserAlertsPrefs.php +++ b/Sources/Db/Schema/v2_1/UserAlertsPrefs.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/UserDrafts.php b/Sources/Db/Schema/v2_1/UserDrafts.php index 9054fbf9df0..b0f6d826028 100644 --- a/Sources/Db/Schema/v2_1/UserDrafts.php +++ b/Sources/Db/Schema/v2_1/UserDrafts.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v2_1/UserLikes.php b/Sources/Db/Schema/v2_1/UserLikes.php index 40a2aacc428..426f1d21312 100644 --- a/Sources/Db/Schema/v2_1/UserLikes.php +++ b/Sources/Db/Schema/v2_1/UserLikes.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/AdminInfoFiles.php b/Sources/Db/Schema/v3_0/AdminInfoFiles.php index 4bbba454a95..fa53723c55e 100644 --- a/Sources/Db/Schema/v3_0/AdminInfoFiles.php +++ b/Sources/Db/Schema/v3_0/AdminInfoFiles.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/ApprovalQueue.php b/Sources/Db/Schema/v3_0/ApprovalQueue.php index a9ac5dea733..e46acc2dea6 100644 --- a/Sources/Db/Schema/v3_0/ApprovalQueue.php +++ b/Sources/Db/Schema/v3_0/ApprovalQueue.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Attachments.php b/Sources/Db/Schema/v3_0/Attachments.php index 4d9681a96e8..d39a553eddb 100644 --- a/Sources/Db/Schema/v3_0/Attachments.php +++ b/Sources/Db/Schema/v3_0/Attachments.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/BackgroundTasks.php b/Sources/Db/Schema/v3_0/BackgroundTasks.php index 30ecd8e035c..08a01b4ce1f 100644 --- a/Sources/Db/Schema/v3_0/BackgroundTasks.php +++ b/Sources/Db/Schema/v3_0/BackgroundTasks.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/BanGroups.php b/Sources/Db/Schema/v3_0/BanGroups.php index 7eac2fdb18a..0b7f0637b52 100644 --- a/Sources/Db/Schema/v3_0/BanGroups.php +++ b/Sources/Db/Schema/v3_0/BanGroups.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/BanItems.php b/Sources/Db/Schema/v3_0/BanItems.php index 566a8c3444b..2d6666ef05c 100644 --- a/Sources/Db/Schema/v3_0/BanItems.php +++ b/Sources/Db/Schema/v3_0/BanItems.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/BoardPermissions.php b/Sources/Db/Schema/v3_0/BoardPermissions.php index eec1735fa61..6f5417fd9dc 100644 --- a/Sources/Db/Schema/v3_0/BoardPermissions.php +++ b/Sources/Db/Schema/v3_0/BoardPermissions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/BoardPermissionsView.php b/Sources/Db/Schema/v3_0/BoardPermissionsView.php index b4b35213537..3b59bd461bd 100644 --- a/Sources/Db/Schema/v3_0/BoardPermissionsView.php +++ b/Sources/Db/Schema/v3_0/BoardPermissionsView.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Boards.php b/Sources/Db/Schema/v3_0/Boards.php index 93d0a61ee40..0f5917f5ee0 100644 --- a/Sources/Db/Schema/v3_0/Boards.php +++ b/Sources/Db/Schema/v3_0/Boards.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Calendar.php b/Sources/Db/Schema/v3_0/Calendar.php index 2a20f588d57..fb4dfc6d3fd 100644 --- a/Sources/Db/Schema/v3_0/Calendar.php +++ b/Sources/Db/Schema/v3_0/Calendar.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Categories.php b/Sources/Db/Schema/v3_0/Categories.php index d7a24c240c5..79fe5389db5 100644 --- a/Sources/Db/Schema/v3_0/Categories.php +++ b/Sources/Db/Schema/v3_0/Categories.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/CustomFields.php b/Sources/Db/Schema/v3_0/CustomFields.php index a22c3d867b4..93b6a1e6520 100644 --- a/Sources/Db/Schema/v3_0/CustomFields.php +++ b/Sources/Db/Schema/v3_0/CustomFields.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/GroupModerators.php b/Sources/Db/Schema/v3_0/GroupModerators.php index 9b015616c38..e96bc10bf2f 100644 --- a/Sources/Db/Schema/v3_0/GroupModerators.php +++ b/Sources/Db/Schema/v3_0/GroupModerators.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogActions.php b/Sources/Db/Schema/v3_0/LogActions.php index 7e1619e1c70..8ca55062512 100644 --- a/Sources/Db/Schema/v3_0/LogActions.php +++ b/Sources/Db/Schema/v3_0/LogActions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogActivity.php b/Sources/Db/Schema/v3_0/LogActivity.php index 8a28a67c0da..3ea52bbb2f6 100644 --- a/Sources/Db/Schema/v3_0/LogActivity.php +++ b/Sources/Db/Schema/v3_0/LogActivity.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogBanned.php b/Sources/Db/Schema/v3_0/LogBanned.php index fd1ca9ef854..f025718d20f 100644 --- a/Sources/Db/Schema/v3_0/LogBanned.php +++ b/Sources/Db/Schema/v3_0/LogBanned.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogBoards.php b/Sources/Db/Schema/v3_0/LogBoards.php index 166e31de697..80800ab9fc0 100644 --- a/Sources/Db/Schema/v3_0/LogBoards.php +++ b/Sources/Db/Schema/v3_0/LogBoards.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogComments.php b/Sources/Db/Schema/v3_0/LogComments.php index cb306d308af..4da1ab617b8 100644 --- a/Sources/Db/Schema/v3_0/LogComments.php +++ b/Sources/Db/Schema/v3_0/LogComments.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogDigest.php b/Sources/Db/Schema/v3_0/LogDigest.php index 7f1a6a935f2..4224c88a802 100644 --- a/Sources/Db/Schema/v3_0/LogDigest.php +++ b/Sources/Db/Schema/v3_0/LogDigest.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogErrors.php b/Sources/Db/Schema/v3_0/LogErrors.php index 1efafc6d7bd..5f5d7730b9d 100644 --- a/Sources/Db/Schema/v3_0/LogErrors.php +++ b/Sources/Db/Schema/v3_0/LogErrors.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogFloodcontrol.php b/Sources/Db/Schema/v3_0/LogFloodcontrol.php index 0a5a0c9cfdf..f2b431a9c4f 100644 --- a/Sources/Db/Schema/v3_0/LogFloodcontrol.php +++ b/Sources/Db/Schema/v3_0/LogFloodcontrol.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogGroupRequests.php b/Sources/Db/Schema/v3_0/LogGroupRequests.php index 51e42550bde..c3c580587e8 100644 --- a/Sources/Db/Schema/v3_0/LogGroupRequests.php +++ b/Sources/Db/Schema/v3_0/LogGroupRequests.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogMarkRead.php b/Sources/Db/Schema/v3_0/LogMarkRead.php index 545044d750d..78e6b8f70a1 100644 --- a/Sources/Db/Schema/v3_0/LogMarkRead.php +++ b/Sources/Db/Schema/v3_0/LogMarkRead.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogMemberNotices.php b/Sources/Db/Schema/v3_0/LogMemberNotices.php index 5ca8a37dff8..3f5848b27e8 100644 --- a/Sources/Db/Schema/v3_0/LogMemberNotices.php +++ b/Sources/Db/Schema/v3_0/LogMemberNotices.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogNotify.php b/Sources/Db/Schema/v3_0/LogNotify.php index 6766038f8e0..73be311febf 100644 --- a/Sources/Db/Schema/v3_0/LogNotify.php +++ b/Sources/Db/Schema/v3_0/LogNotify.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogOnline.php b/Sources/Db/Schema/v3_0/LogOnline.php index 8211e80ed5a..d647e0e7db2 100644 --- a/Sources/Db/Schema/v3_0/LogOnline.php +++ b/Sources/Db/Schema/v3_0/LogOnline.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogPackages.php b/Sources/Db/Schema/v3_0/LogPackages.php index 58f4dac893e..9fe146588da 100644 --- a/Sources/Db/Schema/v3_0/LogPackages.php +++ b/Sources/Db/Schema/v3_0/LogPackages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogPolls.php b/Sources/Db/Schema/v3_0/LogPolls.php index 0dcf77983a9..584f456d8e6 100644 --- a/Sources/Db/Schema/v3_0/LogPolls.php +++ b/Sources/Db/Schema/v3_0/LogPolls.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogReported.php b/Sources/Db/Schema/v3_0/LogReported.php index b8c75d36ec8..1d2e4fdb963 100644 --- a/Sources/Db/Schema/v3_0/LogReported.php +++ b/Sources/Db/Schema/v3_0/LogReported.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogReportedComments.php b/Sources/Db/Schema/v3_0/LogReportedComments.php index c04eb8d8be2..8fb8d7f1f7f 100644 --- a/Sources/Db/Schema/v3_0/LogReportedComments.php +++ b/Sources/Db/Schema/v3_0/LogReportedComments.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogScheduledTasks.php b/Sources/Db/Schema/v3_0/LogScheduledTasks.php index bdb88e4ad10..35392797a28 100644 --- a/Sources/Db/Schema/v3_0/LogScheduledTasks.php +++ b/Sources/Db/Schema/v3_0/LogScheduledTasks.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogSearchMessages.php b/Sources/Db/Schema/v3_0/LogSearchMessages.php index df9f9487e43..3b30abd1ff2 100644 --- a/Sources/Db/Schema/v3_0/LogSearchMessages.php +++ b/Sources/Db/Schema/v3_0/LogSearchMessages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogSearchResults.php b/Sources/Db/Schema/v3_0/LogSearchResults.php index a652de6b384..313e6562524 100644 --- a/Sources/Db/Schema/v3_0/LogSearchResults.php +++ b/Sources/Db/Schema/v3_0/LogSearchResults.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogSearchSubjects.php b/Sources/Db/Schema/v3_0/LogSearchSubjects.php index 210c29f09b0..6f9a59d1ac5 100644 --- a/Sources/Db/Schema/v3_0/LogSearchSubjects.php +++ b/Sources/Db/Schema/v3_0/LogSearchSubjects.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogSearchTopics.php b/Sources/Db/Schema/v3_0/LogSearchTopics.php index c889302b90c..77eb5927017 100644 --- a/Sources/Db/Schema/v3_0/LogSearchTopics.php +++ b/Sources/Db/Schema/v3_0/LogSearchTopics.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogSpiderHits.php b/Sources/Db/Schema/v3_0/LogSpiderHits.php index 85c26f0e65d..5b3308a9f09 100644 --- a/Sources/Db/Schema/v3_0/LogSpiderHits.php +++ b/Sources/Db/Schema/v3_0/LogSpiderHits.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogSpiderStats.php b/Sources/Db/Schema/v3_0/LogSpiderStats.php index ef14dfeb122..0cd34bdcf09 100644 --- a/Sources/Db/Schema/v3_0/LogSpiderStats.php +++ b/Sources/Db/Schema/v3_0/LogSpiderStats.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogSubscribed.php b/Sources/Db/Schema/v3_0/LogSubscribed.php index 1a7eb481500..1856e0634c0 100644 --- a/Sources/Db/Schema/v3_0/LogSubscribed.php +++ b/Sources/Db/Schema/v3_0/LogSubscribed.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/LogTopics.php b/Sources/Db/Schema/v3_0/LogTopics.php index 3da3b93d899..cc93008b4bd 100644 --- a/Sources/Db/Schema/v3_0/LogTopics.php +++ b/Sources/Db/Schema/v3_0/LogTopics.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/MailQueue.php b/Sources/Db/Schema/v3_0/MailQueue.php index 2b3c27170f1..24449c69171 100644 --- a/Sources/Db/Schema/v3_0/MailQueue.php +++ b/Sources/Db/Schema/v3_0/MailQueue.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/MemberLogins.php b/Sources/Db/Schema/v3_0/MemberLogins.php index af9ca3957ce..6745ce3ed70 100644 --- a/Sources/Db/Schema/v3_0/MemberLogins.php +++ b/Sources/Db/Schema/v3_0/MemberLogins.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Membergroups.php b/Sources/Db/Schema/v3_0/Membergroups.php index 1af348fd34f..e4677f1ed39 100644 --- a/Sources/Db/Schema/v3_0/Membergroups.php +++ b/Sources/Db/Schema/v3_0/Membergroups.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Members.php b/Sources/Db/Schema/v3_0/Members.php index 973f7fbcd87..2a8a5d4337f 100644 --- a/Sources/Db/Schema/v3_0/Members.php +++ b/Sources/Db/Schema/v3_0/Members.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Mentions.php b/Sources/Db/Schema/v3_0/Mentions.php index 54df5dfd099..95474fb585b 100644 --- a/Sources/Db/Schema/v3_0/Mentions.php +++ b/Sources/Db/Schema/v3_0/Mentions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/MessageIcons.php b/Sources/Db/Schema/v3_0/MessageIcons.php index a98f6b96dc4..5157db1d10b 100644 --- a/Sources/Db/Schema/v3_0/MessageIcons.php +++ b/Sources/Db/Schema/v3_0/MessageIcons.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Messages.php b/Sources/Db/Schema/v3_0/Messages.php index 6fe9254d034..091994cff5a 100644 --- a/Sources/Db/Schema/v3_0/Messages.php +++ b/Sources/Db/Schema/v3_0/Messages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/ModeratorGroups.php b/Sources/Db/Schema/v3_0/ModeratorGroups.php index cdc5d2d3b20..be4740c861f 100644 --- a/Sources/Db/Schema/v3_0/ModeratorGroups.php +++ b/Sources/Db/Schema/v3_0/ModeratorGroups.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Moderators.php b/Sources/Db/Schema/v3_0/Moderators.php index d40dd470abe..fb979ba714d 100644 --- a/Sources/Db/Schema/v3_0/Moderators.php +++ b/Sources/Db/Schema/v3_0/Moderators.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PackageServers.php b/Sources/Db/Schema/v3_0/PackageServers.php index 3221e1d3107..c460d59f0de 100644 --- a/Sources/Db/Schema/v3_0/PackageServers.php +++ b/Sources/Db/Schema/v3_0/PackageServers.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PermissionProfiles.php b/Sources/Db/Schema/v3_0/PermissionProfiles.php index 67556e7bc6a..e2f69d5dcdf 100644 --- a/Sources/Db/Schema/v3_0/PermissionProfiles.php +++ b/Sources/Db/Schema/v3_0/PermissionProfiles.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Permissions.php b/Sources/Db/Schema/v3_0/Permissions.php index 05d130404e5..bb982da802e 100644 --- a/Sources/Db/Schema/v3_0/Permissions.php +++ b/Sources/Db/Schema/v3_0/Permissions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PersonalMessages.php b/Sources/Db/Schema/v3_0/PersonalMessages.php index 7726f666a84..7c59616579e 100644 --- a/Sources/Db/Schema/v3_0/PersonalMessages.php +++ b/Sources/Db/Schema/v3_0/PersonalMessages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PmLabeledMessages.php b/Sources/Db/Schema/v3_0/PmLabeledMessages.php index 6b76841067a..4612d0cfc04 100644 --- a/Sources/Db/Schema/v3_0/PmLabeledMessages.php +++ b/Sources/Db/Schema/v3_0/PmLabeledMessages.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PmLabels.php b/Sources/Db/Schema/v3_0/PmLabels.php index 3a84e730f9e..674ba7bcc49 100644 --- a/Sources/Db/Schema/v3_0/PmLabels.php +++ b/Sources/Db/Schema/v3_0/PmLabels.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PmRecipients.php b/Sources/Db/Schema/v3_0/PmRecipients.php index 9162eb64385..7504c25ba5c 100644 --- a/Sources/Db/Schema/v3_0/PmRecipients.php +++ b/Sources/Db/Schema/v3_0/PmRecipients.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PmRules.php b/Sources/Db/Schema/v3_0/PmRules.php index cd91e918984..106953b3e85 100644 --- a/Sources/Db/Schema/v3_0/PmRules.php +++ b/Sources/Db/Schema/v3_0/PmRules.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/PollChoices.php b/Sources/Db/Schema/v3_0/PollChoices.php index 44014c031fe..397e87dd73c 100644 --- a/Sources/Db/Schema/v3_0/PollChoices.php +++ b/Sources/Db/Schema/v3_0/PollChoices.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Polls.php b/Sources/Db/Schema/v3_0/Polls.php index dad48c79ac5..56874ecf548 100644 --- a/Sources/Db/Schema/v3_0/Polls.php +++ b/Sources/Db/Schema/v3_0/Polls.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Qanda.php b/Sources/Db/Schema/v3_0/Qanda.php index 030989d3ed6..fd2135c4d92 100644 --- a/Sources/Db/Schema/v3_0/Qanda.php +++ b/Sources/Db/Schema/v3_0/Qanda.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/ScheduledTasks.php b/Sources/Db/Schema/v3_0/ScheduledTasks.php index 7c97e27c99d..3687b0f62d9 100644 --- a/Sources/Db/Schema/v3_0/ScheduledTasks.php +++ b/Sources/Db/Schema/v3_0/ScheduledTasks.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Sessions.php b/Sources/Db/Schema/v3_0/Sessions.php index 698486b4c09..72ca90c3731 100644 --- a/Sources/Db/Schema/v3_0/Sessions.php +++ b/Sources/Db/Schema/v3_0/Sessions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Settings.php b/Sources/Db/Schema/v3_0/Settings.php index 9648283117c..5b69002c228 100644 --- a/Sources/Db/Schema/v3_0/Settings.php +++ b/Sources/Db/Schema/v3_0/Settings.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/SmileyFiles.php b/Sources/Db/Schema/v3_0/SmileyFiles.php index b77f723f215..8792f89d22c 100644 --- a/Sources/Db/Schema/v3_0/SmileyFiles.php +++ b/Sources/Db/Schema/v3_0/SmileyFiles.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Smileys.php b/Sources/Db/Schema/v3_0/Smileys.php index 928a8b23b0e..21fee938a16 100644 --- a/Sources/Db/Schema/v3_0/Smileys.php +++ b/Sources/Db/Schema/v3_0/Smileys.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Spiders.php b/Sources/Db/Schema/v3_0/Spiders.php index b8829da8c63..f0a02cd8b1d 100644 --- a/Sources/Db/Schema/v3_0/Spiders.php +++ b/Sources/Db/Schema/v3_0/Spiders.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Subscriptions.php b/Sources/Db/Schema/v3_0/Subscriptions.php index 3746a99c7fe..1ec513484c9 100644 --- a/Sources/Db/Schema/v3_0/Subscriptions.php +++ b/Sources/Db/Schema/v3_0/Subscriptions.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Themes.php b/Sources/Db/Schema/v3_0/Themes.php index 8f0ead55114..9bb685b9400 100644 --- a/Sources/Db/Schema/v3_0/Themes.php +++ b/Sources/Db/Schema/v3_0/Themes.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/Topics.php b/Sources/Db/Schema/v3_0/Topics.php index 6de9a6192dd..d7d4777d129 100644 --- a/Sources/Db/Schema/v3_0/Topics.php +++ b/Sources/Db/Schema/v3_0/Topics.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/UserAlerts.php b/Sources/Db/Schema/v3_0/UserAlerts.php index 7f4f9cfb4df..734d990cf0f 100644 --- a/Sources/Db/Schema/v3_0/UserAlerts.php +++ b/Sources/Db/Schema/v3_0/UserAlerts.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/UserAlertsPrefs.php b/Sources/Db/Schema/v3_0/UserAlertsPrefs.php index 333dd51a2f2..2dd589b58c2 100644 --- a/Sources/Db/Schema/v3_0/UserAlertsPrefs.php +++ b/Sources/Db/Schema/v3_0/UserAlertsPrefs.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/UserDrafts.php b/Sources/Db/Schema/v3_0/UserDrafts.php index f66f52e1f66..64c6b69b146 100644 --- a/Sources/Db/Schema/v3_0/UserDrafts.php +++ b/Sources/Db/Schema/v3_0/UserDrafts.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Db/Schema/v3_0/UserLikes.php b/Sources/Db/Schema/v3_0/UserLikes.php index 157dd2a14f4..76f1aff4c33 100644 --- a/Sources/Db/Schema/v3_0/UserLikes.php +++ b/Sources/Db/Schema/v3_0/UserLikes.php @@ -8,7 +8,7 @@ * @copyright 2023 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Debug/DebugUtils.php b/Sources/Debug/DebugUtils.php index 3cbf4ea1866..bb3cf92d18d 100644 --- a/Sources/Debug/DebugUtils.php +++ b/Sources/Debug/DebugUtils.php @@ -8,7 +8,7 @@ * @copyright 2025 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Sources/Maintenance/Migration/v3_0/PackageVersion.php b/Sources/Maintenance/Migration/v3_0/PackageVersion.php index dbcc3a58f94..e37c70eacde 100644 --- a/Sources/Maintenance/Migration/v3_0/PackageVersion.php +++ b/Sources/Maintenance/Migration/v3_0/PackageVersion.php @@ -8,7 +8,7 @@ * @copyright 2024 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 1 + * @version 3.0 Alpha 3 */ declare(strict_types=1); diff --git a/Themes/default/scripts/jquery.sceditor.smf.js b/Themes/default/scripts/jquery.sceditor.smf.js index 10c22903093..3f3d15c60e6 100644 --- a/Themes/default/scripts/jquery.sceditor.smf.js +++ b/Themes/default/scripts/jquery.sceditor.smf.js @@ -6,7 +6,7 @@ * @copyright 2025 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 */ (function ($) { diff --git a/Themes/default/scripts/smf_jquery_plugins.js b/Themes/default/scripts/smf_jquery_plugins.js index b956f96fa4c..c261582c1c1 100644 --- a/Themes/default/scripts/smf_jquery_plugins.js +++ b/Themes/default/scripts/smf_jquery_plugins.js @@ -14,7 +14,7 @@ * @copyright 2025 Simple Machines and individual contributors * @license https://www.simplemachines.org/about/smf/license.php BSD * - * @version 3.0 Alpha 2 + * @version 3.0 Alpha 3 * */ From fc82f9be6bb53e9ea6dd1d54c12a3d8776cebbb0 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 20 May 2025 12:00:20 -0600 Subject: [PATCH 17/90] Fixes logic in Table::alterIndex() Signed-off-by: Jon Stovell --- Sources/Db/Schema/Table.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Db/Schema/Table.php b/Sources/Db/Schema/Table.php index 931ae3b7341..4d41dc3fa7a 100644 --- a/Sources/Db/Schema/Table.php +++ b/Sources/Db/Schema/Table.php @@ -222,7 +222,9 @@ public function addIndex(DbIndex $index, string $if_exists = 'update'): bool public function alterIndex(DbIndex $index): bool { // This method is really just a convenient way to replace an existing index. - $this->dropIndex($index); + if (!$this->dropIndex($index)) { + return false; + } return $this->addIndex($index); } From 3312c03d5deaca3d179d0c570cd31b50d28f50f8 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 20 May 2025 12:09:48 -0600 Subject: [PATCH 18/90] PHP's safe_mode was deprecated long ago Signed-off-by: Jon Stovell --- Sources/Tasks/ExportProfileData.php | 2 +- Sources/Tasks/Utf8EntityDecode.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Tasks/ExportProfileData.php b/Sources/Tasks/ExportProfileData.php index e480f4a1761..d5c87eadb75 100644 --- a/Sources/Tasks/ExportProfileData.php +++ b/Sources/Tasks/ExportProfileData.php @@ -887,7 +887,7 @@ public function execute(): bool // Avoid leaving files in an inconsistent state. ignore_user_abort(true); - $this->time_limit = (int) ((ini_get('safe_mode') === false && @set_time_limit(Taskrunner::MAX_CLAIM_THRESHOLD) !== false) ? Taskrunner::MAX_CLAIM_THRESHOLD : (int) ini_get('max_execution_time')); + $this->time_limit = (int) (Sapi::setTimeLimit(Taskrunner::MAX_CLAIM_THRESHOLD) !== false ? Taskrunner::MAX_CLAIM_THRESHOLD : (int) ini_get('max_execution_time')); // This could happen if the user manually changed the URL params of the export request. if ($this->_details['format'] == 'HTML' && (!class_exists('DOMDocument') || !class_exists('XSLTProcessor'))) { diff --git a/Sources/Tasks/Utf8EntityDecode.php b/Sources/Tasks/Utf8EntityDecode.php index f8c2ba8d48e..8e37f47e7f1 100644 --- a/Sources/Tasks/Utf8EntityDecode.php +++ b/Sources/Tasks/Utf8EntityDecode.php @@ -45,7 +45,7 @@ public function execute(): bool // Avoid leaving data in an inconsistent state. ignore_user_abort(true); - $time_limit = (int) (((ini_get('safe_mode') === false && Sapi::setTimeLimit(Taskrunner::MAX_CLAIM_THRESHOLD) !== false) ? Taskrunner::MAX_CLAIM_THRESHOLD : (int) ini_get('max_execution_time')) / 2); + $time_limit = (int) ((Sapi::setTimeLimit(Taskrunner::MAX_CLAIM_THRESHOLD) !== false ? Taskrunner::MAX_CLAIM_THRESHOLD : (int) ini_get('max_execution_time')) / 2); // Check that the table actually exists. if ( From f048b40041965166112be2d8827cfd9f80fa883b Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 20 May 2025 12:49:53 -0600 Subject: [PATCH 19/90] Fixes a bug in SMF\Tasks\Utf8EntityDecode::updateDirectly() Signed-off-by: Jon Stovell --- Sources/Tasks/Utf8EntityDecode.php | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/Sources/Tasks/Utf8EntityDecode.php b/Sources/Tasks/Utf8EntityDecode.php index 8e37f47e7f1..1e45e06a93f 100644 --- a/Sources/Tasks/Utf8EntityDecode.php +++ b/Sources/Tasks/Utf8EntityDecode.php @@ -149,7 +149,7 @@ public function execute(): bool $this->_details['offset']++; if ($update_directly) { - $this->updateDirectly($row, $order_by, $where, $string_columns); + $this->updateDirectly($row, $where, $string_columns); } else { $this->recordInTempTable($row, $string_columns); } @@ -199,11 +199,10 @@ protected function decode(string $string): string * each string column in a row and then updates the table with the new data. * * @param array $row A row of data that was retrieved from the table. - * @param array $order_by The columns used to order the rows during retrieval. * @param array $where Conditions used to find the correct row to update. * @param array $string_columns The columns whose data needs to be updated. */ - private function updateDirectly(array $row, array $order_by, array $where, array $string_columns): void + private function updateDirectly(array $row, array $where, array $string_columns): void { $params = [ 'table' => $this->_details['table'], @@ -212,12 +211,7 @@ private function updateDirectly(array $row, array $order_by, array $where, array $set = []; foreach ($row as $col => $value) { - if (!is_string($value)) { - unset($where[$col]); - continue; - } - - if (in_array($col, $order_by)) { + if (isset($where[$col])) { $params[$col] = $value; } @@ -378,21 +372,21 @@ private function dropTempTable(array $string_columns): void private function respawn(): void { Db::$db->insert( - 'insert', - '{db_prefix}background_tasks', - [ + method: 'insert', + table: '{db_prefix}background_tasks', + columns: [ 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int', ], - [ + data: [ [ get_class($this), json_encode($this->_details), 0, ], ], - [], + keys: [], ); } } From 924b6c1cd4e09f53be04f8e7299ddbb4b09bc9ee Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 11:51:03 -0600 Subject: [PATCH 20/90] Bulletproofing for TasksDirCase::execute() Signed-off-by: Jon Stovell --- .../Maintenance/Cleanup/v3_0/TasksDirCase.php | 106 +++++++++++++++--- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php b/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php index 900fb885d64..a90f131b012 100644 --- a/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php +++ b/Sources/Maintenance/Cleanup/v3_0/TasksDirCase.php @@ -17,6 +17,7 @@ use SMF\Config; use SMF\Maintenance\Cleanup\CleanupBase; +use SMF\Utils; class TasksDirCase extends CleanupBase { @@ -40,15 +41,14 @@ class TasksDirCase extends CleanupBase */ public function isCandidate(): bool { - clearstatcache(); - $current_settings = Config::getCurrentSettings(filemtime(SMF_SETTINGS_FILE)); + list($sourcedir, $tasksdir) = $this->getDirs(); return ( - isset($current_settings['tasksdir']) - && is_dir($current_settings['tasksdir']) - && basename($current_settings['tasksdir']) !== 'Tasks' - && is_writable($current_settings['tasksdir']) - && is_writable($current_settings['sourcedir']) + isset($tasksdir) + && is_dir($tasksdir) + && basename($tasksdir) !== 'Tasks' + && Utils::makeWritable($tasksdir) + && Utils::makeWritable($sourcedir) ); } @@ -57,28 +57,104 @@ public function isCandidate(): bool */ public function execute(): bool { + list($sourcedir, $tasksdir) = $this->getDirs(); + // Do 'tasks' and 'Tasks' both exist? + // (This can only happen on case sensitive file systems.) if ( - !empty(fileinode(realpath($current_settings['sourcedir'] . '/tasks'))) - && !empty(fileinode(realpath($current_settings['sourcedir'] . '/Tasks'))) - && fileinode(realpath($current_settings['tasksdir'])) !== fileinode(realpath($current_settings['sourcedir'] . '/Tasks')) + is_dir($tasksdir) + && is_dir($sourcedir . DIRECTORY_SEPARATOR . 'Tasks') + && !empty(fileinode(realpath($tasksdir))) + && !empty(fileinode(realpath($sourcedir . DIRECTORY_SEPARATOR . 'Tasks'))) + && fileinode(realpath($tasksdir)) !== fileinode(realpath($sourcedir . DIRECTORY_SEPARATOR . 'Tasks')) ) { // Move everything in 'Tasks' to 'tasks'. foreach ( - glob(realpath($current_settings['sourcedir'] . '/Tasks') . DIRECTORY_SEPARATOR . '*') as $path + glob($sourcedir . DIRECTORY_SEPARATOR . 'Tasks' . DIRECTORY_SEPARATOR . '*') as $path ) { - rename($path, realpath($current_settings['tasksdir']) . DIRECTORY_SEPARATOR . basename($path)); + $new_path = $tasksdir . DIRECTORY_SEPARATOR . basename($path); + + // Does a file already exist at the new path? + if (file_exists($new_path)) { + Utils::makeWritable($new_path); + + // Remove the conflicting file. + if (!@unlink($new_path)) { + // Unlinking failed? Try renaming it. + if (!@rename($new_path, $new_path . '_' . date_create()->format('YmdHis'))) { + // Last ditch effort. + if (file_put_contents($new_path, file_get_contents($path)) === false) { + return false; + } + + if (!@unlink($path)) { + return false; + } + + continue; + } + } + } + + rename($path, $new_path); } // Now delete 'Tasks'. - rmdir(realpath($current_settings['sourcedir'] . '/Tasks')); + if (!@rmdir($sourcedir . DIRECTORY_SEPARATOR . 'Tasks')) { + // If 'Tasks' couldn't be deleted, try renaming it. + if ( + !@rename( + $sourcedir . DIRECTORY_SEPARATOR . 'Tasks', + $sourcedir . DIRECTORY_SEPARATOR . 'DELETE_ME', + ) + ) { + // Cannot continue. + return false; + } + } } // Rename 'tasks' to 'Tasks'. // Do this in two steps to make sure it works on case insensitive file systems. - rename($current_settings['tasksdir'], $current_settings['sourcedir'] . DIRECTORY_SEPARATOR . 'Tasks_temp'); - rename($current_settings['sourcedir'] . DIRECTORY_SEPARATOR . 'Tasks_temp', $current_settings['sourcedir'] . DIRECTORY_SEPARATOR . 'Tasks'); + rename( + $tasksdir, + $sourcedir . DIRECTORY_SEPARATOR . 'Tasks_temp', + ); + rename( + $sourcedir . DIRECTORY_SEPARATOR . 'Tasks_temp', + $sourcedir . DIRECTORY_SEPARATOR . 'Tasks', + ); + + // Remove the tasksdir setting. SMF 3.0 does not use it. + Config::updateSettingsFile(['tasksdir' => '']); return true; } + + /****************** + * Internal methods + ******************/ + + /** + * Gets the $sourcedir and $tasksdir paths. + * + * @return array The values for $sourcedir and $tasksdir. + */ + private function getDirs(): array + { + clearstatcache(); + $current_settings = Config::getCurrentSettings(filemtime(SMF_SETTINGS_FILE)); + + // If the tasksdir setting does not exist but the existing directory's + // real name does in fact use the wrong case, we still want to fix it. + if (!isset($current_settings['tasksdir'])) { + foreach (glob($current_settings['sourcedir'] . DIRECTORY_SEPARATOR . '*') as $path) { + if (is_dir($path) && basename($path) === 'tasks') { + $current_settings['tasksdir'] = $current_settings['sourcedir'] . DIRECTORY_SEPARATOR . 'tasks'; + } + } + } + + return [$current_settings['sourcedir'], $current_settings['tasksdir'] ?? null]; + } } From f7a6c9160916323fc43e96c9435fba8a851a2e3e Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 11:55:02 -0600 Subject: [PATCH 21/90] Fixes language file name in installer Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Install.php | 92 +++++++++++++-------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/Sources/Maintenance/Tools/Install.php b/Sources/Maintenance/Tools/Install.php index b5363a7e203..d018d2f1c29 100644 --- a/Sources/Maintenance/Tools/Install.php +++ b/Sources/Maintenance/Tools/Install.php @@ -133,7 +133,7 @@ public function __construct() */ public function getScriptName(): string { - return Lang::getTxt('smf_installer', file: 'Install'); + return Lang::getTxt('smf_installer', file: 'Maintenance'); } /** @@ -171,54 +171,54 @@ public function getSteps(): array return [ 0 => new Step( id: 1, - name: Lang::getTxt('install_step_welcome', file: 'Install'), - title: Lang::getTxt('install_welcome', file: 'Install'), + name: Lang::getTxt('install_step_welcome', file: 'Maintenance'), + title: Lang::getTxt('install_welcome', file: 'Maintenance'), function: 'welcome', template: 'welcome', progress: 0, ), 1 => new Step( id: 2, - name: Lang::getTxt('install_step_writable', file: 'Install'), + name: Lang::getTxt('install_step_writable', file: 'Maintenance'), function: 'checkFilesWritable', template: 'checkFilesWritable', progress: 10, ), 2 => new Step( id: 3, - name: Lang::getTxt('install_step_databaseset', file: 'Install'), - title: Lang::getTxt('db_settings', file: 'Install'), + name: Lang::getTxt('install_step_databaseset', file: 'Maintenance'), + title: Lang::getTxt('db_settings', file: 'Maintenance'), function: 'databaseSettings', template: 'databaseSettings', progress: 15, ), 3 => new Step( id: 4, - name: Lang::getTxt('install_step_forum', file: 'Install'), - title: Lang::getTxt('install_settings', file: 'Install'), + name: Lang::getTxt('install_step_forum', file: 'Maintenance'), + title: Lang::getTxt('install_settings', file: 'Maintenance'), function: 'forumSettings', template: 'forumSettings', progress: 40, ), 4 => new Step( id: 5, - name: Lang::getTxt('install_step_databasechange', file: 'Install'), - title: Lang::getTxt('db_populate', file: 'Install'), + name: Lang::getTxt('install_step_databasechange', file: 'Maintenance'), + title: Lang::getTxt('db_populate', file: 'Maintenance'), function: 'databasePopulation', template: 'databasePopulation', progress: 15, ), 5 => new Step( id: 6, - name: Lang::getTxt('install_step_admin', file: 'Install'), - title: Lang::getTxt('user_settings', file: 'Install'), + name: Lang::getTxt('install_step_admin', file: 'Maintenance'), + title: Lang::getTxt('user_settings', file: 'Maintenance'), function: 'adminAccount', template: 'adminAccount', progress: 20, ), 6 => new Step( id: 7, - name: Lang::getTxt('install_step_finalize', file: 'Install'), + name: Lang::getTxt('install_step_finalize', file: 'Maintenance'), function: 'finalize', template: 'finalize', progress: 0, @@ -249,60 +249,60 @@ public function welcome(): bool } if (Maintenance::isInstalled()) { - Maintenance::$context['warning'] = Lang::getTxt('error_already_installed', file: 'Install'); + Maintenance::$context['warning'] = Lang::getTxt('error_already_installed', file: 'Maintenance'); } Maintenance::$context['supported_databases'] = $this->supportedDatabases(); // Needs to at least meet our miniumn version. if ((version_compare(Maintenance::PHP_MIN_VERSION, PHP_VERSION, '>'))) { - Maintenance::$fatal_error = Lang::getTxt('error_php_too_low', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_php_too_low', file: 'Maintenance'); return false; } // Make sure we have a supported database if (empty(Maintenance::$context['supported_databases'])) { - Maintenance::$fatal_error = Lang::getTxt('error_db_missing', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_db_missing', file: 'Maintenance'); return false; } // How about session support? Some crazy sysadmin remove it? if (!function_exists('session_start')) { - Maintenance::$errors[] = Lang::getTxt('error_session_missing', file: 'Install'); + Maintenance::$errors[] = Lang::getTxt('error_session_missing', file: 'Maintenance'); } // Make sure they uploaded all the files. if (!file_exists(Config::$boarddir . '/index.php')) { - Maintenance::$errors[] = Lang::getTxt('error_missing_files', file: 'Install'); + Maintenance::$errors[] = Lang::getTxt('error_missing_files', file: 'Maintenance'); } // Very simple check on the session.save_path for Windows. // @todo Move this down later if they don't use database-driven sessions? elseif (@ini_get('session.save_path') == '/tmp' && Sapi::isOS(Sapi::OS_WINDOWS)) { - Maintenance::$errors[] = Lang::getTxt('error_session_save_path', file: 'Install'); + Maintenance::$errors[] = Lang::getTxt('error_session_save_path', file: 'Maintenance'); } // Mod_security blocks everything that smells funny. Let SMF handle security. if (!$this->checkAndTryToFixModSecurity() && !isset($_GET['overmodsecurity'])) { - Maintenance::$fatal_error = Lang::getTxt('error_mod_security', file: 'Install') . '

' . Lang::getTxt('error_message_click', file: 'Install') . ' ' . Lang::getTxt('error_message_bad_try_again', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_mod_security', file: 'Maintenance') . '

' . Lang::getTxt('error_message_click', file: 'Maintenance') . ' ' . Lang::getTxt('error_message_bad_try_again', file: 'Maintenance'); } // Confirm mbstring is loaded... if (!extension_loaded('mbstring')) { - Maintenance::$errors[] = Lang::getTxt('install_no_mbstring', file: 'Install'); + Maintenance::$errors[] = Lang::getTxt('install_no_mbstring', file: 'Maintenance'); } // Confirm fileinfo is loaded... if (!extension_loaded('fileinfo')) { - Maintenance::$errors[] = Lang::getTxt('install_no_fileinfo', file: 'Install'); + Maintenance::$errors[] = Lang::getTxt('install_no_fileinfo', file: 'Maintenance'); } // Check for https stream support. $supported_streams = stream_get_wrappers(); if (!in_array('https', $supported_streams)) { - Maintenance::$warnings[] = Lang::getTxt('install_no_https', file: 'Install'); + Maintenance::$warnings[] = Lang::getTxt('install_no_https', file: 'Maintenance'); } if (empty(Maintenance::$errors)) { @@ -406,7 +406,7 @@ public function checkFilesWritable(): bool // It's not going to be possible to use FTP on windows to solve the problem... if ($failure && Sapi::isOS(Sapi::OS_WINDOWS)) { - Maintenance::$fatal_error = Lang::getTxt('error_windows_chmod', file: 'Install') . ' + Maintenance::$fatal_error = Lang::getTxt('error_windows_chmod', file: 'Maintenance') . '
  • ' . implode('
  • ', $failed_files) . '
  • @@ -465,7 +465,7 @@ public function checkFilesWritable(): bool 'port' => $_POST['ftp']['port'] ?? '21', 'username' => $_POST['ftp']['username'] ?? '', 'path' => $_POST['ftp']['path'] ?? '/', - 'path_msg' => !empty($found_path) ? Lang::getTxt('ftp_path_found_info', file: 'Install') : Lang::getTxt('ftp_path_info', file: 'Install'), + 'path_msg' => !empty($found_path) ? Lang::getTxt('ftp_path_found_info', file: 'Maintenance') : Lang::getTxt('ftp_path_info', file: 'Maintenance'), ]; return false; @@ -505,7 +505,7 @@ public function checkFilesWritable(): bool // Set the username etc, into context. Maintenance::$context['ftp'] = $_SESSION['ftp'] += [ - 'path_msg' => Lang::getTxt('ftp_path_info', file: 'Install'), + 'path_msg' => Lang::getTxt('ftp_path_info', file: 'Maintenance'), ]; return false; @@ -571,7 +571,7 @@ public function databaseSettings(): bool $db_prefix = $_POST['db_prefix']; if (!isset(Maintenance::$context['databases'][$db_type])) { - Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Maintenance'); return false; } @@ -582,7 +582,7 @@ public function databaseSettings(): bool // Use a try/catch here, so we can send specific details about the validation error. try { if (!$db->validatePrefix($db_prefix)) { - Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Maintenance'); return false; } @@ -612,12 +612,12 @@ public function databaseSettings(): bool // God I hope it saved! try { if (!Config::updateSettingsFile($vars)) { - Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Maintenance'); return false; } } catch (\Throwable $e) { - Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Maintenance'); return false; } @@ -656,7 +656,7 @@ public function databaseSettings(): bool } $db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message; - Maintenance::$fatal_error = Lang::getTxt('error_db_connect', file: 'Install') . '
    ' . $db_error . '
    '; + Maintenance::$fatal_error = Lang::getTxt('error_db_connect', file: 'Maintenance') . '
    ' . $db_error . '
    '; return false; } @@ -735,7 +735,7 @@ public function forumSettings(): bool throw new \Exception(); } } catch (\Throwable $e) { - Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Maintenance'); return false; } @@ -757,7 +757,7 @@ public function forumSettings(): bool // We have a failure of database configuration. try { if (!Db::$db->checkConfiguration()) { - Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('upgrade_unknown_error', file: 'Maintenance'); return false; } @@ -814,7 +814,7 @@ public function forumSettings(): bool throw new \Exception(); } } catch (\Throwable $e) { - Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Maintenance'); return false; } @@ -865,7 +865,7 @@ public function databasePopulation(): bool // Do they match? If so, this is just a refresh so charge on! if (!isset(Config::$modSettings['smfVersion']) || Config::$modSettings['smfVersion'] != SMF_VERSION) { - Maintenance::$fatal_error = Lang::getTxt('error_versions_do_not_match', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_versions_do_not_match', file: 'Maintenance'); return false; } @@ -1014,12 +1014,12 @@ public function databasePopulation(): bool // Find out if we have permissions we didn't use, but will need for the future. // @@ TODO: This was at this location in the original code, it should come earlier. if (!Db::$db->hasPermissions()) { - Maintenance::$fatal_error = Lang::getTxt('error_db_alter_priv', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_db_alter_priv', file: 'Maintenance'); } // Was this a refresh? if (count($existing_tables) > 0) { - $this->page_title = Lang::getTxt('user_refresh_install', file: 'Install'); + $this->page_title = Lang::getTxt('user_refresh_install', file: 'Maintenance'); Maintenance::$context['was_refresh'] = true; } @@ -1087,21 +1087,21 @@ public function adminAccount(): bool // Wrong password? if (Maintenance::$context['require_db_confirm'] && $_POST['password3'] != Config::$db_passwd) { - Maintenance::$fatal_error = Lang::getTxt('error_db_connect', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_db_connect', file: 'Maintenance'); return false; } // Not matching passwords? if ($_POST['password1'] != $_POST['password2']) { - Maintenance::$fatal_error = Lang::getTxt('error_user_settings_again_match', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_user_settings_again_match', file: 'Maintenance'); return false; } // No password? if (strlen($_POST['password1']) < 4) { - Maintenance::$fatal_error = Lang::getTxt('error_user_settings_no_password', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_user_settings_no_password', file: 'Maintenance'); return false; } @@ -1138,25 +1138,25 @@ public function adminAccount(): bool list(Maintenance::$context['member_id'], Maintenance::$context['member_salt']) = Db::$db->fetch_row($result); Db::$db->free_result($result); - Maintenance::$context['account_existed'] = Lang::getTxt('error_user_settings_taken', file: 'Install'); + Maintenance::$context['account_existed'] = Lang::getTxt('error_user_settings_taken', file: 'Maintenance'); } elseif ($_POST['username'] == '' || strlen($_POST['username']) > 25) { // Try the previous step again. - Maintenance::$fatal_error = $_POST['username'] == '' ? Lang::getTxt('error_username_left_empty', file: 'Install') : Lang::getTxt('error_username_too_long', file: 'Install'); + Maintenance::$fatal_error = $_POST['username'] == '' ? Lang::getTxt('error_username_left_empty', file: 'Maintenance') : Lang::getTxt('error_username_too_long', file: 'Maintenance'); return false; } elseif ($invalid_characters || $_POST['username'] == '_' || $_POST['username'] == '|' || strpos($_POST['username'], '[code') !== false || strpos($_POST['username'], '[/code') !== false) { // Try the previous step again. - Maintenance::$fatal_error = Lang::getTxt('error_invalid_characters_username', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_invalid_characters_username', file: 'Maintenance'); return false; } elseif (empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) || strlen($_POST['email']) > 255) { // One step back, this time fill out a proper admin email address. - Maintenance::$fatal_error = Lang::getTxt('error_valid_admin_email_needed', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_valid_admin_email_needed', file: 'Maintenance'); return false; } elseif (empty($_POST['server_email']) || !filter_var($_POST['server_email'], FILTER_VALIDATE_EMAIL) || strlen($_POST['server_email']) > 255) { // One step back, this time fill out a proper admin email address. - Maintenance::$fatal_error = Lang::getTxt('error_valid_server_email_needed', file: 'Install'); + Maintenance::$fatal_error = Lang::getTxt('error_valid_server_email_needed', file: 'Maintenance'); return false; } elseif ($_POST['username'] != '') { @@ -1370,7 +1370,7 @@ public function finalize(): bool Utils::$context['utf8'] = true; if (Db::$db->num_rows($request) > 0) { - Logging::updateStats('subject', 1, htmlspecialchars(Lang::getTxt('default_topic_subject', file: 'Install'))); + Logging::updateStats('subject', 1, htmlspecialchars(Lang::getTxt('default_topic_subject', file: 'Maintenance'))); } Db::$db->free_result($request); From 1b5f7bcabaa6e783cec6558673409439d0ca599d Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 11:58:41 -0600 Subject: [PATCH 22/90] Fixes 'Undefined array key' in Utf8ConverterStep::convertDatabase() Signed-off-by: Jon Stovell --- Sources/Maintenance/Utf8ConverterStep.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Maintenance/Utf8ConverterStep.php b/Sources/Maintenance/Utf8ConverterStep.php index 6ab9618342e..69e623a6bb9 100644 --- a/Sources/Maintenance/Utf8ConverterStep.php +++ b/Sources/Maintenance/Utf8ConverterStep.php @@ -647,7 +647,7 @@ public function convertDatabase(): bool return true; } - while (Maintenance::getCurrentSubStep() <= Maintenance::$total_substeps) { + while (Maintenance::getCurrentSubStep() < Maintenance::$total_substeps) { $substep = $substeps[Maintenance::getCurrentSubStep()]; if (Sapi::isCLI()) { From 05adf5b67e324b786931b034e93599e33646b825 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 18:43:45 -0600 Subject: [PATCH 23/90] Fixes 'Undefined array key' in Upgrade::backupDatabase() Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Upgrade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index b9ce5090e04..a6855581bca 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -988,7 +988,7 @@ public function backupDatabase(): bool } // Back up each table! - while (Maintenance::getCurrentSubStep() <= Maintenance::$total_substeps) { + while (Maintenance::getCurrentSubStep() < Maintenance::$total_substeps) { $current_table = $table_names[Maintenance::getCurrentSubStep()]; $this->doBackupTable($current_table); From 640e261e5d46ba874e02dfca51675b0d37784b07 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 18:44:49 -0600 Subject: [PATCH 24/90] Improves parsing in Maintenance::parseCliArguments() Signed-off-by: Jon Stovell --- Sources/Maintenance/Maintenance.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Maintenance/Maintenance.php b/Sources/Maintenance/Maintenance.php index 1f8fd1fd3ed..d67bcdac103 100644 --- a/Sources/Maintenance/Maintenance.php +++ b/Sources/Maintenance/Maintenance.php @@ -874,8 +874,8 @@ protected static function parseCliArguments(): void if (!empty($_SERVER['argv']) && Sapi::isCLI()) { for ($i = 1; $i < count($_SERVER['argv']); $i++) { - if (preg_match('/^--([^=]+)=(.*)/', $_SERVER['argv'][$i], $match)) { - $_REQUEST[$match[1]] = $match[2]; + if (preg_match('/^--([^=\s]+)(?:=(.*))?/', $_SERVER['argv'][$i], $match)) { + $_POST[$match[1]] = $match[2] ?? true; } } } From 7e55644660cf1afc736fb0aa70b18ca72b06c854 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 12:29:22 -0600 Subject: [PATCH 25/90] Improves error handling when updating the settings file in upgrader Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Upgrade.php | 68 ++++++++++++++++----------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index a6855581bca..e02db7132b3 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -634,11 +634,11 @@ public function welcomeLogin(): bool $new_locale = Lang::getLocaleFromLanguageName($current_language); if ($new_locale !== null && $new_locale != Config::$language) { - Config::updateSettingsFile(['language' => $new_locale]); + $this->updateSettingsFile(['language' => $new_locale]); } if (empty(Config::$languagesdir)) { - Config::updateSettingsFile(['languagesdir' => Config::$boarddir . '/Languages']); + $this->updateSettingsFile(['languagesdir' => Config::$boarddir . '/Languages']); } // Check agreement.txt. It may not exist, in which case $boarddir must be writable. @@ -913,11 +913,7 @@ public function upgradeOptions(): bool Config::updateModSettings($db_settings); // Update Settings.php with the new settings, and rebuild if they selected that option. - $res = Config::updateSettingsFile($file_settings, false, !empty($_POST['migrateSettings'])); - - if (Sapi::isCLI() && !$res) { - die('FAILURE: Could not update ' . basename(SMF_SETTINGS_FILE) . "\n"); - } + $this->updateSettingsFile($file_settings, false, !empty($_POST['migrateSettings'])); // Empty our error log. if (!empty($_POST['empty_error'])) { @@ -1094,24 +1090,6 @@ public function finalize(): bool { Maintenance::$context['form_action'] = Config::$boardurl . '/index.php'; - // Finalize some settings in the settings file. - $file_settings = [ - 'maintenance' => $this->user['maint'] ?? 0, - ]; - - // Delete all the obsolete settings. - foreach (Config::getSettingsDefs() as $var => $setting_def) { - if (is_string($var) && ($setting_def['auto_delete'] ?? null) === 3) { - $file_settings[$var] = $setting_def['default']; - } - } - - $res = Config::updateSettingsFile($file_settings); - - if (Sapi::isCLI() && !$res) { - die('FAILURE: Could not update ' . basename(SMF_SETTINGS_FILE) . "\n"); - } - // Update the database with the new SMF version. Config::updateModSettings(['smfVersion' => SMF_VERSION]); @@ -1200,6 +1178,21 @@ public function finalize(): bool User::setMe(0); + // Finalize some settings in the settings file. + $file_settings = [ + 'maintenance' => $this->user['maint'] ?? 0, + ]; + + // Delete all the obsolete settings. + foreach (Config::getSettingsDefs() as $var => $setting_def) { + if (is_string($var) && ($setting_def['auto_delete'] ?? null) === 3) { + $file_settings[$var] = $setting_def['default']; + } + } + + $this->updateSettingsFile($file_settings); + + // We're done! if (Sapi::isCLI()) { echo "\n"; echo 'Upgrade Complete!', "\n"; @@ -1316,7 +1309,7 @@ private function prepareUpgrade(): void if (empty(Config::$db_type) || Config::$db_type == 'mysqli') { Config::$db_type = 'mysql'; // If overriding Config::$db_type, need to set its Settings.php entry, too. - Config::updateSettingsFile(['db_type' => 'mysql']); + $this->updateSettingsFile(['db_type' => 'mysql']); } try { @@ -1379,8 +1372,29 @@ private function saveUpgradeData(): bool $data = ''; } - if (!Config::updateSettingsFile(['upgradeData' => $data])) { + return $this->updateSettingsFile(['upgradeData' => $data]); + } + + /** + * Wrapper for Config::updateSettingsFile() with special error handling. + * + * @param array $config_vars An array of one or more variables to update. + * @param bool|null $keep_quotes Whether to strip slashes and trim quotes + * from string values. Defaults to auto-detection. + * @param bool $rebuild If true, attempts to rebuild with standard format. + * Default false. + * @return bool True on success, false on failure. + */ + private function updateSettingsFile(array $config_vars, ?bool $keep_quotes = null, bool $rebuild = false): bool + { + if (!Config::updateSettingsFile($config_vars, $keep_quotes, $rebuild)) { + if (Sapi::isCLI()) { + die('FAILURE: Could not update ' . basename(SMF_SETTINGS_FILE) . "\n"); + } + Maintenance::$fatal_error = Lang::getTxt('upgrade_writable_files', file: 'Maintenance') . ': ' . basename(SMF_SETTINGS_FILE); + + return false; } return true; From 862da9af61ed9db77000cdaf6d02c85e8859e023 Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 14:13:58 -0600 Subject: [PATCH 26/90] Uses Lang::getTxt() for all localized strings in the upgrader Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Upgrade.php | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index e02db7132b3..acb76164cab 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -384,7 +384,7 @@ public function __construct() */ public function getScriptName(): string { - return Lang::$txt['smf_upgrade']; + return Lang::getTxt('smf_upgrade', file: 'Maintenance'); } /** @@ -422,28 +422,28 @@ public function getSteps(): array return [ new Step( id: 1, - name: Lang::$txt['upgrade_step_login'], + name: Lang::getTxt('upgrade_step_login', file: 'Maintenance'), function: 'welcomeLogin', template: 'welcomeLogin', progress: 2, ), new Step( id: 2, - name: Lang::$txt['upgrade_step_options'], + name: Lang::getTxt('upgrade_step_options', file: 'Maintenance'), function: 'upgradeOptions', template: 'upgradeOptions', progress: 3, ), new Step( id: 3, - name: Lang::$txt['upgrade_step_backup'], + name: Lang::getTxt('upgrade_step_backup', file: 'Maintenance'), function: 'backupDatabase', template: 'backupDatabase', progress: 10, ), new Step( id: 4, - name: Lang::$txt['upgrade_step_migration'], + name: Lang::getTxt('upgrade_step_migration', file: 'Maintenance'), function: 'migrations', template: 'migrations', progress: 45, @@ -451,20 +451,20 @@ function: 'migrations', new Utf8ConverterStep( // Note: Utf8ConverterStep does not take a function argument. id: 5, - name: Lang::$txt['upgrade_step_convertutf'], + name: Lang::getTxt('upgrade_step_convertutf', file: 'Maintenance'), template: 'convertUtf8', progress: 30, ), new Step( id: 6, - name: Lang::$txt['upgrade_step_cleanup'], + name: Lang::getTxt('upgrade_step_cleanup', file: 'Maintenance'), function: 'cleanup', template: 'cleanup', progress: 10, ), new Step( id: 7, - name: Lang::$txt['upgrade_step_finalize'], + name: Lang::getTxt('upgrade_step_finalize', file: 'Maintenance'), function: 'finalize', template: 'finalize', progress: 0, @@ -495,14 +495,14 @@ public function welcomeLogin(): bool // Needs to at least meet our minium version. if (version_compare(Maintenance::PHP_MIN_VERSION, PHP_VERSION, '>=')) { - Maintenance::$fatal_error = Lang::$txt['error_php_too_low']; + Maintenance::$fatal_error = Lang::getTxt('error_php_too_low', file: 'Maintenance'); return false; } // Form submitted, but no javascript support. if (isset($_POST['contbutt']) && !isset($_POST['js_support'])) { - Maintenance::$fatal_error = Lang::$txt['error_no_javascript']; + Maintenance::$fatal_error = Lang::getTxt('error_no_javascript', file: 'Maintenance'); return false; } @@ -529,7 +529,7 @@ public function welcomeLogin(): bool if (!$check) { // Don't tell them what files exactly because it's a spot check - just like teachers don't tell which problems they are spot checking, that's dumb. - Maintenance::$fatal_error = Lang::$txt['error_upgrade_files_missing']; + Maintenance::$fatal_error = Lang::getTxt('error_upgrade_files_missing', file: 'Maintenance'); return false; } @@ -570,7 +570,7 @@ public function welcomeLogin(): bool preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match); if (empty($match[1]) || (trim($match[1]) != SMF_VERSION)) { - Maintenance::$fatal_error = Lang::$txt['error_upgrade_old_files']; + Maintenance::$fatal_error = Lang::getTxt('error_upgrade_old_files', file: 'Maintenance'); return false; } @@ -616,7 +616,7 @@ public function welcomeLogin(): bool } if (!file_exists($cache_dir_temp)) { - Maintenance::$fatal_error = Lang::$txt['error_cache_not_found']; + Maintenance::$fatal_error = Lang::getTxt('error_cache_not_found', file: 'Maintenance'); return false; } @@ -650,42 +650,42 @@ public function welcomeLogin(): bool ) && !is_writable(Config::$languagesdir . '/' . $this->default_language . '/agreement.txt') ) { - Maintenance::$fatal_error = Lang::$txt['error_agreement_not_writable']; + Maintenance::$fatal_error = Lang::getTxt('error_agreement_not_writable', file: 'Maintenance'); return false; } // Confirm mbstring is loaded... if (!extension_loaded('mbstring')) { - Maintenance::$errors[] = Lang::$txt['install_no_mbstring']; + Maintenance::$errors[] = Lang::getTxt('install_no_mbstring', file: 'Maintenance'); } // Confirm fileinfo is loaded... if (!extension_loaded('fileinfo')) { - Maintenance::$errors[] = Lang::$txt['install_no_fileinfo']; + Maintenance::$errors[] = Lang::getTxt('install_no_fileinfo', file: 'Maintenance'); } // Check for https stream support. $supported_streams = stream_get_wrappers(); if (!in_array('https', $supported_streams)) { - Maintenance::$warnings[] = Lang::$txt['install_no_https']; + Maintenance::$warnings[] = Lang::getTxt('install_no_https', file: 'Maintenance'); } // First, check the avatar directory... // Note it wasn't specified in YabbSE, but there was no smfVersion either. if (!empty(Config::$modSettings['smfVersion']) && !is_dir(Config::$modSettings['avatar_directory'])) { - Maintenance::$warnings[] = Lang::$txt['warning_av_missing']; + Maintenance::$warnings[] = Lang::getTxt('warning_av_missing', file: 'Maintenance'); } // Next, check the custom avatar directory... Note this is optional in 2.0. if (!empty(Config::$modSettings['custom_avatar_dir']) && !is_dir(Config::$modSettings['custom_avatar_dir'])) { - Maintenance::$warnings[] = Lang::$txt['warning_custom_av_missing']; + Maintenance::$warnings[] = Lang::getTxt('warning_custom_av_missing', file: 'Maintenance'); } // Ensure we have a valid attachment directory. if ($this->attachmentDirectoryIsValid()) { - Maintenance::$warnings[] = Lang::$txt['warning_att_dir_missing']; + Maintenance::$warnings[] = Lang::getTxt('warning_att_dir_missing', file: 'Maintenance'); } if (Sapi::isCLI()) { @@ -705,7 +705,7 @@ public function welcomeLogin(): bool ) ) { if (!SecurityToken::validate('login', 'post', false)) { - Maintenance::$errors[] = Lang::$txt['token_verify_fail']; + Maintenance::$errors[] = Lang::getTxt('token_verify_fail', file: 'Maintenance'); Maintenance::$context += SecurityToken::create('login'); return false; @@ -843,8 +843,8 @@ public function upgradeOptions(): bool $file_settings['mtitle'] = $_POST['maintitle']; $file_settings['mmessage'] = $_POST['mainmessage']; } else { - $file_settings['mtitle'] = Lang::$txt['mtitle']; - $file_settings['mmessage'] = Lang::$txt['mmessage']; + $file_settings['mtitle'] = Lang::getTxt('mtitle', file: 'Maintenance'); + $file_settings['mmessage'] = Lang::getTxt('mmessage', file: 'Maintenance'); } } From d0c8f2dbb9bea934fc373f66575bfb966a6b8e5c Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 16:30:53 -0600 Subject: [PATCH 27/90] Adds some methods to Maintenance\Tools\ToolsInterface Signed-off-by: Jon Stovell --- Sources/Maintenance/Maintenance.php | 3 + Sources/Maintenance/Tools/Install.php | 24 ++++---- Sources/Maintenance/Tools/ToolsBase.php | 30 ++++++++++ Sources/Maintenance/Tools/ToolsInterface.php | 58 ++++++++++++++++++-- Sources/Maintenance/Tools/Upgrade.php | 27 ++++++--- 5 files changed, 114 insertions(+), 28 deletions(-) diff --git a/Sources/Maintenance/Maintenance.php b/Sources/Maintenance/Maintenance.php index d67bcdac103..29895aec2f4 100644 --- a/Sources/Maintenance/Maintenance.php +++ b/Sources/Maintenance/Maintenance.php @@ -272,6 +272,9 @@ public function execute(int $type): void continue; } + // Inform the tool about which step we are performing. + self::$tool->setStep($step); + // The current weight of this step in terms of overall progress. self::$context['step_weight'] = $step->getProgress(); diff --git a/Sources/Maintenance/Tools/Install.php b/Sources/Maintenance/Tools/Install.php index d018d2f1c29..fe28f996911 100644 --- a/Sources/Maintenance/Tools/Install.php +++ b/Sources/Maintenance/Tools/Install.php @@ -45,14 +45,18 @@ class Install extends ToolsBase implements ToolsInterface /** * @var bool * - * When true, we can continue, when false the continue button is removed. + * Whether we can continue. + * + * When false the continue button is removed. */ public bool $continue = true; /** * @var bool * - * When true, we can skip this step, otherwise false and no skip option. + * Whether we can skip the current step. + * + * If false, no skip option will be shown. */ public bool $skip = false; @@ -127,9 +131,7 @@ public function __construct() } /** - * Get the script name * - * @return string Page Title */ public function getScriptName(): string { @@ -142,19 +144,17 @@ public function getScriptName(): string * Selection is in the following order: * 1. A custom page title. * 2. Step has provided a title. - * 3. Default for the installer tool. + * 3. The value of $this->getScriptName(). * - * @return string Page Title + * @return string The title for the page. */ public function getPageTitle(): string { - return $this->page_title ?? $this->getSteps()[Maintenance::getCurrentStep()]->getTitle() ?? $this->getScriptName(); + return $this->page_title ?? $this->getStep()->getTitle() ?? $this->getScriptName(); } /** - * If a tool does not contain steps, this should be false, true otherwise. * - * @return bool Whether or not a tool has steps. */ public function hasSteps(): bool { @@ -162,9 +162,7 @@ public function hasSteps(): bool } /** - * Installer Steps * - * @return \SMF\Maintenance\Step[] */ public function getSteps(): array { @@ -227,13 +225,11 @@ function: 'finalize', } /** - * Gets the title for the step we are performing * - * @return string */ public function getStepTitle(): string { - return $this->getSteps()[Maintenance::getCurrentStep()]->getName(); + return $this->getStep()->getName(); } /** diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php index 6125001e5b2..4ad750d8db8 100644 --- a/Sources/Maintenance/Tools/ToolsBase.php +++ b/Sources/Maintenance/Tools/ToolsBase.php @@ -19,6 +19,7 @@ use SMF\Db\DatabaseApi as Db; use SMF\Lang; use SMF\Maintenance\Maintenance; +use SMF\Maintenance\Step; use SMF\PackageManager\FtpConnection; use SMF\Sapi; use SMF\SecurityToken; @@ -50,6 +51,15 @@ abstract class ToolsBase * Internal properties *********************/ + /** + * @var ?Step + * + * Which step is currently being performed. + * + * This is set by $this->setStep() and retrieved by $this->getStep(). + */ + private ?Step $current_step; + /** * @var FtpConnection * @@ -61,6 +71,26 @@ abstract class ToolsBase * Public methods ****************/ + /** + * Sets $this->current_step. + * + * @return ?Step The current step or null if no step is being performed. + */ + public function setStep(?Step $step = null): void + { + $this->current_step = $step; + } + + /** + * Gets $this->current_step. + * + * @return ?Step The value of $this->current_step. + */ + public function getStep(): ?Step + { + return $this->current_step ?? null; + } + /** * Find all databases that are supported on this system. * diff --git a/Sources/Maintenance/Tools/ToolsInterface.php b/Sources/Maintenance/Tools/ToolsInterface.php index f0551b0ac05..fafd6021ad0 100644 --- a/Sources/Maintenance/Tools/ToolsInterface.php +++ b/Sources/Maintenance/Tools/ToolsInterface.php @@ -15,6 +15,8 @@ namespace SMF\Maintenance\Tools; +use SMF\Maintenance\Step; + /** * Tools Interface, all tools have these methods. */ @@ -25,9 +27,11 @@ interface ToolsInterface ****************/ /** - * Get the script name + * Get the localized script name. + * + * Example: "SMF Upgrade Utility" * - * @return string Page Title + * @return string Localized name of the script. */ public function getScriptName(): string; @@ -36,7 +40,7 @@ public function getScriptName(): string; * * The tool may override and just change. * - * @return string + * @return string The title for the page. */ public function getPageTitle(): string; @@ -57,9 +61,51 @@ public function hasSteps(): bool; public function getSteps(): array; /** - * Gets the title for the step we are performing + * Sets $this->current_step. + * + * Used to keep track of which step is being performed. + * + * @return ?Step The current step or null if no step is being performed. + */ + public function setStep(?Step $step = null): void; + + /** + * Gets $this->current_step. + * + * Used to keep track of which step is being performed. + * + * @return ?Step The value of $this->current_step. + */ + public function getStep(): ?Step; + + /** + * Gets the title for the step we are performing. + * + * @return ?string + */ + public function getStepTitle(): ?string; + + /** + * Used by various places to determine if the tool is in debug mode or not. + * + * @return bool + */ + public function isDebug(): bool; + + /** + * Last chance to do anything before we exit. + * + * Some tools may call this to save their progress, etc. + */ + public function preExit(): void; + + /** + * Delete the tool. + * + * This is typically called with a ?delete. * - * @return string + * No output is returned. Upon successful deletion, the browser is + * redirected to a blank file. */ - public function getStepTitle(): string; + public function deleteTool(): void; } diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index acb76164cab..43d83dc0800 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -178,14 +178,18 @@ class Upgrade extends ToolsBase implements ToolsInterface /** * @var bool * - * When true, we can continue, when false the continue button is removed. + * Whether we can continue. + * + * When false the continue button is removed. */ public bool $continue = true; /** * @var bool * - * When true, we can skip this step, otherwise false and no skip option. + * Whether we can skip the current step. + * + * If false, no skip option will be shown. */ public bool $skip = false; @@ -301,6 +305,15 @@ class Upgrade extends ToolsBase implements ToolsInterface */ private bool $is_large_forum = false; + /** + * @var ?Step + * + * Which step is currently being performed. + * + * This is set by $this->setStep() and retrieved by $this->getStep(). + */ + private ?Step $current_step; + /**************** * Public methods ****************/ @@ -378,9 +391,7 @@ public function __construct() } /** - * Get the script name * - * @return string Page Title */ public function getScriptName(): string { @@ -393,13 +404,13 @@ public function getScriptName(): string * Selection is in the following order: * 1. A custom page title. * 2. Step has provided a title. - * 3. Default for the installer tool. + * 3. The value of $this->getScriptName(). * - * @return string Page Title + * @return string The title for the page. */ public function getPageTitle(): string { - return $this->page_title ?? $this->getSteps()[Maintenance::getCurrentStep()]->getTitle() ?? $this->getScriptName(); + return $this->page_title ?? $this->getStep()->getTitle() ?? $this->getScriptName(); } /** @@ -479,7 +490,7 @@ function: 'finalize', */ public function getStepTitle(): string { - return $this->getSteps()[Maintenance::getCurrentStep()]->getName(); + return $this->getStep()->getName(); } /** From 3daf9060b0e636d8c9495ac33861cef2da3635bc Mon Sep 17 00:00:00 2001 From: Jon Stovell Date: Tue, 27 May 2025 16:55:04 -0600 Subject: [PATCH 28/90] Code deduplication Signed-off-by: Jon Stovell --- Sources/Maintenance/Tools/Install.php | 207 ++----------------- Sources/Maintenance/Tools/ToolsBase.php | 105 ++++------ Sources/Maintenance/Tools/ToolsInterface.php | 9 +- Sources/Maintenance/Tools/Upgrade.php | 8 +- Themes/default/InstallTemplate.php | 6 +- 5 files changed, 78 insertions(+), 257 deletions(-) diff --git a/Sources/Maintenance/Tools/Install.php b/Sources/Maintenance/Tools/Install.php index fe28f996911..f59a38194d2 100644 --- a/Sources/Maintenance/Tools/Install.php +++ b/Sources/Maintenance/Tools/Install.php @@ -23,7 +23,6 @@ use SMF\Logging; use SMF\Maintenance\Maintenance; use SMF\Maintenance\Step; -use SMF\PackageManager\FtpConnection; use SMF\Sapi; use SMF\Security; use SMF\TaskRunner; @@ -63,9 +62,11 @@ class Install extends ToolsBase implements ToolsInterface /** * @var string * - * The name of the script this tool uses. This is used by various actions and links. + * The name of the script this tool uses. + * + * This is used by various actions and links. */ - public string $script_name = 'install.php'; + public string $script_file = 'install.php'; /********************* * Internal properties @@ -316,199 +317,29 @@ public function welcome(): bool public function checkFilesWritable(): bool { $writable_files = [ - 'attachments', - 'avatars', - 'custom_avatar', - 'cache', - 'Packages', - 'Smileys', - 'Themes', - 'Languages/en_US/agreement.txt', - 'Settings.php', - 'Settings_bak.php', - 'cache/db_last_error.php', + Config::$boarddir . '/attachments', + Config::$boarddir . '/avatars', + Config::$boarddir . '/custom_avatar', + Config::$boarddir . '/cache', + Config::$boarddir . '/Packages', + Config::$boarddir . '/Smileys', + Config::$boarddir . '/Themes', + Config::$boarddir . '/Languages/en_US/agreement.txt', + Config::$boarddir . '/Settings.php', + Config::$boarddir . '/Settings_bak.php', + Config::$boarddir . '/cache/db_last_error.php', ]; foreach ($this->detectLanguages() as $lang => $temp) { - $extra_files[] = 'Languages/' . $lang; + $writable_files[] = Config::$boarddir . '/Languages/' . $lang; } // With mod_security installed, we could attempt to fix it with .htaccess. if (function_exists('apache_get_modules') && in_array('mod_security', apache_get_modules())) { - $writable_files[] = file_exists(Config::$boarddir . '/.htaccess') ? '.htaccess' : '.'; - } - - $failed_files = []; - - // Windows is trickier. Let's try opening for r+... - if (Sapi::isOS(Sapi::OS_WINDOWS)) { - foreach ($writable_files as $file) { - // Folders can't be opened for write... but the index.php in them can ;) - if (is_dir(Config::$boarddir . '/' . $file)) { - $file .= '/index.php'; - } - - // Funny enough, chmod actually does do something on windows - it removes the read only attribute. - @chmod(Config::$boarddir . '/' . $file, 0777); - $fp = @fopen(Config::$boarddir . '/' . $file, 'r+'); - - // Hmm, okay, try just for write in that case... - if (!is_resource($fp)) { - $fp = @fopen(Config::$boarddir . '/' . $file, 'w'); - } - - if (!is_resource($fp)) { - $failed_files[] = $file; - } - - @fclose($fp); - } - - foreach ($extra_files as $file) { - @chmod(Config::$boarddir . (empty($file) ? '' : '/' . $file), 0777); - } - } else { - // On linux, it's easy - just use is_writable! - foreach ($writable_files as $file) { - // Some files won't exist, try to address up front - if (!file_exists(Config::$boarddir . '/' . $file)) { - @touch(Config::$boarddir . '/' . $file); - } - - // NOW do the writable check... - if (!is_writable(Config::$boarddir . '/' . $file)) { - @chmod(Config::$boarddir . '/' . $file, 0755); - - // Well, 755 hopefully worked... if not, try 777. - if (!is_writable(Config::$boarddir . '/' . $file) && !@chmod(Config::$boarddir . '/' . $file, 0777)) { - $failed_files[] = $file; - } - } - } - - foreach ($extra_files as $file) { - @chmod(Config::$boarddir . (empty($file) ? '' : '/' . $file), 0777); - } - } - - $failure = count($failed_files) >= 1; - - if (!isset($_SERVER)) { - return !$failure; - } - - // Put the list into context. - Maintenance::$context['failed_files'] = $failed_files; - - // It's not going to be possible to use FTP on windows to solve the problem... - if ($failure && Sapi::isOS(Sapi::OS_WINDOWS)) { - Maintenance::$fatal_error = Lang::getTxt('error_windows_chmod', file: 'Maintenance') . ' -
      -
    • ' . implode('
    • -
    • ', $failed_files) . '
    • -
    '; - - return false; - } - - // We're going to have to use... FTP! - if ($failure) { - // Load any session data we might have... - if (!isset($_POST['ftp']['username']) && isset($_SESSION['ftp'])) { - $_POST['ftp']['server'] = $_SESSION['ftp']['server']; - $_POST['ftp']['port'] = $_SESSION['ftp']['port']; - $_POST['ftp']['username'] = $_SESSION['ftp']['username']; - $_POST['ftp']['password'] = $_SESSION['ftp']['password']; - $_POST['ftp']['path'] = $_SESSION['ftp']['path']; - } - - Maintenance::$context['ftp_errors'] = []; - - if (isset($_POST['ftp_username'])) { - $ftp = new FtpConnection($_POST['ftp']['server'], $_POST['ftp']['port'], $_POST['ftp']['username'], $_POST['ftp']['password']); - - if ($ftp->error === false) { - // Try it without /home/abc just in case they messed up. - if (!$ftp->chdir($_POST['ftp']['path'])) { - Maintenance::$context['ftp_errors'][] = $ftp->last_message; - $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp']['path'])); - } - } - } - - if (!isset($ftp) || $ftp->error !== false) { - if (!isset($ftp)) { - $ftp = new FtpConnection(null); - } - // Save the error so we can mess with listing... - elseif ($ftp->error !== false && empty(Maintenance::$context['ftp_errors']) && !empty($ftp->last_message)) { - Maintenance::$context['ftp_errors'][] = $ftp->last_message; - } - - list($username, $detect_path, $found_path) = $ftp->detect_path(Config::$boarddir); - - if (empty($_POST['ftp']['path']) && $found_path) { - $_POST['ftp']['path'] = $detect_path; - } - - if (!isset($_POST['ftp']['username'])) { - $_POST['ftp']['username'] = $username; - } - - // Set the username etc, into context. - Maintenance::$context['ftp'] = [ - 'server' => $_POST['ftp']['server'] ?? 'localhost', - 'port' => $_POST['ftp']['port'] ?? '21', - 'username' => $_POST['ftp']['username'] ?? '', - 'path' => $_POST['ftp']['path'] ?? '/', - 'path_msg' => !empty($found_path) ? Lang::getTxt('ftp_path_found_info', file: 'Maintenance') : Lang::getTxt('ftp_path_info', file: 'Maintenance'), - ]; - - return false; - } - - $_SESSION['ftp'] = [ - 'server' => $_POST['ftp']['server'], - 'port' => $_POST['ftp']['port'], - 'username' => $_POST['ftp']['username'], - 'password' => $_POST['ftp']['password'], - 'path' => $_POST['ftp']['path'], - ]; - - $failed_files_updated = []; - - foreach ($failed_files as $file) { - if (!is_writable(Config::$boarddir . '/' . $file)) { - $ftp->chmod($file, 0755); - } - - if (!is_writable(Config::$boarddir . '/' . $file)) { - $ftp->chmod($file, 0777); - } - - if (!is_writable(Config::$boarddir . '/' . $file)) { - $failed_files_updated[] = $file; - Maintenance::$context['ftp_errors'][] = rtrim($ftp->last_message) . ' -> ' . $file . "\n"; - } - } - - $ftp->close(); - - // Are there any errors left? - if (count($failed_files_updated) >= 1) { - // Guess there are... - Maintenance::$context['failed_files'] = $failed_files_updated; - - // Set the username etc, into context. - Maintenance::$context['ftp'] = $_SESSION['ftp'] += [ - 'path_msg' => Lang::getTxt('ftp_path_info', file: 'Maintenance'), - ]; - - return false; - } + $writable_files[] = file_exists(Config::$boarddir . '/.htaccess') ? Config::$boarddir . '/.htaccess' : Config::$boarddir; } - return true; + return $this->makeFilesWritable($writable_files); } /** @@ -1396,7 +1227,7 @@ public function finalize(): bool // Some final context for the template. Maintenance::$context['dir_still_writable'] = is_writable(Config::$boarddir); - Maintenance::$context['probably_delete_install'] = isset($_SESSION['installer_temp_ftp']) || is_writable(Config::$boarddir) || is_writable(Config::$boarddir . '/' . $this->script_name); + Maintenance::$context['can_delete_script'] = $this->canDeleteTool(); // Update hash's cost to an appropriate setting Config::updateModSettings([ diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php index 4ad750d8db8..01862aebc82 100644 --- a/Sources/Maintenance/Tools/ToolsBase.php +++ b/Sources/Maintenance/Tools/ToolsBase.php @@ -23,6 +23,7 @@ use SMF\PackageManager\FtpConnection; use SMF\Sapi; use SMF\SecurityToken; +use SMF\Utils; /** * Base class for all our tools. Includes commonly needed logic among all tools. @@ -38,7 +39,7 @@ abstract class ToolsBase * * Script name of the tool we are running. */ - public string $script_name; + public string $script_file; /** * @var bool @@ -163,6 +164,24 @@ public function isDebug(): bool return $this->debug ?? false; } + /** + * Checks whether we can the tool's script file. + * + * @return bool + */ + public function canDeleteTool(): bool + { + return ( + !empty($this->script_file) + && file_exists(Config::$boarddir . '/' . $this->script_file) + && ( + !empty($_SESSION['ftp']) + || is_writable(Config::$boarddir) + || is_writable(Config::$boarddir . '/' . $this->script_file) + ) + ); + } + /** * Delete the tool. * @@ -173,19 +192,16 @@ public function isDebug(): bool */ public function deleteTool(): void { - if ( - !empty($this->script_name) - && file_exists(Config::$boarddir . '/' . $this->script_name) - ) { + if ($this->canDeleteTool()) { if (!empty($_SESSION['ftp'])) { $ftp = new FtpConnection($_SESSION['ftp']['server'], $_SESSION['ftp']['port'], $_SESSION['ftp']['username'], $_SESSION['ftp']['password']); $ftp->chdir($_SESSION['ftp']['path']); - $ftp->unlink($this->script_name); + $ftp->unlink($this->script_file); $ftp->close(); unset($_SESSION['ftp']); } else { - @unlink(Config::$boarddir . '/' . $this->script_name); + @unlink(Config::$boarddir . '/' . $this->script_file); } // Now just redirect to a blank.png... @@ -218,78 +234,43 @@ final public function makeFilesWritable(array &$files): bool return true; } - $failure = false; - - // On linux, it's easy - just use is_writable! - // Windows is trickier. Let's try opening for r+... - if (Sapi::isOS(Sapi::OS_WINDOWS)) { - foreach ($files as $k => $file) { - // Folders can't be opened for write... but the index.php in them can ;). - if (is_dir($file)) { - $file .= '/index.php'; - } - - // Funny enough, chmod actually does do something on windows - it removes the read only attribute. - @chmod($file, 0777); - $fp = @fopen($file, 'r+'); - - // Hmm, okay, try just for write in that case... - if (!$fp) { - $fp = @fopen($file, 'w'); - } - - if (!$fp) { - $failure = true; - } else { - unset($files[$k]); - } - @fclose($fp); + foreach ($files as $k => $file) { + // Folders can't be opened for write on Windows... but the index.php in them can ;) + if (Sapi::isOS(Sapi::OS_WINDOWS) && is_dir($file)) { + $file .= '/index.php'; } - } else { - foreach ($files as $k => $file) { - // Some files won't exist, try to address up front - if (!file_exists($file)) { - @touch($file); - } - // NOW do the writable check... - if (!is_writable($file)) { - @chmod($file, 0755); - - // Well, 755 hopefully worked... if not, try 777. - if (!is_writable($file) && !@chmod($file, 0777)) { - $failure = true; - } - // Otherwise remove it as it's good! - else { - unset($files[$k]); - } - } else { - unset($files[$k]); - } + // Some files won't exist, try to address up front + if (!file_exists($file)) { + @touch($file); } - } - if (empty($files)) { - return true; + // NOW do the writable check... + if (Utils::makeWritable($file)) { + unset($files[$k]); + } } - if (!isset($_SERVER)) { - return !$failure; + if (Sapi::isCLI()) { + return empty($files); } // What still needs to be done? Maintenance::$context['chmod_files'] = $files; // If it's windows it's a mess... - if ($failure && Sapi::isOS(Sapi::OS_WINDOWS)) { - Maintenance::$context['chmod']['ftp_error'] = 'total_mess'; + if (!empty($files) && Sapi::isOS(Sapi::OS_WINDOWS)) { + Maintenance::$fatal_error = Lang::getTxt('error_windows_chmod', file: 'Maintenance') . ' +
      +
    • ' . implode('
    • +
    • ', $files) . '
    • +
    '; return false; } // We're going to have to use... FTP! - if ($failure) { + if (!empty($files)) { // Load any session data we might have... if (!isset($_POST['ftp_username']) && isset($_SESSION['temp_ftp'])) { Maintenance::$context['chmod']['server'] = $_SESSION['temp_ftp']['server']; diff --git a/Sources/Maintenance/Tools/ToolsInterface.php b/Sources/Maintenance/Tools/ToolsInterface.php index fafd6021ad0..4be82e5de9d 100644 --- a/Sources/Maintenance/Tools/ToolsInterface.php +++ b/Sources/Maintenance/Tools/ToolsInterface.php @@ -100,7 +100,14 @@ public function isDebug(): bool; public function preExit(): void; /** - * Delete the tool. + * Checks whether we can the tool's script file. + * + * @return bool + */ + public function canDeleteTool(): bool; + + /** + * Delete the tool's script file. * * This is typically called with a ?delete. * diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index 43d83dc0800..3dd3fae7a41 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -196,9 +196,11 @@ class Upgrade extends ToolsBase implements ToolsInterface /** * @var string * - * The name of the script this tool uses. This is used by various actions and links. + * The name of the script this tool uses. + * + * This is used by various actions and links. */ - public string $script_name = 'upgrade.php'; + public string $script_file = 'upgrade.php'; /** * @var int @@ -1213,7 +1215,7 @@ public function finalize(): bool } // Can we delete the file? - Maintenance::$context['can_delete_script'] = is_writable(Config::$boarddir) || is_writable(Config::$boarddir . '/' . $this->script_name); + Maintenance::$context['can_delete_script'] = $this->canDeleteTool(); // Show Upgrade time in debug mode when we completed the upgrade process totally if ($this->debug) { diff --git a/Themes/default/InstallTemplate.php b/Themes/default/InstallTemplate.php index e6f16a2e2c2..de421d5eb25 100644 --- a/Themes/default/InstallTemplate.php +++ b/Themes/default/InstallTemplate.php @@ -123,14 +123,14 @@ public static function checkFilesWritable(): void

    ', Lang::$txt['ftp_setup_why_info'], '

    • ', implode('
    • -
    • ', Maintenance::$context['failed_files']), '
    • +
    • ', Maintenance::$context['chmod_files']), '
    '; if (isset(Maintenance::$context['systemos'], Maintenance::$context['detected_path']) && Maintenance::$context['systemos'] == 'linux') { echo '

    ', Lang::$txt['chmod_linux_info'], '

    - # chmod a+w ', implode(' ' . Maintenance::$context['detected_path'] . '/', Maintenance::$context['failed_files']), ''; + # chmod a+w ', implode(' ' . Maintenance::$context['detected_path'] . '/', Maintenance::$context['chmod_files']), ''; } // This is serious! @@ -479,7 +479,7 @@ public static function finalize(): void } // Don't show the box if it's like 99% sure it won't work :P. - if (Maintenance::$context['probably_delete_install']) { + if (Maintenance::$context['can_delete_script']) { echo '
'; + $this->logProgress(Lang::getTxt('error_windows_chmod', file: 'Maintenance') . "\n\t" . implode("\n\t", $files)); + return false; } @@ -350,6 +402,8 @@ final public function makeFilesWritable(array &$files): bool ]; foreach ($files as $k => $file) { + $this->logProgress(Lang::getTxt('log_ensuring_file_writable_ftp', ['file' => $file], file: 'Maintenance'), true); + if (!is_writable($file)) { $ftp->chmod($file, 0755); } @@ -382,6 +436,9 @@ final public function makeFilesWritable(array &$files): bool if (is_writable($file)) { unset($files[$k]); + $this->logProgress(Lang::getTxt('done', file: 'Maintenance')); + } else { + $this->logProgress(Lang::getTxt('failed', file: 'Maintenance')); } } diff --git a/Sources/Maintenance/Tools/ToolsInterface.php b/Sources/Maintenance/Tools/ToolsInterface.php index 4be82e5de9d..6588f1fa72b 100644 --- a/Sources/Maintenance/Tools/ToolsInterface.php +++ b/Sources/Maintenance/Tools/ToolsInterface.php @@ -92,6 +92,14 @@ public function getStepTitle(): ?string; */ public function isDebug(): bool; + /** + * Updates the tool's log file with new info. + * + * @param mixed $message The message to append to the log. + * If not a string, will be converted into one using print_r(). + */ + public function logProgress(mixed $message): void; + /** * Last chance to do anything before we exit. * diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php index 3dd3fae7a41..1f18d823494 100644 --- a/Sources/Maintenance/Tools/Upgrade.php +++ b/Sources/Maintenance/Tools/Upgrade.php @@ -20,15 +20,16 @@ use SMF\Db\DatabaseApi as Db; use SMF\Lang; use SMF\Maintenance\Cleanup; +use SMF\Maintenance\GenericSubStep; use SMF\Maintenance\Maintenance; use SMF\Maintenance\Migration; use SMF\Maintenance\Step; -use SMF\Maintenance\Template\Template; use SMF\Maintenance\Utf8ConverterStep; use SMF\QueryString; use SMF\Sapi; use SMF\SecurityToken; use SMF\Session; +use SMF\Themes\default\MaintenanceTemplate; use SMF\Time; use SMF\User; use SMF\Utils; @@ -329,7 +330,7 @@ public function __construct() if (empty(Maintenance::$languages)) { if (!Sapi::isCLI()) { - Template::missingLanguages(); + MaintenanceTemplate::missingLanguages(); } throw new \Exception('This script was unable to find this tools\'s language file or files.'); @@ -464,7 +465,7 @@ function: 'migrations', new Utf8ConverterStep( // Note: Utf8ConverterStep does not take a function argument. id: 5, - name: Lang::getTxt('upgrade_step_convertutf', file: 'Maintenance'), + name: Lang::getTxt('upgrade_step_convertutf8', file: 'Maintenance'), template: 'convertUtf8', progress: 30, ), @@ -502,6 +503,10 @@ public function getStepTitle(): string */ public function welcomeLogin(): bool { + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + if (!empty($_SESSION['is_logged'])) { return true; } @@ -509,6 +514,7 @@ public function welcomeLogin(): bool // Needs to at least meet our minium version. if (version_compare(Maintenance::PHP_MIN_VERSION, PHP_VERSION, '>=')) { Maintenance::$fatal_error = Lang::getTxt('error_php_too_low', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -516,6 +522,7 @@ public function welcomeLogin(): bool // Form submitted, but no javascript support. if (isset($_POST['contbutt']) && !isset($_POST['js_support'])) { Maintenance::$fatal_error = Lang::getTxt('error_no_javascript', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -543,6 +550,7 @@ public function welcomeLogin(): bool if (!$check) { // Don't tell them what files exactly because it's a spot check - just like teachers don't tell which problems they are spot checking, that's dumb. Maintenance::$fatal_error = Lang::getTxt('error_upgrade_files_missing', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -557,6 +565,7 @@ public function welcomeLogin(): bool ) ) { Maintenance::$fatal_error = Lang::getTxt('error_db_too_low', ['name' => Db::$db->getTitle()]); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -574,6 +583,7 @@ public function welcomeLogin(): bool // Sorry... we need CREATE, ALTER and DROP if (!$create || !$alter || !$drop) { Maintenance::$fatal_error = Lang::getTxt('error_db_privileges', ['name' => Config::$db_type]); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -584,6 +594,7 @@ public function welcomeLogin(): bool if (empty($match[1]) || (trim($match[1]) != SMF_VERSION)) { Maintenance::$fatal_error = Lang::getTxt('error_upgrade_old_files', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -612,6 +623,7 @@ public function welcomeLogin(): bool // Are we good now? if (!is_writable($custom_av_dir)) { Maintenance::$fatal_error = Lang::getTxt('error_dir_not_writable', ['dir' => $custom_av_dir]); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -630,6 +642,7 @@ public function welcomeLogin(): bool if (!file_exists($cache_dir_temp)) { Maintenance::$fatal_error = Lang::getTxt('error_cache_not_found', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -638,6 +651,7 @@ public function welcomeLogin(): bool if (!is_writable($cache_dir_temp . '/db_last_error.php')) { Maintenance::$fatal_error = Lang::getTxt('error_dir_not_writable', ['dir' => $cache_dir_temp]); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -664,6 +678,7 @@ public function welcomeLogin(): bool && !is_writable(Config::$languagesdir . '/' . $this->default_language . '/agreement.txt') ) { Maintenance::$fatal_error = Lang::getTxt('error_agreement_not_writable', file: 'Maintenance'); + $this->logProgress(Maintenance::$fatal_error); return false; } @@ -671,11 +686,13 @@ public function welcomeLogin(): bool // Confirm mbstring is loaded... if (!extension_loaded('mbstring')) { Maintenance::$errors[] = Lang::getTxt('install_no_mbstring', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('install_no_mbstring', file: 'Maintenance')); } // Confirm fileinfo is loaded... if (!extension_loaded('fileinfo')) { Maintenance::$errors[] = Lang::getTxt('install_no_fileinfo', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('install_no_fileinfo', file: 'Maintenance')); } // Check for https stream support. @@ -683,22 +700,26 @@ public function welcomeLogin(): bool if (!in_array('https', $supported_streams)) { Maintenance::$warnings[] = Lang::getTxt('install_no_https', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('install_no_https', file: 'Maintenance')); } // First, check the avatar directory... // Note it wasn't specified in YabbSE, but there was no smfVersion either. if (!empty(Config::$modSettings['smfVersion']) && !is_dir(Config::$modSettings['avatar_directory'])) { Maintenance::$warnings[] = Lang::getTxt('warning_av_missing', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('warning_av_missing', file: 'Maintenance')); } // Next, check the custom avatar directory... Note this is optional in 2.0. if (!empty(Config::$modSettings['custom_avatar_dir']) && !is_dir(Config::$modSettings['custom_avatar_dir'])) { Maintenance::$warnings[] = Lang::getTxt('warning_custom_av_missing', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('warning_custom_av_missing', file: 'Maintenance')); } // Ensure we have a valid attachment directory. if ($this->attachmentDirectoryIsValid()) { Maintenance::$warnings[] = Lang::getTxt('warning_att_dir_missing', file: 'Maintenance'); + $this->logProgress(Lang::getTxt('warning_att_dir_missing', file: 'Maintenance')); } if (Sapi::isCLI()) { @@ -804,6 +825,10 @@ public function upgradeOptions(): bool return false; } + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + Db::load(); Db::$db->setSqlMode('strict'); @@ -982,44 +1007,30 @@ public function backupDatabase(): bool Maintenance::$total_substeps = count($table_names); // Template things. - Maintenance::$context['table_count'] = Maintenance::$total_substeps; - Maintenance::$context['cur_table_num'] = Maintenance::getCurrentSubStep(); - Maintenance::$context['cur_table_name'] = str_replace(Config::$db_prefix, '', $table_names[Maintenance::getCurrentSubStep()]); + Maintenance::$context['cur_table_name'] = $table_names[Maintenance::getCurrentSubStep()]; Maintenance::$context['continue'] = true; - if (Sapi::isCLI()) { - echo 'Backing Up Tables.'; - } - // We are set up for backing up. if (!Sapi::isCLI() && !Maintenance::isJson()) { return false; } - // Back up each table! - while (Maintenance::getCurrentSubStep() < Maintenance::$total_substeps) { - $current_table = $table_names[Maintenance::getCurrentSubStep()]; - $this->doBackupTable($current_table); + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } - // Increase our current substep by 1. - Maintenance::setCurrentSubStep(); + // Back up each table! + $substeps = []; - // If this is JSON to keep it nice for the user do one table at a time anyway! - if (Maintenance::isJson()) { - Maintenance::jsonResponse( - [ - 'current_table_name' => str_replace(Config::$db_prefix, '', $current_table), - 'current_table_index' => Maintenance::getCurrentSubStep(), - 'substep_progress' => Maintenance::getSubStepProgress(), - ], - ); - } + foreach ($table_names as $table_name) { + $substeps[] = new GenericSubStep( + name: Lang::getTxt('log_table_backup', ['table' => $table_name], file: 'Maintenance'), + exec: [$this, 'doBackupTable'], + exec_args: [$table_name], + ); } - if (Sapi::isCLI()) { - echo "\n" . ' Successful.\'' . "\n"; - flush(); - } + $this->performSubsteps($substeps); // Make sure we move on! return true; @@ -1051,7 +1062,9 @@ public function migrations(): bool continue; } - $substeps = array_merge($substeps, self::MIGRATIONS[$ns]); + foreach (self::MIGRATIONS[$ns] as $class) { + $substeps[] = new $class(); + } } $this->performSubsteps($substeps); @@ -1085,7 +1098,9 @@ public function cleanup(): bool continue; } - $substeps = array_merge($substeps, self::CLEANUPS[$ns]); + foreach (self::CLEANUPS[$ns] as $class) { + $substeps[] = new $class(); + } } $this->performSubsteps($substeps); @@ -1101,6 +1116,10 @@ public function cleanup(): bool */ public function finalize(): bool { + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + Maintenance::$context['form_action'] = Config::$boardurl . '/index.php'; // Update the database with the new SMF version. @@ -1206,39 +1225,38 @@ public function finalize(): bool $this->updateSettingsFile($file_settings); // We're done! - if (Sapi::isCLI()) { - echo "\n"; - echo 'Upgrade Complete!', "\n"; - echo 'Please delete this file as soon as possible for security reasons.', "\n"; + $this->logProgress(Lang::getTxt('log_upgrade_complete', file: 'Maintenance')); + Maintenance::$overall_percent = 100; + Maintenance::setCurrentSubStep(0); - exit; - } + // Wipe this out... + $this->user = []; - // Can we delete the file? - Maintenance::$context['can_delete_script'] = $this->canDeleteTool(); + if (!Sapi::isCLI()) { + // Can we delete the file? + Maintenance::$context['can_delete_script'] = $this->canDeleteTool(); - // Show Upgrade time in debug mode when we completed the upgrade process totally - if ($this->debug) { - $active = time() - (int) $this->time_started; + // Show Upgrade time in debug mode when we completed the upgrade process totally + if ($this->isDebug()) { + $active = time() - (int) $this->time_started; - Maintenance::$context['upgrade_completed_time'] = Lang::getTxt( - $active >= 3600 ? 'upgrade_completed_time_hms' : ($active >= 60 ? 'upgrade_completed_time_ms' : 'upgrade_completed_time_s'), - [ - 'h' => (int) ($active / 3600), - 'm' => (int) ((int) ($active / 60) % 60), - 's' => (int) ($active % 60), - ], - file: 'Maintenance', - ); - } - - // Make sure it says we're done. - Maintenance::$overall_percent = 100; + Maintenance::$context['upgrade_completed_time'] = Lang::getTxt( + $active >= 3600 ? 'upgrade_completed_time_hms' : ($active >= 60 ? 'upgrade_completed_time_ms' : 'upgrade_completed_time_s'), + [ + 'h' => (int) ($active / 3600), + 'm' => (int) ((int) ($active / 60) % 60), + 's' => (int) ($active % 60), + ], + file: 'Maintenance', + ); - // Wipe this out... - $this->user = []; + Maintenance::$context['log_contents'] = file_get_contents($this->log_file); + } + } - Maintenance::setCurrentSubStep(0); + if (isset($this->log_file)) { + @unlink($this->log_file); + } return Sapi::isCLI(); } @@ -1283,24 +1301,12 @@ public function backupRecommended(): bool /** * Actually backup a table. * - * @param mixed $table_name Name of the table to be backed up - * @return bool True if succesfull, false otherwise. + * @param mixed $table_name Name of the table to be backed up. + * @return bool True if successful, false otherwise. */ public function doBackupTable($table): bool { - if (Sapi::isCLI()) { - echo "\n" . ' +++ Backing up \"' . str_replace(Config::$db_prefix, '', $table) . '"...'; - flush(); - } - - // @@TODO: Check result? Should be a object, false if it failed. - Db::$db->backup_table($table, 'backup_' . $table); - - if (Sapi::isCLI()) { - echo ' done.'; - } - - return true; + return Db::$db->backup_table($table, 'backup_' . $table); } /****************** @@ -1401,11 +1407,13 @@ private function saveUpgradeData(): bool private function updateSettingsFile(array $config_vars, ?bool $keep_quotes = null, bool $rebuild = false): bool { if (!Config::updateSettingsFile($config_vars, $keep_quotes, $rebuild)) { + $this->logProgress(Lang::getTxt('settings_error', file: 'Maintenance')); + if (Sapi::isCLI()) { - die('FAILURE: Could not update ' . basename(SMF_SETTINGS_FILE) . "\n"); + die(); } - Maintenance::$fatal_error = Lang::getTxt('upgrade_writable_files', file: 'Maintenance') . ': ' . basename(SMF_SETTINGS_FILE); + Maintenance::$fatal_error = Lang::getTxt('settings_error', file: 'Maintenance'); return false; } @@ -1570,10 +1578,7 @@ private function getSubstepName(int $num, array $substeps): string return ''; } - $class = $substeps[$num]; - $obj = new $class(); - - return $obj->name; + return $substeps[$num]->name; } catch (\Throwable $e) { return ''; } @@ -1582,7 +1587,7 @@ private function getSubstepName(int $num, array $substeps): string /** * Performs a series of substeps. * - * @param array $substeps All substep classes that we are running. + * @param array $substeps All substep objects that we are running. */ private function performSubsteps(array $substeps): void { @@ -1620,24 +1625,27 @@ private function performSubsteps(array $substeps): void return; } + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + $this->logProgress(Lang::getTxt('log_starting_step', ['num' => $this->getStep()->getId(), 'step' => $this->getStep()->getName()])); + } + /* * When SKIP occurs, note it in JS and continue to next step. * When success occurs, ensure it moves to next stesp. * When error occurs, ensure we properly show the error. */ while (Maintenance::getCurrentSubStep() < Maintenance::$total_substeps) { - $substep_class = $substeps[Maintenance::getCurrentSubStep()]; - $substep = new $substep_class(); + $substep = $substeps[Maintenance::getCurrentSubStep()]; - if (Sapi::isCLI()) { - echo "\n" . ' +++ ' . $substep->name . '... '; - } + $this->logProgress(' +++ ' . $substep->name, true); // If this is not a canidate for us to execute, skip it. try { if (!$substep->isCandidate()) { Maintenance::setCurrentSubStep(); + $this->logProgress(Lang::getTxt('log_skipped', file: 'Maintenance')); + Maintenance::jsonResponse([ 'name' => $substep->name, 'next' => $this->getSubstepName(Maintenance::getCurrentSubStep(), $substeps), @@ -1646,17 +1654,15 @@ private function performSubsteps(array $substeps): void 'start' => Maintenance::getCurrentStart(), 'total' => Maintenance::$total_substeps, 'debug' => [ - 'call' => basename($substep_class), + 'call' => $substep::class, ], ]); - if (Sapi::isCLI()) { - echo 'skipped.'; - } - continue; } } catch (\Throwable $e) { + $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => $e->getMessage()], file: 'Maintenance')); + Maintenance::jsonResponse([ 'name' => $substep->name, 'failed' => true, @@ -1664,7 +1670,7 @@ private function performSubsteps(array $substeps): void 'start' => Maintenance::getCurrentStart(), 'total' => Maintenance::$total_substeps, 'debug' => [ - 'call' => basename($substep_class), + 'call' => $substep::class, 'msg' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), @@ -1674,11 +1680,26 @@ private function performSubsteps(array $substeps): void return; } - $res = false; - try { - $res = $substep->execute(); + if (!$substep->execute()) { + $this->logProgress(Lang::getTxt('log_failed', file: 'Maintenance')); + + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'completed' => false, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + ], + ]); + + return; + } } catch (\Throwable $e) { + $this->logProgress(Lang::getTxt('log_failed_with_error', ['error' => $e->getMessage()], file: 'Maintenance')); + Maintenance::jsonResponse([ 'name' => $substep->name, 'failed' => true, @@ -1686,43 +1707,17 @@ private function performSubsteps(array $substeps): void 'start' => Maintenance::getCurrentStart(), 'total' => Maintenance::$total_substeps, 'debug' => [ - 'call' => basename($substep_class), + 'call' => $substep::class, 'msg' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), ], ]); - if (Sapi::isCLI()) { - echo 'failed with error: "' . $e->getMessage() . '"' . "\n"; - } - - return; - } - - // If not ready yet, fail. - if (!$res) { - Maintenance::jsonResponse([ - 'name' => $substep->name, - 'completed' => false, - 'substep' => Maintenance::getCurrentSubStep(), - 'start' => Maintenance::getCurrentStart(), - 'total' => Maintenance::$total_substeps, - 'debug' => [ - 'call' => basename($substep_class), - ], - ]); - - if (Sapi::isCLI()) { - echo 'failed.'; - } - return; } - if (Sapi::isCLI()) { - echo 'done.'; - } + $this->logProgress(Lang::getTxt('log_done', file: 'Maintenance')); // Increase our current substep by 1. Maintenance::setCurrentSubStep(); @@ -1738,7 +1733,7 @@ private function performSubsteps(array $substeps): void 'start' => Maintenance::getCurrentStart(), 'total' => Maintenance::$total_substeps, 'debug' => [ - 'call' => basename($substep_class), + 'call' => $substep::class, ], ]); } diff --git a/Sources/Maintenance/Utf8ConverterStep.php b/Sources/Maintenance/Utf8ConverterStep.php index 69e623a6bb9..9ae5e4d33a5 100644 --- a/Sources/Maintenance/Utf8ConverterStep.php +++ b/Sources/Maintenance/Utf8ConverterStep.php @@ -539,7 +539,7 @@ public function getSubSteps(): array } $substeps[] = new GenericSubStep( - name: Lang::getTxt('converting_table_to_utf8mb4', [$table_name], file: 'Maintenance'), + name: Lang::getTxt('log_table_convertutf8', ['table' => $table_name], file: 'Maintenance'), test: [$this, 'isCandidateTable'], test_args: [$table_name], exec: [$this, 'convertTable'], @@ -630,6 +630,10 @@ public function convertDatabase(): bool return false; } + if (Maintenance::getCurrentSubStep() === 0 && Maintenance::getCurrentStart() === 0) { + Maintenance::$tool->logProgress(Lang::getTxt('log_starting_step', ['num' => Maintenance::$tool->getStep()->getId(), 'step' => Maintenance::$tool->getStep()->getName()])); + } + if (Maintenance::$total_substeps === 0) { if (Maintenance::isJson()) { Maintenance::jsonResponse([ @@ -650,34 +654,104 @@ public function convertDatabase(): bool while (Maintenance::getCurrentSubStep() < Maintenance::$total_substeps) { $substep = $substeps[Maintenance::getCurrentSubStep()]; - if (Sapi::isCLI()) { - echo "\n" . ' +++ ' . $substep->name . '... '; - } + Maintenance::$tool->logProgress(' +++ ' . $substep->name, true); + + try { + if (!$substep->isCandidate()) { + Maintenance::setCurrentSubStep(); - Maintenance::$context['cur_table_num'] = Maintenance::getCurrentSubStep(); - Maintenance::$context['cur_table_name'] = $substep->test_args[0]; + Maintenance::$tool->logProgress(Lang::getTxt('log_skipped', file: 'Maintenance')); - if ($substep->isCandidate()) { - $table_success = $substep->execute(); + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'next' => $substeps[Maintenance::getCurrentSubStep()]->name, + 'skipped' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + ], + ]); - if (Sapi::isCLI()) { - echo $table_success ? 'done.' : 'failed.'; + continue; } - } elseif (Sapi::isCLI()) { - echo 'skipped.'; + } catch (\Throwable $e) { + Maintenance::$tool->logProgress(Lang::getTxt('log_failed_with_error', ['error' => $e->getMessage()], file: 'Maintenance')); + + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'failed' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + 'msg' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], + ]); + + return false; } + try { + if (!$substep->execute()) { + Maintenance::$tool->logProgress(Lang::getTxt('log_failed', file: 'Maintenance')); + + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'completed' => false, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + ], + ]); + + return false; + } + } catch (\Throwable $e) { + Maintenance::$tool->logProgress(Lang::getTxt('log_failed_with_error', ['error' => $e->getMessage()], file: 'Maintenance')); + + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'failed' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, + 'msg' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ], + ]); + + return false; + } + + Maintenance::$tool->logProgress(Lang::getTxt('log_done', file: 'Maintenance')); + // Increase our current substep by 1. Maintenance::setCurrentSubStep(); + Maintenance::setCurrentStart(0); + // If this is JSON to keep it nice for the user do one table at a time anyway! if (Maintenance::isJson()) { - Maintenance::jsonResponse( - [ - 'current_table_name' => str_replace(Config::$db_prefix, '', Maintenance::$context['cur_table_name']), - 'current_table_index' => Maintenance::getCurrentSubStep(), - 'substep_progress' => Maintenance::getSubStepProgress(), + Maintenance::jsonResponse([ + 'name' => $substep->name, + 'next' => $substeps[Maintenance::getCurrentSubStep()]->name, + 'completed' => true, + 'substep' => Maintenance::getCurrentSubStep(), + 'start' => Maintenance::getCurrentStart(), + 'total' => Maintenance::$total_substeps, + 'debug' => [ + 'call' => $substep::class, ], - ); + ]); } } diff --git a/Themes/default/InstallTemplate.php b/Themes/default/InstallTemplate.php index de421d5eb25..7bad5c74083 100644 --- a/Themes/default/InstallTemplate.php +++ b/Themes/default/InstallTemplate.php @@ -467,10 +467,12 @@ public static function adminAccount(): void */ public static function finalize(): void { + MaintenanceTemplate::warningsAndErrors(); + echo '

', Lang::$txt['congratulations_help'], '

'; - MaintenanceTemplate::warningsAndErrors(); + MaintenanceTemplate::showLog(); // Install directory still writable? if (Maintenance::$context['dir_still_writable']) { diff --git a/Themes/default/MaintenanceTemplate.php b/Themes/default/MaintenanceTemplate.php index 039e9e05abc..f8288f3bfdd 100644 --- a/Themes/default/MaintenanceTemplate.php +++ b/Themes/default/MaintenanceTemplate.php @@ -281,26 +281,64 @@ public static function convertUtf8(): void // Show the continue button. Maintenance::$context['continue'] = true; - echo ' -

', Lang::getTxt('upgrade_wait2', file: 'Maintenance'), '

- - ', Lang::getTxt('upgrade_completedtables_outof', Maintenance::$context), ' -
- -
-

- ', Lang::getTxt('upgrade_current_table', file: 'Maintenance'), ' "', Maintenance::$context['cur_table_name'], '" -

'; - - // If we dropped their index, let's let them know. - if (!empty(Maintenance::$context['dropping_index'])) { + self::showStepWithSubSteps('convertutf8', 'utf8_done'); + } + + /** + * Show the contents of the log, if available. + */ + public static function showLog(): void + { + if (!empty(Maintenance::$context['log_contents'])) { echo ' -

', Lang::getTxt('show_log', file: 'Maintenance'), ' + +

'; } + } + + /************************* + * Internal static methods + *************************/ + + /** + * Shows the HTML for a step that has substeps. + */ + protected static function showStepWithSubSteps(string $type, string $done_param): void + { + echo ' +

', Lang::getTxt('upgrade_performing_substeps', ['type' => $type], file: 'Maintenance'), '

+

', Lang::getTxt('upgrade_please_be_patient', file: 'Maintenance'), '

+ +
'; - // Completion notification. echo ' -

', Lang::getTxt('upgrade_conversion_proceed', file: 'Maintenance'), '

'; +

', + Lang::getTxt( + 'upgrade_current_substep', + [ + 'substep' => '' . (Maintenance::$context['current_substep'] ?? '') . '', + ], + file: 'Maintenance', + ), + '

'; + + echo ' + ', + Lang::getTxt( + 'upgrade_substep_progress', + [ + 'substep_num' => '' . Maintenance::getCurrentSubStep() . '', + 'total_substeps' => Maintenance::$total_substeps, + 'type' => $type, + ], + file: 'Maintenance', + ), + ''; + + echo ' +

', Lang::getTxt('upgrade_step_complete', ['step' => Lang::getTxt('upgrade_step_' . $type, file: 'Maintenance')], file: 'Maintenance'), '

'; echo '